在《你不知道的JavaScript》裡面關於this繫結機制的部分講的特別好,很清晰,這部分對我們js的使用也是相當關鍵的,並且這也是一個面試的高頻考點,所以整理一篇文章分享一下這部分的內容,相信看本文的解析,你一定會有所收穫的 為什麼要用this:
function identify() {
console.log("Hello,I'm " + this.name);
}
let me = {
name: "Kyle"
};
let you = {
name: "Reader"
};
identify.call(me); // Hello,I'm Kyle
identify.call(you); // Hello,I'm Reader
複製程式碼
這個簡單的栗子,可以在不同的物件中複用函式identify,不用針對每個物件編寫一個新函式。
this解決的問題:
this提供了一種更優雅的方法來隱式'傳遞'一個物件的引用,因此可以將API設計得更加簡潔並且易於複用。
this的四種繫結規則:
預設繫結:
規則:在非嚴格模式下,預設繫結的this指向全域性物件,嚴格模式下this指向undefined
function foo() {
console.log(this.a); // this指向全域性物件
}
var a = 2;
foo(); // 2
function foo2() {
"use strict"; // 嚴格模式this繫結到undefined
console.log(this.a);
}
foo2(); // TypeError:a undefined
複製程式碼
預設繫結規則如上述栗子,書中還提到了一個微妙的細節:
function foo() {
console.log(this.a); // foo函式不是嚴格模式 預設繫結全域性物件
}
var a = 2;
function foo2(){
"use strict";
foo(); // 嚴格模式下呼叫其他函式,不影響預設繫結
}
foo2(); // 2
複製程式碼
所以:對於預設繫結來說,決定this繫結物件的是函式體是否處於嚴格模式,嚴格指向undefined,非嚴格指向全域性物件。
通常不會在程式碼中混用嚴格模式和非嚴格模式,所以這種情況很罕見,知道一下就可以了,避免某些變態的面試題挖坑。
隱式繫結:
規則:函式在呼叫位置,是否有上下文物件,如果有,那麼this就會隱式繫結到這個物件上。
function foo() {
console.log(this.a);
}
var a = "Oops, global";
let obj2 = {
a: 2,
foo: foo
};
let obj1 = {
a: 22,
obj2: obj2
};
obj2.foo(); // 2 this指向呼叫函式的物件
obj1.obj2.foo(); // 2 this指向最後一層呼叫函式的物件
// 隱式繫結丟失
let bar = obj2.foo; // bar只是一個函式別名 是obj2.foo的一個引用
bar(); // "Oops, global" - 指向全域性
複製程式碼
隱式繫結丟失:
隱式繫結丟失的問題:實際上就是函式呼叫時,並沒有上下文物件,只是對函式的引用,所以會導致隱式繫結丟失。
同樣的問題,還發生在傳入回撥函式中,這種情況更加常見,並且隱蔽,類似:
test(obj2.foo); // 傳入函式的引用,呼叫時也是沒有上下文物件。
複製程式碼
顯式繫結: 就像我們上面看到的,如果單純使用隱式繫結肯定沒有辦法得到期望的繫結,幸好我們還可以在某個物件上強制呼叫物件,從而將this繫結在這個函式上。
規則:我們可以通過apply、call、bind將函式中的this繫結到指定物件上。
function foo() {
console.log(this.a);
}
let obj = {
a: 2
};
foo.call(obj); // 2
複製程式碼
傳入的不是物件:
如果你傳入了一個原始值(字串,布林型別,數字型別),來當做this的繫結物件,這個原始值轉換成它的物件形式。
如果你把null或者undefined作為this的繫結物件傳入call/apply/bind,這些值會在呼叫時被忽略,實際應用的是預設繫結規則。
new繫結:
書中提到:在js中,實際上並不存在所謂的'建構函式',只有對於函式的'構造呼叫'。 new的時候會做哪些事情:
建立一個全新的物件。
這個新物件會被執行 [[Prototype]] 連線。 這個新物件會繫結到函式呼叫的this。 如果函式沒有返回其他物件,那麼new表示式中的函式呼叫會自動返回這個新物件。 規則:使用構造呼叫的時候,this會自動繫結在new期間建立的物件上。
function foo(a) {
this.a = a; // this繫結到bar上
}
let bar = new foo(2);
console.log(bar.a); // 2
複製程式碼
this四種繫結規則的優先順序 如果在某個呼叫位置應用了多條規則,如何確定哪條規則生效?
obj.foo.call(obj2); // this指向obj2 顯式繫結比隱式繫結優先順序高。
new obj.foo(); // this指向new新建立的物件 new繫結比隱式繫結優先順序高。
複製程式碼
顯式繫結和new繫結無法直接比較((會報錯),預設繫結是不應用其他規則之後的兜底繫結所以優先順序最低,最後的結果是:
顯式繫結 > 隱式繫結 > 預設繫結
**new繫結 > 隱式繫結 > 預設繫結 ** 箭頭函式的this指向不會使用上述的四條規則:
function foo() {
return () => {
console.log(this.a);
};
}
let obj1 = {
a: 2
};
let obj2 = {
a: 22
};
let bar = foo.call(obj1); // foo this指向obj1
bar.call(obj2); // 輸出2 這裡執行箭頭函式 並試圖繫結this指向到obj2
複製程式碼
從上述栗子可以得出,箭頭函式的this規則:
箭頭函式中的this繼承於它外面第一個不是箭頭函式的函式的this指向。 **箭頭函式的 this 一旦繫結了上下文,就不會被任何程式碼改變。 **
認真看完的話,相信你已經get到this的用法了,《你不知道的JavaScript》,這本書真的很好,寫的也很有趣,沒看過的小夥伴抓緊入手了。
這裡推薦一下我的前端學習交流群:784783012,裡面都是學習前端的,如果你想製作酷炫的網頁,想學習程式設計。自己整理了一份2018最全面前端學習資料,從最基礎的HTML+CSS+JS【炫酷特效,遊戲,外掛封裝,設計模式】到移動端HTML5的專案實戰的學習資料都有整理,送給每一位前端小夥伴,有想學習web前端的,或是轉行,或是大學生,還有工作中想提升自己能力的,正在學習的小夥伴歡迎加入學習。