做者|Casper Beyer譯者|王強編輯|王文婧自告別 Harmony 的時代以來,JavaScript 推出了許多新的、帶語法糖的功能。雖然說更多新功能可讓咱們編寫可讀性和質量更高的代碼,但咱們也很容易被這些新奇、亮眼的特性迷惑,反而陷入一些潛在的陷阱。本文做者回顧他在使用 JS 時常常遇到的困惑,新舊問題都有。但願你能夠經過閱讀本文,避免這些問題在你的編碼中發生。箭頭函數和對象字面量箭頭函數提供了更簡短的語法,其中一個特性是你能夠將函數編寫爲具備隱式返回值的 lambda 表達式。編寫函數樣式的代碼時這就很順手,好比說有時你必須使用一個函數映射一些數組的狀況。使用常規函數可能會多出不少空行。例如:javascript
const numbers = [1, 2, 3, 4];用 lambda 樣式的箭頭函數來寫的話,就會寫成兩行優雅、易讀的代碼:
numbers.map(function(n) {
return n * n;
});
const numbers = [1, 2, 3, 4];
numbers.map(n => n * n);
在這種用例中,箭頭函數的表現符合預期,它將值自己相乘並返回到包含 [1, 4, 9, 16] 的新數組。html
但若是你嘗試映射到對象,那麼語法可能就不是你直覺指望的那樣了。例如,假設咱們試圖將數字映射到包含以下值的對象數組中:const numbers = [1, 2, 3, 4];這裏的結果其實是一個包含未定義值的數組。雖然看起來咱們在這裏返回一個對象,可是解釋器看到的東西是徹底不同的。花括號被解釋爲箭頭函數的塊做用域,而值語句最後實際上成爲了標籤。若是將上述箭頭函數外推到解釋器最終實際執行的內容中,它將看起來像這樣:
numbers.map(n => { value: n });
const numbers = [1, 2, 3, 4];解決方法很是微妙。咱們只須要將對象包裝在括號中,就能夠將它變成一個表達式而不是一個塊語句,以下所示:
numbers.map(function(n) {
value:
n
return;
});
const numbers = [1, 2, 3, 4];
numbers.map(n => ({ value: n }));
這會計算出一個包含對象數組的數組,該對象數組具備預期的值。前端
箭頭函數和綁定箭頭函數另外一個須要注意的點是,它們沒有本身的 this 綁定,意味着它們的 this 值和封閉詞法做用域的 this 值是同樣的。java
所以,儘管箭頭函數的語法更時尚一些,但它並不能替代一些很好的舊函數。你可能會很容易遇到 this 綁定與你本來所想不同的狀況。例如:let calculator = {
value: 0,
add: (values) => {
this.value = values.reduce((a, v) => a + v, this.value);
},
};
calculator.add([1, 2, 3]);
console.log(calculator.value);
儘管人們可能但願這裏的 this 綁定爲此處的 calculator 對象,但實際上 this 綁定最後要麼是未定義,要麼是全局對象,具體取決於代碼是否在嚴格模式下運行。這是由於這裏最接近的詞彙做用域是全局做用域。在嚴格模式下這是未定義的。不然,它會是瀏覽器中的窗口對象(或 Node.js 兼容環境中的過程對象)。數組
常規函數確實具備 this 綁定。在對象上調用時,this 將指向該對象,所以常規函數仍然是得到成員函數的正確途徑。let calculator = {
value: 0,
add: (values) => {
this.value = values.reduce((a, v) => a + v, this.value);
},
};
calculator.add([1, 2, 3]);
console.log(calculator.value);
另外,因爲箭頭函數沒有 this 綁定,所以 Function.prototype.call、Function.proto-type.bind 和 Function.prototype.apply 均沒法使用。聲明箭頭函數後,this 綁定設置爲固定,沒法更改。瀏覽器
所以,在下面的示例中,咱們將遇到與以前相同的問題:當調用 adder 的 add 函數時,this 綁定又成了全局對象,儘管咱們嘗試使用 Function.prototype.call 覆蓋它:const adder = {
add: (values) => {
this.value = values.reduce((a, v) => a + v, this.value);
},
};
let calculator = {
value: 0
};
adder.add.call(calculator, [1, 2, 3]);
箭頭函數很簡潔,但不能替換須要 this 綁定的常規成員函數。微信
自動分號插入雖然這不是一項新功能,但自動分號插入(ASI)是 JavaScript 中比較怪異的功能之一,所以值得一提。從理論上講,你能夠在大多數時候省略分號(許多項目都這樣作)。若是項目有先例,則應遵循此先例。但你必定須要記得 ASI 是一項功能,不然最後你會寫出容易迷惑人的代碼。架構
請看如下示例:return有人可能會認爲它會返回對象字面量,但實際上它會返回未定義的值,由於發生了分號插入,使其成爲空的 return 語句,後跟一個 block 語句和一個 label 語句。換句話說,最終被解釋的代碼看起來更像是下面這種寫法:
{
value: 42
}
return;
{
value: 42
};
根據經驗,即便使用分號時也切勿以大括號、方括號或模板字符串字面量開頭,由於 ASI 老是會起做用。app
淺集合集合較淺,意味着重複的數組和具備相同值的對象,這將致使集合中有多個條目。例如:let set = new Set();該集合的大小將爲 2,若是你考慮引用它的話就要注意了,由於它們是不一樣的對象。但字符串是不可變的。以集合中的多個字符串爲例:
set.add([1, 2, 3]);
set.add([1, 2, 3]);
console.log(set.length);
let set = new Set();
set.add([1, 2, 3].join(','));
set.add([1, 2, 3].join(','));
console.log(set.size);
因爲字符串是不可變的,且駐留在 JavaScript 中,所以最終的集合大小爲 1。若是你須要存儲一組對象,那麼這就能夠是一種解決方法,也能夠將它們序列化和反序列化。less
類與暫時死區在 JavaScript 中,常規函數被提高到詞法做用域的頂部,這意味着下面的示例將按預期工做:let segment = new Segment();可是對於類來講並不是如此。類實際上沒有被提高,而且在嘗試使用它們以前須要在詞法做用域內對其進行徹底定義。例如:
function Segment() {
this.x = 0;
this.y = 0;
}
let segment = new Segment();
class Segment {
constructor() {
this.x = 0;
this.y = 0;
}
}
嘗試構造類的新實例時會致使 ReferenceError,由於它們沒有像函數那樣被提高。
FinallyFinally 是一個特殊狀況。看一下如下代碼片斷:try {
return true;
} finally {
return false;
}
你認爲它會返回什麼值?答案既符合直覺,同時又能夠是反直覺的。有人可能會認爲第一個 return 語句使函數實際返回並彈出調用棧。但這裏是該規則的例外,由於 Finally 語句始終都會運行,所以會返回 Finally 塊中的 return 語句。
小 結JavaScript 很容易入門,但很難精通。換句話說,開發人員須要搞清楚本身正在作什麼,明白爲何要這樣作,不然就很容易出錯。
ECMAScript 6 及其含糖功能尤爲如此。特別是箭頭函數哪裏都會冒出來。讓我來猜的話,那是由於開發人員認爲它們比常規函數更漂亮。但它們不是常規函數,所以沒法替代後者。
時不時地瀏覽一下規範並無什麼壞處。它不是世界上最激動人心的文檔,但就規範自己而言寫得還算不錯。
規範:https://www.ecma-international.org/ecma-262/9.0/index.html
AST Explorer 之類的工具還能夠幫助你瞭解某些極端場景下的情況。人類和計算機每每會以不一樣的方式來解析事物。
文章最後,我把最後一個示例留做練習供你們思考。
原文連接:https://medium.com/better-programming/lesser-known-javascript-hazards-8d688a463b1f
活動推薦偶發 bug 大大增長了排查的成本,復現也變成了全部研發心中的痛。在即將召開的 ArchSummit 全球架構師峯會(北京站)上,貝殼找房基礎架構中心前端架構委員會專家陳辰將爲你們帶來貝殼自研監控平臺燈塔以外的另外一項目——時光機,揭祕如何利用時光機讓偶現 bug 無所遁形。
點擊【閱讀原文】查看詳情。目前 9 折限時直降 880 元!瞭解詳情請聯繫票務經理灰灰:15600537884 (同微信)。