第七章 this
去年校招的時候,關(guān)于this基本每家公司必問。或者是出題考你,或者是讓你簡述改變this原理。關(guān)于this,個人認為本書講的不如《你不知道的JavaScript(上)》講得好,這本書只適合入門,如想深挖,還是建議童鞋們看下上面提到的那本書。
關(guān)于this,無論哪本書,都會提到這樣一點,這也是學好this的前提。
當前函數(shù)的this是在函數(shù)被調(diào)用執(zhí)行的時候才確定的。
記住這一點后,我將結(jié)合《你不知道的JavaScript(上)》,對this進行介紹。
1. 默認綁定
首先要介紹的是最常用的函數(shù)調(diào)用類型:獨立函數(shù)調(diào)用。可以把這條規(guī)則看作是無法應用其他規(guī)則時的默認規(guī)則。
function foo () {
console.log(this.a);
}
var a = 2;
foo(); // 2
當調(diào)用foo()時,this.a被解析成了全局變量a,函數(shù)調(diào)用時應用了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()被調(diào)用時,它的落腳點確實指向obj對象。當函數(shù)引用有上下文對象時,隱式綁定規(guī)則會把函數(shù)調(diào)用中的this綁定到這個上下文對象。因為調(diào)用foo()時this被綁定到obj,因此this.a和obj.a是一樣的。
對象屬性引用鏈中只有最頂層或者說只有最后一層影響調(diào)用位置。
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函數(shù)本身,因此此時的bar其實是一個不帶任何修飾的函數(shù)調(diào)用,因此應用了默認綁定。
一種更微妙、更常見并且更出乎意料的情況發(fā)生在傳入回調(diào)函數(shù)時:
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'
參數(shù)傳遞其實就是一種隱式賦值,因此我們傳入函數(shù)時也會被隱式賦值。
JavaScript環(huán)境中內(nèi)置的setTimeout()函數(shù)實現(xiàn)和下面的偽代碼類似:
function setTimeout (fn, delay) {
// 等待delay毫秒
fn(); // 調(diào)用位置
}
關(guān)于隱形丟失,《JavaScript核心技術(shù)開發(fā)解密》一書給出兩道還不錯的思考題。這兩道題這里不再給出答案,有興趣的同學請思考后,使用控制臺自行驗證。
// 思考題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可以使用函數(shù)的call和apply方法改變this的指向。它們的第一個參數(shù)是一個對象,它們會把這個對象綁定到this,接著在調(diào)用函數(shù)時指定這個this。因為你可以直接指定this的綁定對象,因此我們稱之為顯示綁定。
function foo () {
console.log(this.a);
}
var obj = {
a: 2
};
foo.call(obj); // 2
通過foo.call(),我們可以在調(diào)用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
我們創(chuàng)建了函數(shù)bar(),并在它的內(nèi)部手動調(diào)用了foo.call(obj),因此強制把foo的this綁定到了obj。無論之后如何調(diào)用函數(shù)bar,它總會手動在obj上調(diào)用foo,這種綁定是一種顯示的強制綁定,因此我們稱之為硬綁定。
由于硬綁定是一種非常常用的模式,所以在ES5中提供了內(nèi)置的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變得十分靈活,它們的應用場景十分廣泛,例如,將類數(shù)組轉(zhuǎn)化為數(shù)據(jù)、實現(xiàn)繼承、實現(xiàn)函數(shù)柯里化等。
4. new綁定
JavaScript有一個new操作符,使用方法看起來和那些面向類的語言一樣,然而,JavaScript中new的機制實際上和面向類的語言完全不同。
使用new來調(diào)用函數(shù),或者說發(fā)生構(gòu)造函數(shù)調(diào)用時,會自動執(zhí)行下面的操作。
1.創(chuàng)建(或者說構(gòu)造)一個全新的對象。
2.這個新對象會被執(zhí)行[[原型]]連接。
3.這個新對象會綁定到函數(shù)調(diào)用的this。
4.如果函數(shù)沒有返回其他對象,那么new表達式中的函數(shù)調(diào)用會自動返回這個信息對象。
function foo (a) {
this.a = a;
}
var bar = new foo(2);
console.log(bar.a); // 2
使用new來調(diào)用foo()時,我們會構(gòu)造一個新對象并把它綁定到foo()調(diào)用中的this上。
5. 優(yōu)先級
new綁定>顯式綁定/硬綁定>隱式綁定
以上是我對JavaScript核心技術(shù)開發(fā)解密第七章的讀書筆記,碼字不易,請尊重作者版權(quán),轉(zhuǎn)載注明出處。
By BeLLESS 2018.7.15 21:17