「前端面試題系列5」ES6 中箭頭函數的用法

圖片描述

前言

年味兒漸散,收拾下心情,繼續敲代碼吧。

對於即將到來金三銀四的求職季,相信不少同學都在默默地做着準備。本系列旨在梳理前端龐雜的知識點,並儘可能通俗易懂地表述出來,也希望能幫到有需要的同學。

這是前端面試題系列的第 5 篇,你可能錯過了前面的篇章,可以在這裏找到:

面試中,我經常會問及 ES6 的知識點,因爲平時工作中用得很多。當問到箭頭函數時,不少候選人都會讚歎地說:箭頭函數很好用,而且再也不用操心 this 的指向了。

我接着問:箭頭函數是挺好用的,但是你有沒有遇到過,不適合使用箭頭函數的場景呢?

這時,能回答得上來的候選人就很少了。箭頭函數在大多數情況下,是很好用的,但是爲什麼在有些場景,使用箭頭函數後會產生問題?是不是箭頭函數還不夠完善?又有哪些場景會發生問題?該如何解決呢?這,正是本文想要一起探討的。

箭頭函數的寫法

爲什麼叫箭頭函數( Arrow Function )?因爲它的寫法,看上去就是一個箭頭:

const multiply = num => num * num;

它等價於:

const multiply = function (num) {
    return num * num;
};

此外,還可以傳多個參數,以及可變參數。

// 多參數
const multiply = (num1, num2) => num1 * num2;

// 可變參數
const sum = (num1, num2, ...rest) => {
    let result = num1 + num2;
    for (let i = 0; i < rest.length; i++) {
        result += rest[i];
    }
    
    return result;
};

當有多條語句時,需要配上 {...}return

另外,如果返回的結果是對象,則需要配上 (),像下面這樣:

const func = val => ({ value: val });

從上述的寫法來看,相較普通函數而言,箭頭函數的確簡便了很多,提升了我們代碼的易用性。但它並非在任何場景下都適用,接下來,將會介紹幾種不適合箭頭函數的場景,並會提出可行的解決方案。

不適合的場景

1、對象的方法

看下面這個例子:

const obj = {
    x: 1,
    print: () => {
        console.log(this === window); // => true
        console.log(this.x); // undefined
    }
};

obj.print();

this.x 打印出來是 undefined。爲什麼?然後,我在上面加了一行,發現 this 指向了 window。

解析:print 方法用了箭頭函數,其內部的 this 指向的還是上下文 window,上下文中並沒有定義 x,所以 this.x 輸出爲 undefined。

解決辦法:用 ES6 的短語法,或者傳統的函數表達式都可以。所以,print 要這樣寫:

print () {
    console.log(this === test); // => true
    console.log(this.x); // 1
}

2、原型方法

同樣的規則也適用於原型方法的定義,使用箭頭函數會導致運行時的執行上下文錯誤。

function Cat (name) {
    this.name = name;
}

Cat.prototype.sayCatName = () => {
    console.log(this === window); // => true
    return this.name;
};

const cat = new Cat('Miao');
cat.sayCatName(); // => undefined

解決辦法是:用回傳統的函數表達式,像下面這樣:

Cat.prototype.sayCatName = function () {
    console.log(this === cat); // => true
    return this.name;
};

sayCatName 變回傳統的函數表達式之後,被調用時的執行上下文就會指向新創建的 cat 實例。

3、事件的回調

看下面這個例子:

const btn = document.getElementById('myButton');
btn.addEventListener('click', () => {
  console.log(this === window); // => true
  this.innerHTML = 'Clicked button';
});

這裏會有問題,因爲 this 指向了 window。

解析:當爲一個 DOM 事件綁定回調函數後,觸發回調函數時的 this,需要指向當前發生事件的 DOM 節點,也就是這裏的 btn。當回調發生時,瀏覽器會用 btn 的上下文去調用處理函數。所以最後的 this.innerHTML 等價於 window.innerHTML,問題就在這裏。

解決辦法:用函數表達式代替箭頭函數。像這樣:

btn.addEventListener('click', function() {
    console.log(this === btn); // => true
    this.innerHTML = 'Clicked button';
});

另外,在 react 中的事件回調,也經常會遇到類似的問題。

// jsx render
<Button onClick={this.handleClickButton.bind(this)}>
    ...
</Button>

// callback
handleClickButton () {
    ...
}

注意:這裏 onClick 的回調函數,並非字符串,而是一個實實在在的函數。可以將 onClick 理解爲一箇中間變量,所以 react 在處理函數時的 this 指向就會丟失。

爲了解決這個問題,我們需要爲回調函數綁定 this,使得事件處理函數無論如何傳遞,this 都指向我們實例化的那個對象。

在這裏,如果用箭頭函數,可以這樣改寫:

<Button onClick={ event => this.handleClickButton(event) }>
    ...
</Button>

箭頭函數並沒有自己的 this,所以事件處理函數的調用者並不受影響。

4、構造函數

箭頭函數不能通過 new 關鍵字調用。

const Message = (text) => {
    this.text = text;
};

var helloMessage = new Message('Hello World!');
// Uncaught TypeError: Message is not a constructor

解析:從報錯信息可以看出,箭頭函數沒有 constructor 方法,所以不能用作構造函數。 JavaScript 會通過拋出異常的方式,進行隱式地預防。

解決方法:用函數表達式代替箭頭函數。

總結

回顧 MDN 給出的解釋:箭頭函數表達式的語法比函數表達式更短,並且沒有自己的this,arguments,super或 new.target。這些函數表達式更適用於那些本來需要匿名函數的地方,並且它們不能用作構造函數。

所以說,箭頭函數無疑是 ES6 帶來的重大改進,在正確的場合使用箭頭函數,能讓代碼變得更加簡潔短小。但箭頭函數也不是萬能的,不能用的時候,千萬別硬往上套。比如,在需要動態上下文的場景中,使用箭頭函數需要格外地小心,這些場景包括:對象的方法、原型方法、事件的回調、構造函數。並非一定要用箭頭函數,才能解決問題。

PS:歡迎關注我的公衆號 “超哥前端小棧”,交流更多的想法與技術。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章