09 面向对象:通过词法作用域和调用点理解 this 绑定
今天,我们来讲讲 JavaScript 中的 this。
讲 this 的资料有很多,其中不少已经把这个概念讲得很清楚了。但是为了课程的系统性,我今天也从对象和面向对象的角度来说一说。
从直观的印象来看,你可能觉得 this 指的是函数本身或它所在的范围,其实这样的理解都是不对的。在 JavaScript 中,this 是在运行时而不是编写时绑定的。所以要正确地使用它,需要考虑到函数调用时的执行上下文。
一、默认绑定
javascript
function aLogger() {
console.log(this.a);
}
var a = 2;
aLogger(); // 2在全局定义的函数,默认绑定到全局上下文,this 指向 window(非严格模式)。
注意:在 strict mode 下,默认绑定会返回 undefined。
二、隐式绑定
当函数作为对象的方法调用时,this 绑定到该对象:
javascript
function aLogger() {
console.log(this.a);
}
var obj = {
a: 3,
logger: aLogger
};
var a = 2;
obj.logger(); // 3隐式绑定的问题:当把方法赋值给全局变量时,绑定会丢失:
javascript
var objLogger = obj.logger;
objLogger(); // 2(绑定丢失,指向全局)三、显式绑定
使用 call 或 apply 强制指定 this:
javascript
function logger() {
console.log(this.a);
}
var obj = {
a: 3
};
logger.call(obj); // 3
logger.apply(obj); // 3四、硬性绑定
使用 ES5 的 bind 方法,无论如何调用,this 始终指向指定对象:
javascript
function logger() {
console.log(this.a);
}
var obj = {
a: 3
};
var hardBinding = logger.bind(obj);
hardBinding(); // 3
hardBinding.call(window); // 3(仍然指向 obj)五、new 绑定
使用 new 创建实例时,this 指向新创建的对象:
javascript
function logger(a) {
this.a = a;
console.log(this.a);
}
var loggerA = new logger(2); // 2new 绑定优先级高于 hard binding:
javascript
function logger(a) {
this.a = a;
}
var obj1 = {};
var hardBinding = logger.bind(obj1);
hardBinding(2);
console.log(obj1.a); // 2
var obj2 = new logger(3);
console.log(obj1.a); // 2
console.log(obj2.a); // 3六、绑定优先级
| 绑定类型 | 优先级 | 说明 |
|---|---|---|
| new 绑定 | 最高 | 构造函数创建新实例 |
| 显式绑定 | 次之 | call/apply/bind |
| 隐式绑定 | 较低 | 作为对象方法调用 |
| 默认绑定 | 最低 | 全局调用 |
七、箭头函数的 this
箭头函数的 this 不遵循普通绑定规则,而是在词法作用域中定义:
javascript
function logger() {
return (a) => {
console.log(this.a);
};
}
var obj1 = { a: 2 };
var obj2 = { a: 3 };
var logger1 = logger.call(obj1);
logger1.call(obj2); // 2(箭头函数的 this 固定为 obj1)箭头函数常用场景 - setTimeout:
javascript
function logger() {
setTimeout(() => {
console.log(this.a);
}, 1000);
}
var obj = { a: 2 };
logger.call(obj); // 2八、软性绑定
自定义软性绑定,在 this 丢失时提供默认对象:
javascript
if (!Function.prototype.softBind) {
Function.prototype.softBind = function(obj) {
var fn = this,
curried = [].slice.call(arguments, 1),
bound = function() {
return fn.apply(
(!this ||
(typeof window !== "undefined" && this === window) ||
(typeof global !== "undefined" && this === global)
) ? obj : this,
curried.concat.apply(curried, arguments)
);
};
bound.prototype = Object.create(fn.prototype);
return bound;
};
}九、柯里化应用
bind 支持预设参数,实现函数的部分应用:
javascript
function fullFunc(x, y, z) {
return x + y + z;
}
const partialFunc = fullFunc.bind(this, 1, 2);
partialFunc(9); // 12(1 + 2 + 9)十、总结
this 的绑定是 JavaScript 中与闭包同等重要的概念:
- 默认绑定:非严格模式下指向全局,严格模式为 undefined
- 隐式绑定:作为对象方法调用时指向对象
- 显式绑定:通过 call/apply/bind 强制指定
- 硬性绑定:bind 始终保持 this 指向
- new 绑定:优先级最高,创建新实例
- 箭头函数:this 固定为词法作用域中的值
理解 this 的绑定规则,才能在 JavaScript 中熟练运用面向对象编程。