默认绑定
function foo() {
console.log(this.a);
}
var a = 2;
foo(); // 其实就是this.foo()
输出:2。
函数调用时应用了this的默认绑定,因此this.a其实就是全局变量中的a变量。
严格模式下,不使用默认绑定。因此foo方法里的this会绑定到undefined。
function foo() {
"use strict";
console.log(this.a);
}
var a = 2;
foo(); // 此时会报错TypeError
隐式绑定
function foo() {
console.log(this.a);
}
var obj = {
a: 3,
foo: foo
}
obj.foo(); // 调用方法foo时有上下文对象obj
输出:3。
当方法调用有上下文对象时,隐式绑定规则会将方法中的this绑定到这个上下文对象。因此此时foo方法里的this其实指向obj。this.a即obj.a。
对象属性引用链中只有最后一层会影响调用位置。看下例:
function foo() {
console.log(this.a);
}
var obj2 = { a: 'obj2', foo: foo};
var obj1 = { a: 'obj1', obj2: obj2};
obj1.obj2.foo(); // 调用方法foo时有上下文对象obj2
输出:obj2。
隐式丢失
var obj = { a:2, foo: foo};function foo() { console.log(this.a); }
var bar = obj.foo; // 方法别名!
var a = "oops, global"; // 全局对象的属性
bar();
输出:oops, global。
obj.foo指向foo方法体。
bar = obj.foo,则bar和obj.foo的指向相同,同指向foo方法体。
即bar是不带任何修饰的方法调用(无上下文),因此此处应用了默认绑定。
如果直接obj.foo(),则输出:2。因为有上下文obj。
看下面的例子:
function foo() { console.log(this.a); } function doFoo(fn) { fn(); } var obj = { a: 2, foo: foo }; var a = "oops, global"; doFoo(obj.foo);
输出:oops, global
参数传递其实就是一种隐式赋值。obj.foo指向foo方法体。
doFoo方法体里的fn=obj.foo,其实就和obj.foo的指向相同。
因此fn也指向foo方法体。
调用fn()时,没有上下文,调用位置就是在全局作用域。
在全局作用域下调用方法,this.a获取的也就是全局对象的属性了。
自己声明的方法传入JS内置的方法中,也是一样的结果。
setTimeout(obj.foo,100);
100ms后,也会输出:oops, global。
JS内置的setTimeout方法和下面伪代码类似:
function setTimeout(fn, delay) { // 等待delay毫秒 fn(); }
显式绑定
call(...)和apply(...)
call和apply的第一个参数就是this的绑定对象。
function foo(add) { console.log(this.a + add); } var obj = { a: 2 }; foo.call(obj, 4);
输出:6
foo方法体内的this指向就是obj。4作为参数,隐式赋值给add。
如果传入的第一个参数是原始值(字符串类型、布尔类型或数字类型),这个原始值会被隐式转换为它的对象形式(new String(...)、new Boolean(...)或new Number(...))。这叫做装箱。硬绑定
API调用的上下文
第三方库、JS语言和宿主环境中许多内置函数都提供了可选参数确保回调函数的this指向。
输出:1 awesome 2 awesome 3 awesomefunction foo(el) { console.log(el, this.id); } var obj = { id: 'awesome'}; [1,2,3].forEach(foo, obj);
上面这类方法实际上就是通过call和apply实现了显式绑定,将foo的this指向obj。
new绑定
JS的构造方法和其他面向对象语言的构造方法不同,它只是使用new操作符时被调用的方法。使用new来调用方法,会自动执行下面的操作:
- 创建一个全新的对象
- 这个新对象会被执行[[原型]]连接
- 这个方法里的this会自动指向这个新的对象
- 如果方法没有其他返回对象,那么new表达式中的方法调用会自动返回这个新的对象
优先级
方法是否在new中调用(new绑定)?如果是,this指向新创建的对象。
方法是否通过call、apply或硬绑定调用?如果是,this绑定的是指定的对象。
方法是否在某个上下文对象中调用?如果是,this绑定的是那个上下文对象。
如果都不是的话,使用的是默认绑定。如果是严格模式下,就绑定到undefined,否则绑到全局对象。
空绑定
call、apply、bind的第一个参数如果传入的是null或undefined,这些值在调用的时候会被忽略,实际应用的是默认绑定规则。
因此,可以用apply来展开一个数组,并被当做参数传入函数中。可以用bind对参数进行柯里化(预先设置一些参数)。
但用null可能会产生一些副作用。可能有些函数确实用到了this,那传入null的话,默认绑定规则会把this绑到全局对象。function foo(a,b,c) { console.log(a+b+c); } foo.apply(null, [2,3,4]); // 9 var bar = foo.bind(null, 2); bar(3,4); // 9
你可以传入Object.create(null)作为第一个参数。
Object.create(null)和{}不同,因为它不会创建Object.prototype这个委托,因此比{}更空。em instanceof Object 是false。function foo(a,b,c) { console.log(a+b+c); } var em = Object.create(null); foo.apply(em, [2,3,4]); // 9 var bar = foo.bind(em, 2); bar(3,4); // 9
间接引用
输出2。function foo() {console.log(this.a)} var a = 2; var o = {a:3,foo:foo}; var p = {a:4}; (p.foo = o.foo)();
因为赋值表达式p.foo = o.foo的返回值是目标方法的引用。因此调用位置是foo()而不是p.foo。所以用了默认绑定。
箭头函数
不使用this的四种标准规则,而是根据外层(函数或全局)作用域来决定this。
(参考《你不知道的JavaScript(上)》)