II-第2章 this全面解析
从上一章知道理解this,就是要理解函数被调用的位置。但实践起来有点复杂。
重要的是分析调用栈,在当前正在执行函数前一个调用中。在函数第一行设置`debugger`,开发者工具调用栈的第二个元素就是真正的调用位置。
四条绑定规则
1. 独立调用
独立函数调用,即使用不带任何修饰的函数引用进行调用的,因此只能使用默认绑定,无法应用其他规则。
this 的绑定规则完全取决于调用位置,但是只有 foo() 运行在非 strict mode 下时,默认绑定才能绑定到全局对象;严格模式下与 foo() 的调用位置无关:
function foo() {
"use strict";
console.log( this.a );
}
var a = 2;
foo(); // TypeError: this is undefined
function foo() {
console.log( this.a );
}
var a = 2;
foo(); // 2
2. 隐式绑定
当函数引用有上下文对象时,隐式绑定规则会把函数调用中的 this 绑定到这个上下文对象。
如下:
function foo() {
console.log( this.a );
}
var obj2 = {
a: 42,
foo: foo
};
var obj1 = {
a: 2,
obj2: obj2
};
obj1.obj2.foo(); // 42
隐式丢失
一个最常见的 this 绑定问题是被隐式绑定的函数会丢失绑定对象,它会应用默认绑定,从而把 this 绑定到全局对象或者 undefined 上,取决于是否是严格模式。
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
var bar = obj.foo; // 函数别名!
var a = "oops, global"; // a 是全局对象的属性 bar(); // "oops, global"
回调函数里也会有this丢失现象
3. 显式绑定
call和apply可以直接指定 this 的绑定对象,称之为显式绑定。
function foo() {
console.log(this.a);
}
var obj = {
a:2
};
foo.call(obj); // 2
硬绑定
function foo() {
console.log( this.a );
}
var obj = {
a:2
};
var bar = function() {
foo.call( obj );
};
bar(); // 2
setTimeout( bar, 10 ); // 2
// 硬绑定的 bar 不可能再修改它的 this
bar.call( window ); // 2
在bar中强制把foo的this绑定到obj上,无论之后怎么调用bar,都不会修改this。这种显示强绑定,称为硬绑定。
ES5 中提供了内置的方法Function.prototype.bind也是强绑定。
API调用的“上下文”(context)
[1, 2, 3].forEach( foo, obj );
// 1 awesome 2 awesome 3 awesome
这种也是通过call和apply实现的显示绑定。
4. new绑定
js通过new来调用函数,或者说发生构造函数调用时,会自动执行下面的操作。
- 创建(构造)一个全新的对象。
- 这个新对象会被执行原型连接。
- 这个新对象会绑定到函数调用的this。
- 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象。
优先级
四条绑定原则的优先级
- 由new调用? 绑定到新创建的对象。
- 由call或者apply(或者bind)调用? 绑定到指定的对象。
- 由上下文对象调用? 绑定到那个上下文对象。
- 默认:在严格模式下绑定到undefined,否则绑定到全局对象。
绑定例外
把 null 或者 undefined 作为 this 的绑定对象传入 call、apply 或者 bind,这些值 在调用时会被忽略,实际应用的是默认绑定规则。
函数的间接引用,会导致应用默认绑定。
软绑定
硬绑定可以强制绑定到指定对象(除new时),降低了函数的灵活性。采用软绑定方式可以实现和硬绑定相同的效果,同时保留隐式绑定修改this的能力。
if (!Function.prototype.softBind) {
Function.prototype.softBind = function(obj) {
var fn = this;
// 捕获所有 curried 参数
var curried = [].slice.call( arguments, 1 );
var bound = function() {
return fn.apply(
(!this || this === (window || global)) ?
obj : this
curried.concat.apply( curried, arguments )
);
};
bound.prototype = Object.create( fn.prototype );
return bound;
};
}
this语法
箭头函数() => {}无法使用上述四种规则。而是根据外层(函数或者全局)作用域来决定this。
箭头函数可以像 bind(..) 一样确保函数的 this 被绑定到指定对象,它用更常见的词法作用域取代了传统的 this 机制。
没有箭头函数之前我们习惯采用的方式
function foo() {
var self = this; // lexical capture of this
setTimeout( function(){
console.log( self.a );
}, 100 );
}
var obj = {
a: 2
};
foo.call( obj ); // 2
self = this 和箭头函数看起来都可以取代bind(..),但是从本质上来说,它们想替代的是 this 机制。
小结this全面解析
学习本章了解this绑定的方式,通过4条准则定位函数运行时this到底指向什么。
在箭头函数出来之后,用() => {}和bind可以解决大多数问题了。
ES5出来之后,很多js的问题被新语法取代了,但我们可以抽时间多了解新语法背后的历史,能更深入的了解js本身的魅力。