第七章 this
去年校招的时候,关于this基本每家公司必问。或者是出题考你,或者是让你简述改变this原理。关于this,个人认为本书讲的不如《你不知道的JavaScript(上)》讲得好,这本书只适合入门,如想深挖,还是建议童鞋们看下上面提到的那本书。
关于this,无论哪本书,都会提到这样一点,这也是学好this的前提。
当前函数的this是在函数被调用执行的时候才确定的。
记住这一点后,我将结合《你不知道的JavaScript(上)》,对this进行介绍。
1. 默认绑定
首先要介绍的是最常用的函数调用类型:独立函数调用。可以把这条规则看作是无法应用其他规则时的默认规则。
function foo () {
console.log(this.a);
}
var a = 2;
foo(); // 2
当调用foo()时,this.a被解析成了全局变量a,函数调用时应用了this的默认绑定,因此this指向全局对象。
如果使用严格模式,那么全局对象将无法使用默认绑定,因此this会绑定到undefined:
function foo () {
"use strict"
console.log(this.a);
}
var a = 2;
foo(); // TypeError: this is undefined
2. 隐式绑定
先看一段代码。
function foo () {
console.log(this.a);
}
var obj = {
a: 2,
foo: foo
};
obj.foo(); // 2
当foo()被调用时,它的落脚点确实指向obj对象。当函数引用有上下文对象时,隐式绑定规则会把函数调用中的this绑定到这个上下文对象。因为调用foo()时this被绑定到obj,因此this.a和obj.a是一样的。
对象属性引用链中只有最顶层或者说只有最后一层影响调用位置。
function foo () {
console.log(this.a);
}
var obj2 = {
a: 42,
foo: foo
};
var obj1 = {
a: 2,
obj2: obj2
};
obj1.obj2.foo(); // 42
隐式丢失
还是先看一段代码。
function foo () {
console.log(this.a);
}
var obj = {
a: 2,
foo: foo
};
var bar = obj.foo;
var a = 'oops, global';
bar(); // 'oops, global'
虽然bar是obj.foo的一个引用,但是实际上,它引用的是foo函数本身,因此此时的bar其实是一个不带任何修饰的函数调用,因此应用了默认绑定。
一种更微妙、更常见并且更出乎意料的情况发生在传入回调函数时:
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'
参数传递其实就是一种隐式赋值,因此我们传入函数时也会被隐式赋值。
JavaScript环境中内置的setTimeout()函数实现和下面的伪代码类似:
function setTimeout (fn, delay) {
// 等待delay毫秒
fn(); // 调用位置
}
关于隐形丢失,《JavaScript核心技术开发解密》一书给出两道还不错的思考题。这两道题这里不再给出答案,有兴趣的同学请思考后,使用控制台自行验证。
// 思考题1
function foo () {
console.log(this.a);
}
function active (fn) {
fn();
}
var a = 20;
var obj = {
a:10,
getA: foo,
active: active
}
active(obj.getA);
obj.active(obj.getA);
// 思考题2
var n = 'window';
var object = {
n: 'object',
getN: function () {
return function () {
return this.n;
}
}
}
console.log(object.getN()());
3. 显示绑定
JavaScript可以使用函数的call和apply方法改变this的指向。它们的第一个参数是一个对象,它们会把这个对象绑定到this,接着在调用函数时指定这个this。因为你可以直接指定this的绑定对象,因此我们称之为显示绑定。
function foo () {
console.log(this.a);
}
var obj = {
a: 2
};
foo.call(obj); // 2
通过foo.call(),我们可以在调用foo时强制把它的this绑定到obj上。
可惜显示绑定仍然无法解决我们之前提出的丢失绑定问题。
硬绑定
先看一段代码。
function foo () {
console.log(this.a);
}
var obj = {
a: 2
};
var bar = function () {
foo.call(obj);
};
bar(); // 2
setTimeout(bar, 100); // 2
// 硬绑定的bar不可能再修改它的this
bar.call(window); // 2
我们创建了函数bar(),并在它的内部手动调用了foo.call(obj),因此强制把foo的this绑定到了obj。无论之后如何调用函数bar,它总会手动在obj上调用foo,这种绑定是一种显示的强制绑定,因此我们称之为硬绑定。
由于硬绑定是一种非常常用的模式,所以在ES5中提供了内置的Function.prototype.bind,它的用法如下:
function foo (something) {
console.log(this.a, something);
return this.a + something;
}
var obj = {
a: 2
};
var bar = foo.bind(obj);
var b = bar(3); // 2 3
console.log(b); // 5
call/apply/bind的特性让JavaScript变得十分灵活,它们的应用场景十分广泛,例如,将类数组转化为数据、实现继承、实现函数柯里化等。
4. new绑定
JavaScript有一个new操作符,使用方法看起来和那些面向类的语言一样,然而,JavaScript中new的机制实际上和面向类的语言完全不同。
使用new来调用函数,或者说发生构造函数调用时,会自动执行下面的操作。
1.创建(或者说构造)一个全新的对象。
2.这个新对象会被执行[[原型]]连接。
3.这个新对象会绑定到函数调用的this。
4.如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个信息对象。
function foo (a) {
this.a = a;
}
var bar = new foo(2);
console.log(bar.a); // 2
使用new来调用foo()时,我们会构造一个新对象并把它绑定到foo()调用中的this上。
5. 优先级
new绑定>显式绑定/硬绑定>隐式绑定
以上是我对JavaScript核心技术开发解密第七章的读书笔记,码字不易,请尊重作者版权,转载注明出处。
By BeLLESS 2018.7.15 21:17