摘要:顯然,箭頭函數是不能用來做構造函數,實際上會禁止你這么做,如果你這么做了,它就會拋出異常。換句話說,箭頭構造函數的執行并沒有任何意義,并且是有歧義的。
共 2670 字,讀完需 5 分鐘。編譯自 Dmitri Pavlutin 的文章,對原文內容做了精簡和代碼風格優化。ES6 中引入的箭頭函數可以讓我們寫出更簡潔的代碼,但是部分場景下使用箭頭函數會帶來嚴重的問題,有哪些場景?會導致什么問題?該怎么解決,容我慢慢道來。
能見證每天在用的編程語言不斷演化是一件讓人非常興奮的事情,從錯誤中學習、探索更好的語言實現、創造新的語言特性是推動編程語言版本迭代的動力。JS 近幾年的變化就是最好的例子, 以 ES6 引入的箭頭函數(arrow functions)、class 等特性為代表,把 JS 的易用性推到了新的高度。
關于 ES6 中的箭頭函數,網上有很多文章解釋其作用和語法,如果你剛開始接觸 ES6,可以從這里開始。任何事物都具有兩面性,語言的新特性常常被誤解、濫用,比如箭頭函數的使用就存在很多誤區。接下來,筆者會通過實例介紹該避免使用箭頭函數的場景,以及在這些場景下該如何使用函數表達式(function expressions)、函數聲明或者方法簡寫(shorthand method)來保障代碼正確性和可讀性。
1. 定義對象方法JS 中對象方法的定義方式是在對象上定義一個指向函數的屬性,當方法被調用的時候,方法內的 this 就會指向方法所屬的對象。
1.1 定義字面量方法因為箭頭函數的語法很簡潔,可能不少同學會忍不住用它來定義字面量方法,比如下面的例子 JS Bin:
const calculator = { array: [1, 2, 3], sum: () => { console.log(this === window); // => true return this.array.reduce((result, item) => result + item); } }; console.log(this === window); // => true // Throws "TypeError: Cannot read property "reduce" of undefined" calculator.sum();
calculator.sum 使用箭頭函數來定義,但是調用的時候會拋出 TypeError,因為運行時 this.array 是未定義的,調用 calculator.sum 的時候,執行上下文里面的 this 仍然指向的是 window,原因是箭頭函數把函數上下文綁定到了 window 上,this.array 等價于 window.array,顯然后者是未定義的。
解決的辦法是,使用函數表達式或者方法簡寫(ES6 中已經支持)來定義方法,這樣能確保 this 是在運行時是由包含它的上下文決定的,修正后的代碼如下 JS Bin:
const calculator = { array: [1, 2, 3], sum() { console.log(this === calculator); // => true return this.array.reduce((result, item) => result + item); } }; calculator.sum(); // => 6
這樣 calculator.sum 就變成了普通函數,執行時 this 就指向 calculator 對象,自然能得到正確的計算結果。
1.2 定義原型方法同樣的規則適用于原型方法(prototype method)的定義,使用箭頭函數會導致運行時的執行上下文錯誤,比如下面的例子 JS Bin:
function Cat(name) { this.name = name; } Cat.prototype.sayCatName = () => { console.log(this === window); // => true return this.name; }; const cat = new Cat("Mew"); cat.sayCatName(); // => undefined
使用傳統的函數表達式就能解決問題 JS Bin:
function Cat(name) { this.name = name; } Cat.prototype.sayCatName = function () { console.log(this === cat); // => true return this.name; }; const cat = new Cat("Mew"); cat.sayCatName(); // => "Mew"
sayCatName 變成普通函數之后,被調用時的執行上下文就會指向新創建的 cat 實例。
2. 定義事件回調函數this 是 JS 中很強大的特性,可以通過多種方式改變函數執行上下文,JS 內部也有幾種不同的默認上下文指向,但普適的規則是在誰上面調用函數 this 就指向誰,這樣代碼理解起來也很自然,讀起來就像在說,某個對象上正在發生某件事情。
但是,箭頭函數在聲明的時候就綁定了執行上下文,要動態改變上下文是不可能的,在需要動態上下文的時候它的弊端就凸顯出來。比如在客戶端編程中常見的 DOM 事件回調函數(event listenner)綁定,觸發回調函數時 this 指向當前發生事件的 DOM 節點,而動態上下文這個時候就非常有用,比如下面這段代碼試圖使用箭頭函數來作事件回調函數 JS Bin:
const button = document.getElementById("myButton"); button.addEventListener("click", () => { console.log(this === window); // => true this.innerHTML = "Clicked button"; });
在全局上下文下定義的箭頭函數執行時 this 會指向 window,當單擊事件發生時,瀏覽器會嘗試用 button 作為上下文來執行事件回調函數,但是箭頭函數預定義的上下文是不能被修改的,這樣 this.innerHTML 就等價于 window.innerHTML,而后者是沒有任何意義的。
使用函數表達式就可以在運行時動態的改變 this,修正后的代碼 JS Bin:
const button = document.getElementById("myButton"); button.addEventListener("click", function() { console.log(this === button); // => true this.innerHTML = "Clicked button"; });
當用戶單擊按鈕時,事件回調函數中的 this 實際指向 button,這樣的 this.innerHTML = "Clicked button" 就能按照預期修改按鈕中的文字。
3. 定義構造函數構造函數中的 this 指向新創建的對象,當執行 new Car() 的時候,構造函數 Car 的上下文就是新創建的對象,也就是說 this instanceof Car === true。顯然,箭頭函數是不能用來做構造函數, 實際上 JS 會禁止你這么做,如果你這么做了,它就會拋出異常。
換句話說,箭頭構造函數的執行并沒有任何意義,并且是有歧義的。比如,當我們運行下面的代碼 JS Bin:
const Message = (text) => { this.text = text; }; // Throws "TypeError: Message is not a constructor" const helloMessage = new Message("Hello World!");
構造新的 Message 實例時,JS 引擎拋了錯誤,因為 Message 不是構造函數。在筆者看來,相比舊的 JS 引擎在出錯時悄悄失敗的設計,ES6 在出錯時給出具體錯誤消息是非常不錯的實踐。可以通過使用函數表達式或者函數聲明 來聲明構造函數修復上面的例子 JS Bin:
const Message = function(text) { this.text = text; }; const helloMessage = new Message("Hello World!"); console.log(helloMessage.text); // => "Hello World!"4. 追求過短的代碼
箭頭函數允許你省略參數兩邊的括號、函數體的花括號、甚至 return 關鍵詞,這對編寫更簡短的代碼非常有幫助。這讓我想起大學計算機老師給學生留過的有趣作業:看誰能使用 C 語言編寫出最短的函數來計算字符串的長度,這對學習和探索新語言特性是個不錯的法子。但是,在實際的軟件工程中,代碼寫完之后會被很多工程師閱讀,真正的 write one, read many times,在代碼可讀性方面,最短的代碼可能并不總是最好的。一定程度上,壓縮了太多邏輯的簡短代碼,閱讀起來就沒有那么直觀,比如下面的例子 JS Bin:
const multiply = (a, b) => b === undefined ? b => a * b : a * b; const double = multiply(2); double(3); // => 6 multiply(2, 3); // => 6
multiply 函數會返回兩個數字的乘積或者返回一個可以繼續調用的固定了一個參數的函數。代碼看起來很簡短,但大多數人第一眼看上去可能無法立即搞清楚它干了什么,怎么讓這段代碼可讀性更高呢?有很多辦法,可以在箭頭函數中加上括號、條件判斷、返回語句,或者使用普通的函數 JS Bin:
function multiply(a, b) { if (b === undefined) { return function (b) { return a * b; } } return a * b; } const double = multiply(2); double(3); // => 6 multiply(2, 3); // => 6
為了讓代碼可讀性更高,在簡短和啰嗦之間把握好平衡是非常有必要的。
5. 總結箭頭函數無疑是 ES6 帶來的重大改進,在正確的場合使用箭頭函數能讓代碼變的簡潔、短小,但某些方面的優勢在另外一些方面可能就變成了劣勢,在需要動態上下文的場景中使用箭頭函數你要格外的小心,這些場景包括:定義對象方法、定義原型方法、定義構造函數、定義事件回調函數。
One More Thing本文作者王仕軍,商業轉載請聯系作者獲得授權,非商業轉載請注明出處。如果你覺得本文對你有幫助,請點贊!如果對文中的內容有任何疑問,歡迎留言討論。想知道我接下來會寫些什么?歡迎訂閱我的掘金專欄或知乎專欄:《前端周刊:讓你在前端領域跟上時代的腳步》。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/82661.html
摘要:回顧我們先來回顧下箭頭函數的基本語法。主要區別包括沒有箭頭函數沒有,所以需要通過查找作用域鏈來確定的值。箭頭函數并沒有方法,不能被用作構造函數,如果通過的方式調用,會報錯。 回顧 我們先來回顧下箭頭函數的基本語法。 ES6 增加了箭頭函數: let func = value => value; 相當于: let func = function (value) { return ...
摘要:第二種情況是箭頭函數的如果指向普通函數它的繼承于該普通函數。箭頭函數的指向全局,使用會報未聲明的錯誤。 showImg(https://segmentfault.com/img/remote/1460000018610072?w=600&h=400); 箭頭函數是ES6的API,相信很多人都知道,因為其語法上相對于普通函數更簡潔,深受大家的喜愛。就是這種我們日常開發中一直在使用的API...
摘要:換句話說,箭頭函數構造函數調用沒有意義,而且是模糊的。讓我們看看如果嘗試這樣做會發生什么執行,其中是一個箭頭函數,拋出一個錯誤,不能用作構造函數。當需要動態上下文時,不能使用箭頭函數定義方法,使用構造函數創建對象,在處理事件時從獲取目標。 為了保證的可讀性,本文采用意譯而非直譯。 想閱讀更多優質文章請猛戳GitHub博客,一年百來篇優質文章等著你! 這些年來,ES6 將 JS 的可用性...
摘要:錯誤的寫法錯誤的寫法中的構造函數新增了支持默認參數和不定參數。箭頭函數的簡單理解箭頭函數的左邊表示輸入的參數,右邊表示輸出的結果。但是有了尾調用優化之后,遞歸函數的性能有了提升。 作為前端切圖仔,越發覺得自己離不開函數了。 說到JavaScript函數,腦子里都是匿名函數、普通函數、閉包函數、構造函數......然后還能說出一大堆函數的概念。如果你達到這個水平,那么函數對你來說沒有難度...
閱讀 807·2021-09-06 15:02
閱讀 2448·2019-08-30 15:43
閱讀 2173·2019-08-30 11:26
閱讀 2378·2019-08-26 12:12
閱讀 3546·2019-08-23 18:24
閱讀 3263·2019-08-23 18:16
閱讀 702·2019-08-23 17:02
閱讀 2251·2019-08-23 15:34