“This” is For JavaScript
What is this?
先提前說明一個觀點,實際上,this既不指向函式自身,也不止像函式的詞法作用域 ,此外還有一個重點:this跟函式在哪裡定義無關,決定this的是函式在哪裡呼叫,它是一種在函式呼叫時發生的繫結。
this的繫結規則
this的四種繫結規則分別為:預設繫結、隱式繫結、顯式繫結、new繫結。
他們中的優先順序為 new繫結 >
顯式繫結 >
陰式繫結 >
預設繫結
預設繫結
即當沒有任何其他繫結規則時預設會生效的規則。最常使用的是當函式被單獨定義以及呼叫時,它預設繫結全域性變數
function fun() {
console.log( this.x );
}var x = '我是一個全域性變數';
fun();
//‘我是一個全域性變數’ this此時指向window複製程式碼
但我們需要注意,當我們在嚴格模式下時,全域性物件是無法用預設繫結的,會丟擲錯誤
function fun() { "use strict";
console.log( this.x );
}var x = '我是嚴格模式下的全域性變數';
fun();
//Uncaught TypeError:Cannot read property 'x' of underfined複製程式碼
隱式繫結
如果函式的呼叫是在某個物件上觸發,那麼這個呼叫位置存在一個上下文物件來繫結this。
function fun() {
console.log( this.a );
}var a = '我是一個全域性變數';
var obj = {
a: '我是obj物件的a屬性', fun: fun
};
obj.fun();
//'我是obj物件的a屬性'複製程式碼
這裡obj物件內的fun函式被當做引用屬性,當呼叫它時實際上的流程為:
通過obj物件會去到fun 屬性 –>
根據引用關係找到 fun 函式 –>
呼叫fun函式
所以在此時呼叫fun函式是,this 被隱式繫結到了obj上下文上,此時的 this.a 也就被解析為了 obj.a。
多層呼叫
有時我們呼叫一個函式時會使用多層呼叫的情況,此時我們通過分析一個實際的例子來演示
function fun() {
console.log( this.a );
}var a = '我是全域性變數!';
var obj1 = {
a: '我是obj1的屬性a', fun: fun
};
var obj2 = {
a: '我是obj2的屬性a', obj1:obj1
}obj2.obj1.fun();
// '我是obj1的屬性a'複製程式碼
此時這個函式的呼叫過程為:
訪問obj2.obj1 –>
通過引用獲取 obj1物件 –>
訪問obj1.fun –>
引用獲取fun函式 –>
呼叫fun函式
此時隱式繫結實際上只是獲取最後一層呼叫的上下文物件,把this繫結上去。
隱式丟失
函式別名
我們可能會在日常開發中有這樣的場景
function fun() {
console.log( this.a );
}var a = '我是全域性變數';
var obj = {
a: '我是obj物件的a屬性', fun: fun
};
//此時再賦予這個函式一個別名var bar = obj.fun;
bar();
//'我是全域性變數'複製程式碼
結果並不是’我是obj物件的a屬性’,這是因為obj.fun 本質上是引用屬性,所以下面兩行本質上是沒有區別的
var bar = obj.fun;
var bar = fun
所以呼叫時本質上是 bar 找到了 fun函式本身,進行呼叫時是在全域性環境下呼叫的,所以不會輸出obj內定義的屬性,所以此時就是預設繫結
回撥函式
當我們使用回撥函式時也會存在隱式繫結丟失的情況
function fun() {
console.log( this.a );
}var a = '我是全域性變數';
var obj = {
a: '我是obj的屬性a', fun: fun
};
setTimeout( obj.fun, 1000);
//我是全域性變數複製程式碼
發證這個結果的原因也是同樣的道理,傳入 setTimeout的obj.fun 是引用屬性, 所以呼叫順序為:
setTimeout –>
獲取obj.fun屬性 –>
通過引用屬性的到fun函式 –>
呼叫執行fun函式。
所以在呼叫執行的時候並沒有在obj的上下文中,所以仍為預設繫結。
顯式繫結
相較於隱式繫結而言,我們有時需要手動來為函式呼叫指明函式執行的上下文,此時據需要使用顯式繫結
顯式繫結主要是使用三個方法 call,apply以及bind方法來實現,這部分內容我在另一篇文章中有詳細解析
new 繫結
當我們使用new 來修飾並呼叫函式時,他會先檢視這個函式是否有其他的返回物件,如果沒有就自動返回自己作為一個新物件。
function fun(a) {
this.a = a;
console.log( this.a );
}var a = '我是全域性變數';
var fun1 = new fun('我是fun1的引數');
var fun2 = new fun('我是fun2的引數');
console.log(fun1.a,fun2.a);
// '我是fun1的引數','我是fun2的引數'複製程式碼
因為當我們使用new時他會生成一個全新的物件來繫結this。
箭頭函式
箭頭函式作為ES6提出的新概念,它是一種對之前this複雜操作的一種極好的解決方案,他的this繫結取決於外層(函式或者全域性)作用域。
注意,是只取決於外層作用域,也就是我們對箭頭函式使用顯示繫結來強制修改上下文繫結也是無效的。
參考
《你不知道的JavaScript(上卷)》——Kyle Simpson 著
《ES6標準入門(第三版)》 —— 阮一峰 著