年味兒漸散,收拾下心情,繼續敲代碼吧。前端
對於即將到來金三銀四的求職季,相信很多同窗都在默默地作着準備。本系列旨在梳理前端龐雜的知識點,並儘量通俗易懂地表述出來,也但願能幫到有須要的同窗。react
這是前端面試題系列的第 5 篇,你可能錯過了前面的篇章,能夠在這裏找到:面試
面試中,我常常會問及 ES6 的知識點,由於平時工做中用得不少。當問到箭頭函數時,很多候選人都會讚歎地說:箭頭函數很好用,並且不再用操心 this 的指向了。segmentfault
我接着問:箭頭函數是挺好用的,可是你有沒有遇到過,不適合使用箭頭函數的場景呢?瀏覽器
這時,能回答得上來的候選人就不多了。箭頭函數在大多數狀況下,是很好用的,可是爲何在有些場景,使用箭頭函數後會產生問題?是否是箭頭函數還不夠完善?又有哪些場景會發生問題?該如何解決呢?這,正是本文想要一塊兒探討的。函數
爲何叫箭頭函數( Arrow Function )?由於它的寫法,看上去就是一個箭頭:佈局
const multiply = num => num * num;
它等價於:this
const multiply = function (num) { return num * num; };
此外,還能夠傳多個參數,以及可變參數。spa
// 多參數 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
。prototype
另外,若是返回的結果是對象,則須要配上 ()
,像下面這樣:
const func = val => ({ value: val });
從上述的寫法來看,相較普通函數而言,箭頭函數的確簡便了不少,提高了咱們代碼的易用性。但它並不是在任何場景下都適用,接下來,將會介紹幾種不適合箭頭函數的場景,並會提出可行的解決方案。
看下面這個例子:
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 }
一樣的規則也適用於原型方法的定義,使用箭頭函數會致使運行時的執行上下文錯誤。
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 實例。
看下面這個例子:
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,因此事件處理函數的調用者並不受影響。
箭頭函數不能經過 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:歡迎關注個人公衆號 「超哥前端小棧」,交流更多的想法與技術。