2017年1月14日

[筆記] JavaScript ES6 中的箭頭函數(arrow function)及對 this 的影響


在這篇文章中要來說明一下在 ES6 中相當常見的箭頭函數(arrow function),讓我們來看一下可以怎麼樣使用。

什麼是箭頭函數(arrow function)


首先,我們來看一下過去我們撰寫函數的方法:



在 ES6 中,我們可以把它改成箭頭函數的寫法,它會變成下面這樣:


沒有參數的時候要記得加上空括號

要特別留意的地方是,在箭頭函數中如果沒有帶入參數時,一樣要加上空括號。



如果只是要回傳某個值,可以省略 return

如果我們的函式本身只是要回傳某個值的話,可以把 return 這個字省略掉:


箭頭函數帶入參數值


兩個以上的參數,需要使用括號

當我們的函式擁有兩個以上的參數時,我們一樣要使用括號來帶入參數,寫法像是下面這樣子:


當函數只有一個參數時,不需要使用括號

從上面的例子我們可以知道,當函數沒有參數或有兩個以上的參數時,我們都要加上括號( ),但是當函數只有一個參數時,可以省略括號不寫,因此,當我們的函數只有一個參數時,我們的函數長得像這樣:



箭頭函數當中的 this 是定義時的對象,而不是使用時的對象


在使用箭頭函數時,有一點要注意的是,在箭頭函數中,this 指稱的對象在所定義時就固定了,而不會隨著使用時的脈絡而改變。

讓我們來看一下這個例子:

在這個範例中,不論我們使用的是原本 function 的寫法或 ES6中的箭頭函式,都會回傳得到最外層的 window 物件(如果不清楚 this 的話可以先參考 [筆記] 談談JavaScript中的"this"和它的bug),這樣看起來似乎兩者沒有太大的差別。



然而,換個例子的情況就不一樣,讓我們來看看下面兩個不同的例子:

例子一(參考自 阮一峰 - ECMAScript 6 入門

我們分別用原本的寫法和箭頭函示的寫法建立了兩個 function:

// 原本的 function
let fn = function(){
  console.log(this.constructor.name);  // Object(data)
  setTimeout(function(){
    console.log(this.constructor.name) // Window
  },100);
}

// 箭頭函式 Arrow function
let fn_arr = function(){
  console.log(this.constructor.name);  // Object(data)
  setTimeout(() => {
    console.log(this.constructor.name) // Object(data)
  },100);
}

let id = 21;
let data = {
  id: 21
}

fn.call(data);     
fn_arr.call(data); 


setTimeout:裡面都分別帶入 setTimeout 的函式,我們知道 setTimeout 執行的時間會在整個 JS execution context 都被執行完後才會執行(如果對這概念不清楚的話可參考:[筆記] 談談JavaScript中的asynchronous和event queue),因此函式建立的時間和實際執行的時間是不同的,因此這也創造了兩個不同時間點的 this 所指稱的對象。

call:另一個要了解的是 call 的用法,在 call 當中,我們會帶入後面所指定的物件當作所指稱的 this 對象(對這個概念不清楚的話可參考:[筆記] 了解function borrowing和function currying ─ bind(), call(), apply() 的應用)。

this.constructor.name:這樣子的寫法只是避免在回傳出物件的時候把整個物件內容給傳出來,而是指示傳出該物件的名稱(參考自:stackoverflow)。

綜合上述,我們可以知道,因為有用 call(data) 的緣故,因此不論是使用傳統函式寫法(fn)或箭頭函式(fn_arr)時,在一開始函式裡面的 this 都指稱的是 data 這個物件。

然而不同的地方會在執行 setTimeout 中的函式,在使用傳統函式的寫法時,使用 fn.call(data) 時,因為它執行的時間點是在整個 JS execution context 執行完才執行,而當時的環境會變成是 global environment,因此使用傳統函式時,這個 this 指稱的對象會轉變成 window object 。但是,如果是使用新的箭頭函式(arrow function),這個 this 所指稱的對象則不會改變,依然是 data 這個 object

範例:https://jsbin.com/wodegu/edit?js,console

例子二(參考自 Accelerated ES6 JavaScript Training

第二個例子是使用 addEventListener 來達到示範,首先我們在 HTML 中建立一個 button element,然後利用 JS 來抓取這個 button,接著 JS 部分則如下所示:

var button = document.querySelector('button');
var fn_arr = () => {
  // 建立 function 時 this 指 Window
  console.log(this.constructor.name)  // 執行function時 this 指 Window
};
var fn = function(){
  // 建立 function 時 this 指 Window
  console.log(this.constructor.name)  // 執行function時 this 指 HTMLButtonElement

button.addEventListener('click', fn_arr);


和例子一中的 setTimeout 類似,我們使用的 addEventListener ,也會在整個 execution context 執行結束後,在網頁觸發事件時才執行。

因此不論在傳統的函式寫法(fn)或箭頭函式(fn_arr)的寫法,一開始建立 function 的時候 this 所指稱的都是  window 這個物件,然而,如果是使用傳統的寫法,在觸發這個事件時所指稱的對象會從原本的 window 變成 HTMLButtonElement;若使用的是箭頭函式,則會固定所指稱的對象,因此 this 依然指稱的是 window 這個物件。

範例:https://jsbin.com/kotetu/edit?js,output

0 意見:

張貼留言