this的原理
this 的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。 this 实际上是在函数被调用时发生的绑定,它指向什么完全取决于函数在哪里被调用。
this绑定分类
如果要判断一个运行中函数的 this 绑定,就需要找到这个函数的直接调用位置。找到之后就可以应用下面这四条规则来判断 this 的绑定对象。
- 由new调用?
function foo(a) {
this.a = a;
}
var bar = new foo(2);
console.log( bar.a ); // 2
this绑定到新创建的对象。
- 由call或者apply(或者bind)调用?
function foo() {
console.log( this.a );
}
var obj1 = { a: 2,foo: foo };
var obj2 = { a: 3,foo: foo };
obj1.foo(); // 2
obj2.foo(); // 3
obj1.foo.call( obj2 ); // 3
obj2.foo.call( obj1 ); // 2
this绑定到指定的对象。
- 由上下文对象调用?
var obj = {
foo: function () { console.log(this.bar) },
bar: 1
};
var foo = obj.foo;
var bar = 2;
obj.foo() // 1
foo() // 2
this绑定到方法被谁执行的的那个对象上。
- 默认
function foo() {
console.log( this.a );
}
var a = 2;
foo(); // 2
foo函数在全局作用域中执行,this绑定到全局作用域中。
function foo() {
'use strict'
console.log( this.a );
}
var a = 2;
foo(); // Uncaught TypeError: Cannot read property 'a' of undefined
this在严格模式下绑定到undefined,而不会绑定到全局作用域。
4种绑定的优先级
- new绑定和显示绑定优先级比较?
function foo(something) {
this.a = something;
}
var obj1 = {};
var bar = foo.bind( obj1 );
bar( 2 );
console.log( obj1.a ); // 2
var baz = new bar(3);
console.log( obj1.a ); // 2
console.log( baz.a ); // 3
baz.a为3,是同时使用new绑定和bind绑定的结果。说明new绑定优先级 > 显式绑定。
- 显示绑定和调用对象优先级比较?
var obj = {
foo: function () { console.log(this.bar) },
bar: 1
};
var foo = obj.foo;
var bar = 2;
foo() // 2
foo.call(obj) // 1
foo()在全局作用域中的打印值为2,调用call更改this为obj的打印结果为1。说明 显示绑定 > 调用对象优先级。
综上所述,优先级顺序为 new绑定优先级 > 显式绑定 > 调用对象优先级 > 默认。
箭头函数
ES6 中的箭头函数并不会使用四条标准的绑定规则,而是根据当前的词法作用域来决定 this。
具体来说,箭头函数没有自己的this,会继承外层函数的 this 绑定。
var obj = {
foo: () => { console.log(this.bar) ;console.log(this)},
bar: 1
};
var foo = obj.foo;
var bar = 2;
obj.foo() // 2
foo() // 2
这个例子与“this绑定分类”第3点的例子一模一样,唯一的区别是obj里的foo函数变成了箭头函数,使得obj.foo()的结果是2. obj在全局中作用域中进行定义,因此this就是全局作用域。
总结
箭头函数里的this是在函数被定义外层的this。 非箭头函数里的this取决于函数被调用时所在的环境里的this。
非箭头函数里的this绑定按照优先级从高到低如下:
- 由new调用?绑定到新创建的对象。
- 由call或者apply(或者bind)调用?绑定到指定的对象。
- 由上下文对象调用?绑定到那个上下文对象。
- 默认:在严格模式下绑定到undefined,否则绑定到全局对象。
参考资料
《你不知道的JavaScript》