前言
在箭頭函數中,this 指稱的對象在所定義時就固定了,而不會隨著使用時的脈絡而改變
傳統函數下的 this 物件,有一個大原則:看呼叫時的物件是誰。不是看定義的語彙位置,而是根據執行當下誰擁有這段程式碼,也就是看誰呼叫的。
但在 Arrow Functions 卻不是這麼回事,幾乎可以說是完全不同的另一套運作邏輯。
Arrow Functions 的 this 判斷原則
Arrow Functions 的 this 和傳統函數的一個重大差異就是看的是語彙位置
傳統函數每次呼叫函數,都會建立一個新的函數執行環境 (Function Execution Context),然後建立一個新的 this 引用物件,指向當下的呼叫者。
而 Arrow Functions 則不會有自己的 this 引用物件,呼叫 this 時,會沿用語彙環境外圍的 this。
我們用具體的例子來看:
物件函式
傳統函數
傳統函數看的是誰呼叫,所以這個例子相對單純,player.whatsThis() 的呼叫者就是 player:
var player = {
whatsThis: function() { // normal function
return this;
},
};
console.log( player.whatsThis() === player ); // true
Arrow Functions
whatsThis() 使用 Arrow Functions 定義,因此 whatsThis() 本身不會有自己的 this,而是沿用外圍環境的 this。
在這個例子裡,從 whatsThis() 再往外一層不在任何 Function Context 內,換言之就是 Global Context。在全域環境裡的 this 就是 Global 物件,在 HTML 裡就是 window:
var player = {
whatsThis: () => { // arrow function
return this;
},
};
console.log( player.whatsThis() === window ); // true
傳統函數
傳統函數一樣單純看是誰呼叫,player.f() 的呼叫者就是 player:
var whatsThis = function() { // normal function
return this;
};
var player = {};
player.f = whatsThis;
console.log(player.f() === player); // true
Arrow Functions
雖然是透過 player.f() 呼叫,但這邊看的是語彙位置,whatsThis() 再往外一層的 this 是 Global 物件:
var whatsThis = () => { // arrow function
return this;
};
var player = {};
player.f = whatsThis;
console.log(player.f() === window); // true
傳統函數
根據 obj.method() 的公式,呼叫者同樣很好辨認:
var player = {
name: 'Bob',
f: function() {
return this;
},
pet: {
name: 'May',
f: function() {
return this;
},
}
};
console.log(player.f() === player); // true
console.log(player.pet.f() === player.pet ); // true
Arrow Functions
雖然呼叫者不同,函數定義的層次也不太一樣,但由於 player.f() 和 player.pet.f() 語彙位置再往外一層其實都是 Global Context,所以兩個函數回傳的 this 都是 Global 物件:
var player = {
name: 'OneJar',
f: () => {
return this;
},
pet: {
name: 'Totoro',
f: () => {
return this;
},
}
};
console.log(player.f() === window); // true
console.log(player.pet.f() === window ); // true
簡易呼叫 (Simple Call)
傳統函數
前面沒有指定呼叫者的狀況,傳統函數在一般模式下是 Global 物件,嚴謹模式下是 undefined:
var whatsThis = function() {
return this;
}
console.log( whatsThis() ); // (normal mode) window / (strict mode) undefined
Arrow Functions
由於看的是語彙位置,往外一層是 Global Context,不管在一般模式或嚴謹模式,this 都是 Global 物件:
var whatsThis = () => {
return this;
}
console.log( whatsThis() ); // window
傳統函數
var x = 10;
var obj = {
x: 20,
f: function(){
console.log('Output#1: ', this.x);
var foo = function(){ console.log('Output#2: ', this.x); }
foo();
}
};
obj.f();
執行結果:
Output#1: 20
Output#2: 10
「Output#1」時,呼叫方式是 obj.f(),因此 this 是呼叫者 obj 物件,this.x 是 20。
「Output#2」時,呼叫方式是 foo(),視同簡單呼叫,一般模式下 this 是 Global 物件,因此 this.x 是 10。
Arrow Functions I
如果只有內部函數是 Arrow Function,外部函數仍是傳統函數:
var x = 10;
var obj = {
x: 20,
f: function(){
console.log('Output#1: ', this.x);
var foo = () => { console.log('Output#2: ', this.x); } // arrow function
foo();
}
};
obj.f();
執行結果:
Output#1: 20
Output#2: 20
「Output#1」所在的函數仍是傳統函數,因此 this.x 不變仍是 20。
「Output#2」所在的函數變成 Arrow Function,沿用外層的 this,其外層就是 obj.f(),因此 this.x 也是 20。
Arrow Functions II
如果外部函數是 Arrow Function,內部函數是傳統函數:
var x = 10;
var obj = {
x: 20,
f: () => { // arrow function
console.log('Output#1: ', this.x);
var foo = function() { console.log('Output#2: ', this.x); }
foo();
}
};
obj.f();
執行結果:
Output#1: 10
Output#2: 10
「Output#2」根據呼叫方式是 foo(),視同簡單呼叫,一般模式下 this 是 Global 物件,因此 this.x 是 10。
「Output#1」會沿用外層的 this,往外找一層是 Global Context,所以 this 也是 Global 物件。
Arrow Functions III
如果外部函數和內部函數都是 Arrow Function:
var x = 10;
var obj = {
x: 20,
f: () => { // arrow function
console.log('Output#1: ', this.x);
var foo = () => { console.log('Output#2: ', this.x); } // arrow function
foo();
}
};
obj.f();
執行結果:
Output#1: 10
Output#2: 10
「Output#1」會沿用外層的 this,往外找一層是 Global Context,所以 this 也是 Global 物件。
「Output#2」沿用外層的 this,其外層是 obj.f();而 obj.f() 的 this 如上面所說,經過沿用後是 Global 物件。