this全面解析
调用位置
在理解this
之前,我们需要理解函数的调用位置,分析调用栈(就是为了到达当前执行位置所调用的所有函数),调用位置就在当前正在执行的函数的前一个调用中。
调用栈是:baz
// 因此当前调用位置是全局作用域
console.log( "baz" );
bar( ); // <— bar的调用位置
}
function bar( ) {
// 当前调用栈baz -> bar
// 因此当前调用位置在baz中
console.log( "bar" );
foo( ); // <— foo的调用位置
}
function foo( ) {
//当前调用栈 baz -> bar -> foo
// 当前调用位置在bar中
console.log( "foo" );
}
baz( ); // <— baz的调用位置
注意我们是如何(从调用栈中)分析出真正的调用位置的,因为它决定了this
的绑定。
绑定位置
你必须找到调用位置,然后判断需要应用下面四条规则中的哪一条。我们首先会分别解释这四条规则,然后解释多条规则都可用时它们的优先级如何排列。
默认绑定
在全局作用域下声明的变量就是全局对象的一个属性,这不是通过赋值得来的,而是它们本身就是一回事,就像一个硬币的两面性那样。在上面的foo
调用时,this
指向默认的绑定,也就是全局对象。
那么如何判断这里的确是应用了默认绑定呢?可以通过分析调用位置来看看foo
是如何调用的。foo
是直接食用不带任何修饰的函数引用进行调用的,因此只能使用默认绑定,无法应用其他规则。在严格模式下,全局对象将无法使用默认绑定,因此this
会绑定到undefined
注意这里有一个微妙但是非常重要的细节
隐式绑定
对象属性引用链中只有最顶层或者说最后一层会影响调用位置,就像
a.b.c.d.foo( )
此事这个函数中的this
被绑定到a.b.c.d
这个对象上隐式丢失,类似于
bar = a.foo( )
而后以bar( )
调用,this
会默认绑定到全局作用域 (假定bar
当前是全局对象的属性)
更微妙、更常见的情况发生在传入回调函数时
函数的调用位置
}
var obj = {
a: 1,foo: foo
};
var a = “frome globa”; // 全局变量的属性a
doFoo(obj.foo); // 对于参数传入的隐式赋值 此时参数fn指向foo
我们可以看到,参数传递就是一种隐式赋值,其中发生的doFoo(obj.foo)
函数调用,对于参数发生的赋值语句就是fn = obj.foo
,参数中的fn调用位置使用的是this
的默认绑定值,也就是this
。
其实,不仅是我们自己定义的函数,就连一些全局属性(函数),当我们传入函数引用作为实参的时候,都会发生实参到行参的赋值,这个时机如果不是在严格模式下,函数被调用话,this
值都会默认绑定到全局对象(在浏览器中就是window
对象)。
显示绑定
在分析隐式绑定时,我们必须在一个对象内部包含一个指向函数的属性,并通过这个属性间接引用函数,从而把this
间接(隐式)绑定到这个对象上。
终于提到了call
和apply
,当我们不想通过隐式绑定那样来把this
绑定到对象上的时候,我们需要使用call
和apply
如果你传入了一个原始值(字符串类型、布尔类型或者数字类型)来当作this
绑定的对象,这个原始值会被转换成它的对象形式(new String( ) new Boolean( ) new number( )),这通常被称为装箱。
硬绑定
思考下面的代码:
修改它的this
上面的代码中,我们在bar
函数中显示的绑定了foo
的this
,这种绑定是不可以被修改的,因此我们称为硬绑定。
典型的硬绑定应用场景就是bind
的使用
API调用的上下文
实际上并不存在所谓的“构造函数”,只有对于函数的“构造调用”。使用new
来调用函数,会自动执行下面的操作:
优先级
this
的优先级从高到低依次为:
如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到
undefined
,否则绑定到全局对象。
但是,以上的规则只是符合大多数情况而言的,凡事都有例外:
当把
null
或则undefined
作为this
的绑定对象传入call
、apply
中,这些值在调用的时候会被忽略,实际应用的是默认绑定规则。软绑定会对指定的函数进行封装,首先检查调用时的
this
,如果this
绑定到全局对象或者undefined
,那就班制定的默认对象绑定到this
,否则不会修改this
。return fn.apply(!this : this === (window || golbal) ? obj : this)
this 词法
在ES6中有一种无法使用这些规则的特殊函数类型:箭头函数。
箭头函数并不是使用function
关键字定义的,而是使用被称为“胖箭头”的操作符=>
定义的,箭头函数不使用this
的四种标准规则,而是根据外层(函数或者全局)作用域来决定this
。
{
console.log(this.name); // 这里的this继承自外层的foo函数
}
}
var a = {
name: "John"
};
var b = {
name: "Tom"
};
var bar = foo.call(a); // 将foo的this绑定到a
var.call(b); // John 返回的箭头函数不会应用call强制绑定
通过上面的代码我们可以很清楚直观的了解箭头函数绑定this
的特殊性。
除了箭头函数之外还有一种方法来显示的否定this
机制,那就是通过在需要绑定的外层声明一个变量并显示的传入this
,var self = this
;
小结
为了判断一个运行中函数的this
绑定,需要找到这个函数的直接调用位子,然后通过上面介绍的4中绑定规则来依次进行判断,但是在这里要注意在ES6中的箭头函数并不使用这4种绑定规则。