思來想去,還是決定先將我理解的 this
用兩句話做個結論,再由此延伸後續的詳細差別。
this
(例如: bind
、 call
、 apply
方法)。this
如前面所提,決定this的方式取決於函式如何被呼叫。
所以再來要對於函式呼叫時 this
的綁定依序了解。
this
來看看這個測試例子:
console.log(this);
如果我們直接調用 this
並查看,會發現 this
指向全域
所以當全域中有變數時:
var name = "Bill";
console.log(this.name);
因為 this
指向全域, 所以才可以取得同樣位於全域的 name
變數的值 Bill
。
當我們 直接呼叫函式 的時候,此時的 this
指向全域
來看看測試例子驗證一下:
var name = "Bill";
function getName(){
console.log(this.name);
}
getName();
因此可以得到全域的 name
的值 Bill
this
MDN: 嚴格模式下,如果
this
沒有定義到執行環境( Execution Context)內,其預設值就會是undefined
。
寫些測試例子驗證一下:
'use strict'
function test(){
console.log(this);
}
test();
前面有提到,當我們直接呼叫函式的時候, this
會是指向全域而非 test
函式本身,所以在嚴格模式底下會因為 this
不是定義在執行環境(test
函式)中,所以會是 undefined
。
如果是 呼叫物件的方法,此時的 this
指向該物件
var habbit = "Read books";
var obj = {
habbit: "Read comics",
getHabbit : function() {
return this.habbit;
}
}
console.log(obj.getHabbit());
此時的 this
指向 obj
物件。 所以 getHabbit
方法中的 this.habbit
會取得 obj
物件的 habbit
的值 Read comics
,而不是全域 habbit
變數的值 Read books
。
以下注意!!!!
讓我們改寫一下測試的例子:
var habbit = "Read books";
var obj = {
habbit: "Read comics",
getHabbit : function() {
console.log('getHabbit方法的habbit值: ' + this.habbit);
function getAnotherHabbit(){
console.log('getAnotherHabbit方法的habbit值: ' + this.habbit);
}
getAnotherHabbit();
}
}
obj.getHabbit();
這時候依序會獲得什麼值呢?
解析的流程如下:
obj
物件的 getHabbit
方法,此時的 this
指向 obj
物件,所以可以看到 getHabbit
方法的值為 Read comics
getHabbit
的程式內容時,會執行 getAnotherHabbit
這個函式,由於在 getHabbit
方法是直接呼叫 getAnotherHabbit
,所以此時的 this
已變成指向全域(就是Window物件)。getAnotherHabbit
函式的 this
指向全域,所以就取得位於全域的變數值 Read books
。that
、 self
怎麼用?this
很常在執行過程中因為呼叫函式的方式改變,而 this
也跟著改變。
所以必須透過一個變數用來承接原本 this
的值 ,而常見的寫法就是that
或者 self
。
將前一個測試例子改寫一下:
var habbit = "Read books";
var obj = {
habbit: "Read comics",
getHabbit : function() {
var that = this;
console.log('getHabbit方法的habbit值: ' + this.habbit);
function getAnotherHabbit(){
console.log('getAnotherHabbit方法的habbit值: ' + that.habbit);
}
return getAnotherHabbit();
}
}
obj.getHabbit();
從圖中可以得知: 因為 that
的值為 this
指向 obj
物件時候的值,所以 that.habbit
自然也就會拿到 obj
物件中的 habbit
的值 Read comics
。
而 that
則取得 obj
這個物件的相關資訊。
this
事件中的 this
會指向那個綁定事件的元素
寫個例子來驗證:
<ul class="orderList">
<li>列表A</li>
<li>列表B</li>
<li>列表C</li>
<li>列表D</li>
<li>列表E</li>
</ul>
var orderList = document.querySelector('.orderList');
orderList.addEventListener('click',getListText);
function getListText(e){
console.log(this);
}
這裡在 <ul></ul>
上 綁定 click
事件,所以當點擊 <ul></ul>
,會得到 this
值為 <ul></ul>
這個 html 標籤的所有元素。
this
關於立即函式(IIFE),在MDN中這麼解釋:
MDN: IIFE (Immediately Invoked Function Expression) 是一個定義完馬上就執行的 JavaScript function。
且立即函式的 this
會指向全域
來個測試例子:
(function(){
console.log(this);
})();
call
、apply
及 bind
指定 this
的值當我們如果需要一個特定的 this
值的時候,這時也許就會用到 call
、apply
及 bind
強迫綁定 this
。
關於 call
、apply
及 bind
就讓我們依序往下看吧
關於 Function.prototype.call
在MDN有這麼一段解釋:
MDN: Function.prototype.call
使用給定的this
參數以及分別給定的參數來呼叫某個函數
fun.call(thisArg[, arg1[, arg2[, ...]]])
簡單來說, 自己定義 this
的值並傳給目標函式當作該函式的 this
值
而MDN定義中提到的其他分別給定的參數則是 如果有設定除了 this
以外的參數,會將那些參數一併傳入目標函式中,如果不需要則不用設定。
還是很文謅謅,所以趕緊來寫個測試例子驗證看看:
var obj = {
name: "Bill",
habbit: "Read Books"
}
function introduce(){
console.log(this);
console.log(this.name + '\'s habbit is ' + this.habbit);
}
introduce.call(obj);
前面有提到,當 直接呼叫函式的時候, this
指向全域
但是當我們透過 call()
方法 將 obj
物件當作 this
傳入 introduce
函式的時候,此時已經綁定 this
,所以 introduce
函式才得以使用 obj
物件中的值。
來看看有額外設定其他參數時的情況:
function add(a,b){
console.log(this);
console.log('總和為: ' + (a+b));
}
add.call(null,2,3);
從結果可以看到, this
會是指向全域,而數值2及數值3被傳入 add
函式中,所以可以獲得加總後的值為 5。
關於 Function.prototype.apply
在MDN有這麼一段解釋:
MDN: Function.prototype.apply
apply() 方法會呼叫一個以this
的代表值和一個陣列形式的值組(或是一個 array-like object )為參數的函式。
fun.apply(thisArg, [argsArray])
和 call()
差別在於 call()
接受一連串的參數傳入,而 apply()
只接受陣列型式的參數
var obj = {
name: "Bill",
habbit: "Read Books"
}
function introduce(){
console.log(this);
console.log(this.name + '\'s habbit is ' + this.habbit);
}
introduce.apply(obj);
可以這邊看起來和 call()
執行結果沒有差別
所以我們再往下看看有額外的參數需要被輸入時的情形:
function add(numberAry){
console.log(numberAry);
const sumTotal = numberAry.reduce((acc,number)=> acc + number,0)
console.log('總和為: ' + sumTotal);
}
add.call(null,[2,3]);
因為接受陣列型別的參數,所以可以透過陣列的方法操作元素中的值,獲得總和值為 5
。
最後一個是 bind()
方法, 在MDN有這麼一段解釋:
MDN: Function.prototype.bind()
bind() 方法,會建立一個新函式。該函式被呼叫時,會將 this 關鍵字設為給定的參數,並在呼叫時,帶有提供之前,給定順序的參數。
fun.bind(thisArg[, arg1[, arg2[, ...]]])
和前兩者差別為 使用 bind()
會回傳一個綁定自定義 this
值的函式提供我們呼叫。
var obj = {
name: "Bill",
habbit: "Read Books"
}
function introduce(){
console.log(this);
console.log(this.name + '\'s habbit is ' + this.habbit);
}
const newIntroduce = introduce.bind(obj);
console.log(newIntroduce);
newIntroduce();
從圖中可以看到透過變數 newIntroduce
儲存了 已經綁定 this
後的 introduce
函式,而這裡的 this
指向的是 obj
物件。
所以當我們呼叫 newIntroduce
函式時,就可以取得與 call()
、 apply()
一樣的結果。
this
建構式的 this
指向使用 new
關鍵字所建立的物件
function Person(name,age,habbit){
this.name = name;
this.age = age;
this.habbit = habbit;
console.log(this);
}
const person1 = new Person('Bill',22,'Read');
this
ES6之後對於 this
有了額外的定義:
this
箭頭函式並不擁有自己的 this 變數;使用的 this 值來自封閉的文本上下文,也就是說,箭頭函式遵循常規變量查找規則。因此,如果在當前範圍中搜索不到 this 變量時,他們最終會尋找其封閉範圍。
箭頭函式的this
簡單來說,大致可以歸納出幾個重點:
this
this
綁定會透過 範圍鏈(scope chain) 的觀念找到其作用域的this指向,並當作自己的this。在往下講之前,需要先稍微了解一下什麼是範圍鏈(scope chain)。
範圍鏈(scope chain) 的概念會於閉包(closure)的篇幅來詳細的理解。
這邊只要知道 當變數在自己的執行環境中如果找不到該變數,就會往外層尋找,直到找到後才停止
來看個測試例子:
const name = "Bill";
function getName(){
console.log(name);
}
getName();
當執行 getName
函式時,因為在函式的執行環境中沒有 name
這個變數,所以會依照範圍鏈(scope chain)的概念往外層尋找,所以會找到位於全域的 name
變數的值 Bill
了解箭頭函式(arrow function)的 this
與範圍鏈(scope chain)的觀念後,再來要看看測試的例子
var name = "Jack";
var obj = {
name: "Bill",
checkThis: () => { console.log(this.name);}
}
obj.checkThis();
箭頭函式的 this
會透過範圍鏈(scope chain)的觀念往外層尋找看看作用域的 this
是指向誰,會發現 this
指向 全域(因為obj
物件位於全域),所以箭頭函式的 this
指向 全域,於是就會得到 name
的值 Jack
讓我們再看看另一個例子
var name = "Jack";
var obj = {
name: "Bill",
checkThis: function(){
test = () => {console.log(this.name);}
test();
}
}
obj.checkThis();
箭頭函式的 this
會透過範圍鏈(scope chain)的觀念往外層尋找看看作用域的 this
是指向誰,會發現 this
指向obj
物件(因為箭頭函式 test
的外層 checkThis
函式指向 obj
物件),於是就會得到 name
的值 Bill
。
關於 this
的學習就到這裡囉~
明天見~