為什么要學(xué)習(xí) this?
- 閱讀源碼需要;很多
js庫里面都有關(guān)于this以及this綁定的用法,所以我們需要深入學(xué)習(xí)了解this幫助我們高效閱讀源碼。 - 提高編程能力;
this提供了一種更優(yōu)雅的方式來隱式“傳遞”一個(gè)對(duì)象的引用,我們可以使用this編寫出更加高級(jí)優(yōu)雅的代碼。
關(guān)于 this 的誤解?
指向自身
從字面意思上理解確實(shí)是說得通的。在我剛剛接觸javascriptthis的時(shí)候,我也以為它是指向于自身,這個(gè)主要是受到以前python編程經(jīng)驗(yàn)的影響,覺得this指向?qū)嵗旧?。事?shí)上,javascript通過new實(shí)例化操作后的實(shí)例也確實(shí)指向于實(shí)例本身,不過javascript里面的this的情況更為多樣和多變。指向作用域
this指向作用域,有些情況下是正確的,但是在其他情況下卻是錯(cuò)誤的。
function foo() {
const a = 2;
this.bar();
}
function bar() {
console.log(this.a);
}
foo();
打印出來的結(jié)果是 undefined,這個(gè)我們想象的不一樣;我們以為應(yīng)該是 2。所以 this 并不是指向作用域的。
this 到底是什么?
this 是在運(yùn)行時(shí)進(jìn)行綁定的,并不是編寫時(shí)綁定,它的上下文取決于函數(shù)調(diào)用時(shí)的各種條件。this 的綁定和函數(shù)聲明的位置沒有任何關(guān)系,只取決于函數(shù)的調(diào)用方式(調(diào)用位置)。
當(dāng)一個(gè)函數(shù)被調(diào)用時(shí),會(huì)創(chuàng)建一個(gè)活動(dòng)記錄(也稱為執(zhí)行上下文)。這個(gè)記錄包含函數(shù)在哪里被調(diào)用(調(diào)用棧),函數(shù)調(diào)用方法以及傳入?yún)?shù)等信息。this 就是其中的一個(gè)屬性,會(huì)在函數(shù)執(zhí)行的過程中用到。
調(diào)用位置
在正式開始 this 學(xué)習(xí)之前,我們先通過下面這個(gè)例子學(xué)習(xí)下調(diào)用位置。
function baz() {
// 當(dāng)前調(diào)用棧是:baz,因此,當(dāng)前調(diào)用位置是全局作用域
console.log("baz");
bar();
}
function bar() {
// 當(dāng)前調(diào)用棧是 baz -> bar,因此,當(dāng)前調(diào)用位置在 baz 中
console.log("bar");
foo();
}
function foo() {
// 當(dāng)前調(diào)用棧是 baz -> bar -> foo,因此,當(dāng)前調(diào)用位置在 bar 中
console.log("foo");
}
baz();
this 的綁定規(guī)則
- 默認(rèn)綁定(獨(dú)立函數(shù)調(diào)用)
可以把這條規(guī)則看做是無法應(yīng)用其他規(guī)則的默認(rèn)規(guī)則。
如上面的調(diào)用位置的例子,如果我們?cè)诿總€(gè)函數(shù)里面打印this,我們會(huì)發(fā)現(xiàn)this都執(zhí)行Window對(duì)象。(嚴(yán)格模式下this為undefined)
function baz() {
console.log(this, "baz");
bar();
}
function bar() {
console.log(this, "bar");
foo();
}
function foo() {
console.log(this, "foo");
}
baz();
- 隱式調(diào)用(調(diào)用位置是否有上下文對(duì)象,或者說是否被某個(gè)對(duì)象擁有或包圍,不過這種說法可能會(huì)造成一些誤導(dǎo))
function foo() {
console.log(this.a);
}
const obj = {
a: 2,
foo: foo
};
obj.foo(); // 2
對(duì)象屬性引用鏈只有最頂層或者最后一層會(huì)影響調(diào)用位置。
function foo() {
console.log(this.a);
}
const obj2 = {
a: 42,
foo: foo
};
const obj1 = {
a: 2,
obj2: obj2
};
obj1.obj2.foo(); // 42
隱式丟失
function foo() {
console.log(this.a);
}
const obj = {
a: 2,
foo: foo
};
const bar = obj.foo;
const a = 'test';
bar(); // "test";
雖然 bar 是 obj.foo 的一個(gè)引用,但實(shí)際上,它引用的是 foo 函數(shù)本身,因此此時(shí) bar() 其實(shí)是一個(gè)不帶任何修飾的函數(shù)調(diào)用,因此應(yīng)用了默認(rèn)綁定。
function foo() {
console.log(this.a);
}
function doFoo(fn) {
// 這里存在一個(gè)隱性賦值,fn = obj.foo
fn();
}
const obj = {
a: 2,
foo: foo
};
const a = "test";
doFoo(obj.foo); // "test"
參數(shù)傳遞其實(shí)就是一種隱式賦值,這是我們需要注意的,這樣的問題開發(fā)中如果不太清楚,很容易迷惑。
- 顯示綁定
直接指定this的綁定對(duì)象,有三種方法:bind,call,apply
function foo() {
console.log(this.a);
}
const obj = {
a: 2
};
foo.call(obj); // 2
通過 foo.call(...),我們可以在調(diào)用 foo 時(shí)強(qiáng)制把 this 綁定到 obj 上。
但是顯示綁定也不能解決綁定丟失的問題,通過借助 bind 方法我們可以實(shí)現(xiàn)硬綁定。
- new 綁定
function foo(a) {
this.a = a;
}
const bar = new foo(2);
console.log(bar.a); // 2
使用 new 來調(diào)用 foo(...) 時(shí),我們會(huì)構(gòu)造一個(gè)新對(duì)象并把它綁定到 foo(...) 調(diào)用的 this 上。
綁定優(yōu)先級(jí)
new 綁定 > 顯示綁定 > 隱式綁定 > 默認(rèn)綁定
例外:關(guān)于箭頭函數(shù)的 this
箭頭函數(shù)是一種無法使用我們上面四種規(guī)則的特殊函數(shù)類型。
function foo() {
return (a) => {
console.log(this.a);
}
}
const obj1 = {
a: 2
};
const obj2 = {
a: 3
};
const bar = foo.call(obj1);
bar.call(obj2); // 2,不是 3
foo() 內(nèi)部創(chuàng)建的箭頭函數(shù)會(huì)捕獲調(diào)用時(shí) foo() 的 this。由于 foo() 的 this 綁定到了 obj1,bar 的 this 也會(huì)綁定到 obj1,箭頭函數(shù)的綁定無法被修改。(new 也不行)。
箭頭函數(shù)常用于回調(diào)函數(shù)中,例如事件處理或定時(shí)器:
function foo() {
setTimeout(() => { console.log(this.a) }, 100);
}
const obj = { a: 2 };
foo.call(obj); // 2
總結(jié):
- 由 new 調(diào)用?綁定到新創(chuàng)建的對(duì)象。
- 由 call 或者 apply(或者 bind)調(diào)用?綁定到指定的對(duì)象。
- 由上下文對(duì)象調(diào)用?綁定到那個(gè)上下文對(duì)象。
- 默認(rèn):在嚴(yán)格模式下綁定到 undefined,否則綁定到全局對(duì)象。
參考:《你不知道的 Javascript(上卷)》