在javascript中,this关键字总让一些初学者迷惑,Function.prototype.call, Function.prototype.apply这两个方法广泛的运用。我们有必要理解这几个概念。

一:this

跟别的语言大相径庭的是,javascript的this总是指向一个对象,而具体指向那个对象在运行时基于函数的执行环境动态绑定的,非函数被声明时的环境。

(1).this的指向

除去不常用的with和eval情况,具体到实际的应用中,this的指向大致分为下面4种。

  • 作为对象的方法调用
  • 作为普通函数调用
  • 构造器调用
  • Function.prototype.call或Function.prototype.apply调用

简单做介绍:

1)作为对象的方法调用

当函数作为对象的方法被调用时,this指向该对象:

 var obj = {
a:1,
getA:function() {
alert( this === obj ); //true
alert( this.a ); //
}
}
obj.getA()

 2)作为普通函数调用

当函数不作为对象的属性被调用时,也就是我们常说的普通函数方式,此时的this总是指向全局对象。在浏览器里,就是window对象

    window.name = 'globalName';

    var getName = function() {
return this.name
} //console.log( getName() ) // globalName var myObject = {
name : 'seven',
getNameA : function() {
return this.name
}
} var a = myObject.getNameA
console.log( a() )

有的时候我们会遇见一些困扰,比如在div节点内部,有一个局部的callback方法,callback作为普通的函数调用时 ,callback内部的this指向了window,但我们往往想让他指向该div节点。如下

<div id="div1">
div1
</div>
<script type="text/javascript">
document.getElementById("div1").onclick = function() {
var that = this;
console.log(this.id)
var callback = function(){
console.log(that.id)//this.id = xx
}
callback()
}
</script>

注意:在ES5的strict模式下,this已经被规定不会指向全局对象,而是undefined

3)构造器调用

javascript目前没有类,但是从构造器中创建对象,同时也提供了new运算符,使得构造器看起来更像一个类。
除了宿主提供的一些内置函数,大部分javascript函数都可以当做构造器来使用。构造器的外表跟普通函数一模一样,他们的区别在于被调用的方式。当用new运算符调用函数时,该函数会返回一个对象,通常情况下,构造器里的this就指向返回的这个对象,如下

    var myClass = function( name , sex ) {
this.name = name;
this.sex = sex;
}; var newObj = new myClass('jj','sxxx');
console.log(newObj.sex + newObj.name)

但是用new调用构造器时,还要注意一个问题,如果构造器显式的返回了一个object类型的对象,那么此次运算结果最终会返回这个对象,而不是我们之前期待的this

    var myClass = function( name ) {
this.name = name ;
return {
name : 'anam'
}
} var myObj = new myClass('jack')
console.log(myObj.name) ;//anam

如果构造器不显式的返回任何数据,或者是返回一个对象类型的数据,就不会造成上述问题

    var myClass = function( name ) {
this.name = name ;
name : 'anam'
} var myObj = new myClass('jack')
console.log(myObj.name) ;//jack

4)Function.prototype.call和Function.prototype.apply调用

跟普通的函数调用相比,用Function.prototype.call或Function.prototype.apply可以动态的改变传入函数的this

var obj1 = {
name: 'seven',
getName: function() {
return this.name
}
} var obj2 = {
name : 'jack'
} console.log(obj1.getName()) ;//seven
console.log(obj1.getName.call(obj2)) //jack

call和apply能很好的体现javascript的函数语言特性,在javascript中,几乎每一次编写函数式语言风格代码都离不开call和apply。在javascript诸多版本的设计模式中,也用到了call和apply。在以后我们分析中会更多说到。

(2)丢失的this

这是一个经常遇到的问题

var obj = {
myname : 'sven',
getNname : function() {
return this.myname;
}
} console.log(obj.getNname()) // sven var getname2 = obj.getNname console.log(getname2()) ;//undefined

当调用obj.getName时,getName方法是作为obj对象的属性被调用的。(本文1.1)此时,this指向obj对象。
所以obj.getName输出 'sven'

当另外一个变量getName2来引用obj.getName,并且调用getname2时,(本文1.2)提到的规律,此时是普通函数调用方式,this是指向全局window的,所以程序执行的是undefined.

我们再来看一个例子。

document.getElementById()这个方法名字实在有点长。我们尝试用一个短的函数代替它。

    var getId = function(id) {
return document.getElementById(id)
}
getId('div1')

我们也许想过为什么不用下面更简单方式

    var getId = document.getElementById;
getId('div1')

我们在浏览器中运行会出现一个错误,这是因为许多浏览器引擎的document.getElementById方法内部需要用到this。这个this本来被期望指向document,当getElementById方法作为document对象属性被调用时,方法内部的this确实是指向document的。

当getId来引用document.ElementById 之后,再调用getId,此时就成了普通函数调用,函数内部的this指向了window,而不是原来的document.

我们可以尝试着利用apply把document当做this传入getId函数。帮助修正this

document.getElementById = (function( func ) {
return function() {
return func.apply( document, arguments )
}
})(document.getElementById) var getId = document.getElementById;
var div = getId('div1'); console.log(div) ;//<div id="div1">div1</div>
console.log(div.id) ;//div1

二:call和apply

ES3给Function的原型定义了两个方法。Function.prototype.call和Function.prototype.apply。在实际开发中,特别是在一些函数式代码编写中,call和apply方法尤其有用。在javascript的设计模式中,应用也十分广泛。能熟练应用这两个方法,是成为一名javascript程序员的重要一步。

(1)call和apply的区别

Function.prototype.call和Function.prototype.apply都是非常常用的方法,它们的作用一模一样,区别仅仅在传入参数形式的不同。

apply接受两个参数,第一个参数指定了函数体内this对象的指向,第二个参数为一个带下标的集合(这个集合可以是数组,也可以为类数组)。apply方法把这个集合的元素作为参数传递给被调用的函数。

    var func = function( a, b, c ){
console.log([a,b,c]) ;//输出[1, 2, 3]
}
console.log(func())
func.apply(null, [1,2,3])

在这段代码中,参数1,2,3被放在数组中一起传入func函数。它们分别对应func参数列表中的a,b,c

call传入参数数量不固定,跟apply相同的是,第一个参数也是代表函数体内的this指向,从第二个参数开始往后,每个参数被依次传入函数。

    var func = function( a, b, c ) {
console.log([a, b, c]) //[1, 2, 3]
} func.call( null, 1, 2, 3 )

当调用一个函数时,javascript的解释器并不会计较形参和实参的数量,类型以及顺序上的区别,javascript的参数在内部就是用一个数组来表示的。从这个意义上说,apply比call的使用率更高。我们不必关心具体有多少参数被传入函数,只要apply一股脑的推过去就行。

call是包装在apply上面的一颗语法糖,如果我们明确知道了函数接受多少个参数,而且想一目了然的表达形参和实参的对应关系。那么也可以用call来传送参数。

当使用call或apply的时候,如果我们传入的第一个参数为null,函数体内的this会指向默认的宿主对象。在浏览器中则是window.

var func = function( a, b, c ){
console.log( this === window ) //true
};
func.apply(null,[1,2,3])

但是在严格模式下,函数体内的this还是为null

var func = function( a, b, c ){
"use strict"
console.log( this === null ) //true
};
func.apply(null,[1,2,3])

有时我们使用call或者apply的目的不在于指定this指向,而是另有用途,比如借用其它的对象方法。那么我们可以传入null来代替某个具体对象。

Math.max.apply(null,[1,2,3,4,5,6,7]) //

(2)call和apply的用途

前面说过,能够熟练使用call和apply,是成为一名正真的javascript程序员的重要一步,下面我们就来详细说说call和apply在实际开发中的用途。

1).改变this的指向

call和apply最常见的用途就是改变this的指向,下面我们来看个例子:

    var obj1 = {
name : 'seven'
} var obj2 = {
name : 'anne'
} window.name = 'window' var getName = function() {
console.log(this.name)
} getName();
getName.call(obj1)
getName.call(obj2)

当执行getName.call(obj1)时,getName函数体内的this就指向obj1对象,所以此处的

    var getName = function() {
console.log(this.name)
}

相当于:

    var getName = function() {
console.log(obj.name)
}

在实际开发中,经常会遇到this指向被不经意改变的场景,比如有一个div节点,func函数体内的this就指向的window,而不是我们预期的div.

<div id="div1">div1</div>

<script type="text/javascript">
document.getElementById('div1').onclick = function(){
console.log(this.id) //div1
}
</script>

假如该事件函数中有一个内部函数func,在事件的内部调用 func函数时,函数体内的this就指向了window,而不是我们预期的div,见如下代码:

document.getElementById('div1').onclick = function(){
console.log(this.id) //div1
function func(){
console.log(this.id) //undefined
}
func()
}

这个时候,我们用call来修正func函数内的this,使其依然指向div

document.getElementById('div1').onclick = function(){
console.log(this.id) //div1
function func(){
console.log(this.id) //div1
}
func.call(this)
}

使用call修正this的场景,我们并非第一次遇到,上一节中,我们曾经修复过document.getElementById函数内部“丢失”的this,代码如下:

document.getElementById = (function( func ){
return function() {
return func.apply( document, arguments );
}
})( document.getElementById ) var getId = document.getElementById
var div = getId('div1')
console.log(div.id) //div1

2).Function.prototype.bind

大部分高级浏览器都实现了内置的Function.prototype.bind,用来指定函数内部的this指向。(即使没有原生的Function.prototype.bind,模拟起来也不算难事)

Function.prototype.bind = function( context ) {
var self = this;
return function() {
return self.apply( context, arguments )
}
}; var obj = {
name : 'seven'
} var getName = function() {
console.log(this.name)
}.bind(obj) getName() ; //seven

我们通过Function.prototype.bind来“包装” func函数,并且传入一个对象context当做参数,这个context对象就是我们要修正的this对象。

3).借用其它对象的方法

我们知道,杜鹃既不会筑巢,也不会孵鸟,而是把自己的蛋生在其它的鸟巢,让他们代为孵化和养育,同样,在javascript中也存在借用现象。

借用的第一种方法是“借用构造函数”,通过技术,可以实现一些类似的继承结果。

var A = function( name ){
this.name = name;
} var B = function() {
A.apply(this, arguments);
} B.prototype.getName = function() {
return this.name
} var b = new B('sven');
console.log(b.getName()) //sven

借用方法的第二种运用场景跟我们的关系更密切。

函数的参数列表 arguments 是一个类数组对象,虽然它也有下标,但它并非正真的数组,所以不能像数组一样,进行排序操作或者往集合里添加一个新的元素。这种情况下,我们常常会借用Array.prototype对象上的方法,比如:想往argumments中添加一个新的元素,通常会借用Array.prototype.push

(function(){
Array.prototype.push.call( arguments, 3)
console.log(arguments) //[12, 1, 1, 23, 3]
})(12,1,1,23)

在操作arguments时,我们非常频繁的找Array.prototype对象借用方法。

想把arguments转成真正的数组的时候,可以借用Array.prototype.slice方法,想截取arguments列表中的头一个元素时,可以使用Array.prototype.shift方法,这种机制的内部原理,我们可以翻开V8引擎源码,以Array.prototype.push方法为例。看看其实现

function ArrayPush() {
var n = TO_UINT32( this.length ); //被push的对象的length
var m = %_ArgumentsLength(); //push的参数个数 for (var i = 0; i < m; i++) {
this[i + n] = %_ArgumentsLength( i ); //复制元素 (1)
} this.length = n + m; //修正length属性的值 (2)
return this.length;
};

从这段代码我们看出,Array.prototype.push实际上是一个属性复制的过程,把参数按照下标依次添加到被push的对象上面,顺便修改了这个对象的length属性,至于被修改的对象是谁,到底是数组还是类数组对象,这一点并不重要。

由此,我们可以推断,我们把“任意对象”传入Array.prototype.push:

var a = {};

Array.prototype.push.call(a, 'frist');
Array.prototype.push.call(a, 'second');
console.log(a.length) //
console.log(a[0]) //frist

对于“任意对象”,我们从ArrayPush()函数的(1)和(2)可以猜到,这个对象还要满足:

  • 对象的本身可以读取属性
  • 对象的lenth属性可读写

(本文已经完结

上一篇文章:(一)面向对象的javascript  下一篇文章 (三)闭包和高阶函数

(二)this、call和apply的更多相关文章

  1. 区别和详解:js中call()和apply()的用法

    1.关于call()和apply()的疑点: apply和call的区别在哪里 什么情况下用apply,什么情况下用call apply的其他巧妙用法(一般在什么情况下可以使用apply) 2.语法和 ...

  2. js中继承的几种用法总结(apply,call,prototype)

    一,js中对象继承 js中有三种继承方式 1.js原型(prototype)实现继承 <SPAN style="BACKGROUND-COLOR: #ffffff">& ...

  3. js: this,call,apply,bind 总结

    对js中的一些基本的很重要的概念做一些总结,对进一步学习js很重. 一.this JavaScript 中的 this 总是指向一个对象,而具体指向那个对象是在运行时基于函数的执行环境动态绑定的,而非 ...

  4. js中继承的方法总结(apply,call,prototype)

    一,js中对象继承 js中有三种继承方式 1.js原型(prototype)实现继承 代码如下: <SPAN style="<SPAN style="FONT-SIZE ...

  5. 理解new构造函数和apply以及call

    今天在看设计模式的时候,遇到一些挺低级的东西,搞不懂,顾查阅资料整理记录一番. 先了解一下new构造函数的过程: function func(){ console.log('do'); } var f ...

  6. 源码来袭:call、apply手写实现与应用

    关于this指向可以了解我的另一篇博客:JavaScript中的this指向规则. 一.call与apply的使用 回顾call与apply的this指向: var value = "win ...

  7. call Apply bind详解

    call方法: 语法:call(thisObj,'',''........) 定义:调用一个对象的一个方法,以另一个对象替换当前对象 说明:call方法可以用来代替另一个对象调用一个方法.call方法 ...

  8. Scala学习教程笔记二之函数式编程、Object对象、伴生对象、继承、Trait、

    1:Scala之函数式编程学习笔记: :Scala函数式编程学习: 1.1:Scala定义一个简单的类,包含field以及方法,创建类的对象,并且调用其方法: class User { private ...

  9. Javascript中call,apply,bind的区别

    一.探索call方法原理 Function.prototype.call = function(obj) { // 1.让fn中的this指向obj // eval(this.toString().r ...

随机推荐

  1. windows服务器让WEB通过防火墙的问题

    服务器环境:windows server 2012 X64WEB服务器:IIS开放8080,PHPSduty开放80 如果关闭防火墙的情况下,不论是IIS还是安装的其他的WEB服务器,都可以正常访问. ...

  2. linq to sql 查找所有开票金额大于回款金额的项目

    查找所有开票金额大于回款金额的项目 TB_Projects 项目表 TB_Recipts 发票表 TB_Finances 回款表 TB_Projects  一对多 TB_Recipts TB_Proj ...

  3. Restframework 频率throttle组件实例-3

    频率逻辑: from rest_framework.throttling import BaseThrottle import time VISIT_RECORD={} class VisitThro ...

  4. Flask 视图,模板,蓝图.

    https://www.cnblogs.com/wupeiqi/articles/7552008.html 1. 配置文件 from flask import Flask app =Flask(__n ...

  5. Day 18 正则表达式.

    一.字符 .匹配除换行符以外的任意字符. \w 匹配字母数字或者下划线. \s 匹配任意的空白符 \d 匹配数字 \n 匹配一个换行符 \t 匹配一个制表符 ^ 匹配字符串的开始. $ 匹配字符串的结 ...

  6. 782. Transform to Chessboard

    An N x N board contains only 0s and 1s. In each move, you can swap any 2 rows with each other, or an ...

  7. Array-Find Pivot Index

    Given an array of integers nums, write a method that returns the "pivot" index of this arr ...

  8. JS弹出对话框函数alert(),confirm(),prompt()

    1,警告消息框alert() alert 方法有一个参数,即希望对用户显示的文本字符串.该字符串不是 HTML 格式.该消息框提供了一个“确定”按钮让用户关闭该消息框,并且该消息框是模式对话框,也就是 ...

  9. 【JS深入学习】——animationend 事件兼容性说明

    animationend 1.兼容性 animationend只有两种形式:animationend和webkitAnimationEnd webkitAnimationEnd 中 w 一定要小写,a ...

  10. puppet更新失败

    # puppet-updatepuppet: no process foundWarning: Unable to fetch my node definition, but the agent ru ...