JavaScript 中的 this 一直是比較讓人頭疼,也是面試特別容易問及的問題。下面就參照這《你不知道的 JavaScript》來學(xué)習(xí)下 this 這個(gè)神奇的東西。
this 到底指向何處
this 是在運(yùn)行時(shí)進(jìn)行綁定的,并不是在編寫時(shí)綁定,它的上下文取決于函數(shù)調(diào)用時(shí)的各種條件。 this 的綁定和函數(shù)聲明的位置沒有任何關(guān)系,只取決于函數(shù)的調(diào)用方式。
所以,this 并不只是簡單地指向函數(shù)或者對(duì)象自身。
this 的四種綁定方式
默認(rèn)綁定
所謂的默認(rèn)綁定就是 this 的默認(rèn)綁定方式。
function foo() {
console.log(this.a)
}
var a = 3;
foo(); // 3
注意:嚴(yán)格模式下這種默認(rèn)綁定形式不成立。
var a = 3;
function foo() {
"use strict";
console.log(this.a)
}
foo(); // TypeError
隱式綁定
隱式綁定是指 this 所在函數(shù)在有上下文的前提下的綁定,如 obj.foo();
。
function foo() {
console.log(this.a);
}
var a = 1;
var obj = {
a: 2,
foo: foo
};
obj.foo(); // 2
注意:對(duì)象中的函數(shù)只是引用關(guān)系,即對(duì)象和函數(shù)存在于兩個(gè)地方。所以在別的地方使用函數(shù),與隱式綁定的對(duì)象就沒有關(guān)系了。看下兩個(gè)例子:
- 其中
var myFoo = obj.foo
的 myFoo 變量引用的是 foo() 函數(shù),與 obj 并無關(guān)系。所以 myFoo 的執(zhí)行函數(shù)行為就變成了默認(rèn)綁定,打印結(jié)果為 1。
function foo() {
console.log(this.a);
}
var a = 1;
var obj = {
a: 2,
foo: foo
};
var myFoo = obj.foo
myFoo(); // 默認(rèn)綁定,值為 1
- 在回調(diào)函數(shù)中其實(shí)也會(huì)出現(xiàn) this 綁定丟失的情況,回調(diào)函數(shù) obj.foo 引用的是 foo 函數(shù),與 obj 對(duì)象并無關(guān)系。
function foo() {
console.log(this.a);
}
var a = 1;
var obj = {
a: 2,
foo: foo
};
setTimeout(obj.foo, 300); // 1
顯示綁定
顯式綁定就是指使用 call、apply、bind 來指定某個(gè)上下文進(jìn)行綁定,它們的一個(gè)作用就只為函數(shù)硬綁定一個(gè)上下文對(duì)象。
之前的回調(diào)函數(shù)使用 bind 進(jìn)行修改后打印出了我們 obj 對(duì)象中的 a 屬性:
function foo() {
console.log(this.a);
}
var a = 1;
var obj = {
a: 2,
foo: foo
};
setTimeout(obj.foo.bind(obj), 300);
call 和 apply 也是類似的,通過對(duì)函數(shù)指定上下文來進(jìn)行硬綁定,且硬綁定只能綁定一次。
call()
方法的作用和apply()
方法類似,區(qū)別就是call()
方法接受的是參數(shù)列表,而apply()
方法接受的是一個(gè)參數(shù)數(shù)組。
new綁定
new 關(guān)鍵字創(chuàng)建對(duì)象的過程其實(shí)也是一個(gè)綁定上下文的過程,所以使用 new 創(chuàng)建的對(duì)象的 this 也要格外注意。
使用 new 來調(diào)用函數(shù),或者說發(fā)生構(gòu)造函數(shù)調(diào)用時(shí),會(huì)自動(dòng)執(zhí)行下面的操作。
- 創(chuàng)建(或者說構(gòu)造)一個(gè)全新的對(duì)象。
- 這個(gè)新對(duì)象會(huì)被執(zhí)行 [[ 原型 ]] 連接。
- 這個(gè)新對(duì)象會(huì)綁定到函數(shù)調(diào)用的 this 。
- 如果函數(shù)沒有返回其他對(duì)象,那么 new 表達(dá)式中的函數(shù)調(diào)用會(huì)自動(dòng)返回這個(gè)新對(duì)象。
function foo(a) {
this.a = a;
}
var bar = new foo(2);
console.log( bar.a ); // 2
可以看到 new 行為的第三步就是進(jìn)行 this 綁定,我們也可以從代碼看到 new 行為的確有綁定 this 的能力。
this 四種綁定方式排序
既然四種綁定都能夠改變 this 的指向,那么這四種綁定的優(yōu)先級(jí)是怎樣的呢?結(jié)論是:
new 綁定 > 顯示綁定 > 隱式綁定 > 默認(rèn)綁定
雖然很少會(huì)出現(xiàn)多個(gè)場景綁定一個(gè) this 的情況,但是知道下也能以防萬一。
箭頭函數(shù)
關(guān)于 this 最后要說的就是 ES6 的箭頭函數(shù)。
function foo() {
setTimeout(() => {
// 這里的 this 在此法上繼承自 foo()
console.log(this.a);
}, 100);
}
var obj = {
a: 2
};
foo.call(obj); // 2
它完全等同于:
function foo() {
var self = this; // lexical capture of this
setTimeout(function () {
console.log(self.a);
}, 100);
}
var obj = {
a: 2
};
foo.call(obj); // 2
關(guān)于箭頭函數(shù)只要記住 var self = this;
就夠了。
它其實(shí)是通過詞法作用域保存當(dāng)前 this 上下文傳遞給回調(diào)函數(shù)。本質(zhì)上是拋棄了 this 原有的機(jī)制。
最后
我們從四種常見 this 綁定方式和箭頭函數(shù)這兩個(gè)角度系統(tǒng)的學(xué)習(xí)了 this 綁定的知識(shí)點(diǎn),相信之后你再也不怕 this 相關(guān)的知識(shí)點(diǎn)了!
本文還有很多可以改進(jìn)的地方,如有任何意見和問題,歡迎留言指出。謝謝~
推薦資料
- 你不知道的 JavaScript (上冊(cè))
- Know this, use this! (總結(jié) this 的常見用法)