iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 14
2
自我挑戰組

JavaScript 試煉之旅系列 第 14

JavaScript 的 this 怎能不知道

  • 分享至 

  • xImage
  •  

思來想去,還是決定先將我理解的 this 用兩句話做個結論,再由此延伸後續的詳細差別。

  1. 在 ES6 之前,決定this的方式取決於函式如何被呼叫,除非直接指定 this (例如: bindcallapply 方法)。
  2. 在 ES6 之後,箭頭函式對於this的綁定則有了重新的定義。

ES6之前的 this

如前面所提,決定this的方式取決於函式如何被呼叫

所以再來要對於函式呼叫時 this 的綁定依序了解。

直接使用 this

來看看這個測試例子:

console.log(this);

如果我們直接調用 this 並查看,會發現 this 指向全域

Day14-1

所以當全域中有變數時:

var name = "Bill";
console.log(this.name);

因為 this 指向全域, 所以才可以取得同樣位於全域的 name 變數的值 Bill

Day14-2

直接呼叫函式

當我們 直接呼叫函式 的時候,此時的 this 指向全域

來看看測試例子驗證一下:

var name = "Bill";
function getName(){
  console.log(this.name);
}
getName();

Day14-3

因此可以得到全域的 name 的值 Bill

嚴格模式下的 this

MDN: 嚴格模式下,如果 this 沒有定義到執行環境( Execution Context)內,其預設值就會是 undefined

寫些測試例子驗證一下:

'use strict'
function test(){
  console.log(this);
}
test();

前面有提到,當我們直接呼叫函式的時候, this 會是指向全域而非 test 函式本身,所以在嚴格模式底下會因為 this 不是定義在執行環境(test 函式)中,所以會是 undefined

Day14-4

物件呼叫方法

如果是 呼叫物件的方法,此時的 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

Day14-5

以下注意!!!!

讓我們改寫一下測試的例子:

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();

這時候依序會獲得什麼值呢?

解析的流程如下:

  1. 呼叫 obj 物件的 getHabbit 方法,此時的 this 指向 obj 物件,所以可以看到 getHabbit 方法的值為 Read comics
  2. 執行 getHabbit 的程式內容時,會執行 getAnotherHabbit 這個函式,由於在 getHabbit 方法是直接呼叫 getAnotherHabbit ,所以此時的 this 已變成指向全域(就是Window物件)。
  3. 因為 getAnotherHabbit 函式的 this 指向全域,所以就取得位於全域的變數值 Read books

Day14-6

thatself 怎麼用?

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 這個物件的相關資訊。

Day14-7

在事件(event)中的 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 標籤的所有元素。

Day14-8

立即函式(Immediately Invoked Function Expression, IIFE)的 this

關於立即函式(IIFE),在MDN中這麼解釋:

MDN: IIFE (Immediately Invoked Function Expression) 是一個定義完馬上就執行的 JavaScript function。

立即函式的 this會指向全域

來個測試例子:

(function(){
  console.log(this);
})();

Day14-9

使用 callapplybind 指定 this 的值

當我們如果需要一個特定的 this 值的時候,這時也許就會用到 callapplybind 強迫綁定 this

關於 callapplybind 就讓我們依序往下看吧

Function.prototype.call

關於 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 物件中的值。

Day14-10

來看看有額外設定其他參數時的情況:

function add(a,b){
  console.log(this);
  console.log('總和為: ' + (a+b));
}
add.call(null,2,3);

Day14-11

從結果可以看到, this 會是指向全域,而數值2及數值3被傳入 add 函式中,所以可以獲得加總後的值為 5。

Function.prototype.apply()

關於 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() 執行結果沒有差別

Day14-12

所以我們再往下看看有額外的參數需要被輸入時的情形:

function add(numberAry){
  console.log(numberAry);
  const sumTotal = numberAry.reduce((acc,number)=> acc + number,0)
  console.log('總和為: ' + sumTotal);
}
add.call(null,[2,3]);

因為接受陣列型別的參數,所以可以透過陣列的方法操作元素中的值,獲得總和值為 5

Day14-13

Function.prototype.bind()

最後一個是 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() 一樣的結果。

Day14-14

建構式的 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');

Day14-15

ES6之後的 this

ES6之後對於 this 有了額外的定義:

箭頭函式(arrow function) 的this

箭頭函式並不擁有自己的 this 變數;使用的 this 值來自封閉的文本上下文,也就是說,箭頭函式遵循常規變量查找規則。因此,如果在當前範圍中搜索不到 this 變量時,他們最終會尋找其封閉範圍。
箭頭函式的this

簡單來說,大致可以歸納出幾個重點:

  1. 箭頭函式(arrow function) 沒有自己的 this
  2. 箭頭函式(arrow function)的 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 的學習就到這裡囉~

明天見~


上一篇
ES6 箭頭函式(arrow function)
下一篇
JavaScript 閉包(Closure)
系列文
JavaScript 試煉之旅30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言