少年,不要濫用箭頭函數啊
在ES6大行其道的今天,不應用點ES6特性似乎有些政治不正確。最近剛好有個Node的項目,最低要支持到nodejs 4.0,在node.green看了下ES6的支持度,我想使用的特性基本都有支持,遂決定在新項目中采用ES6來寫。
當然第一件事情就是毫不留情地消滅var,項目中能用const的地方不用let,能用let的地方不用var。
第二件事情就是使用勞動人民喜聞樂見的箭頭函數替代function。當我心滿意足地看到滿屏的 => 時,現實給了我一記響亮的耳光——改過之后的程序錯誤百出!
所以,當我們使用箭頭函數時,一定要搞清楚箭頭函數是什么回事,適用于什么場景。本文就針對以上問題來討論下箭頭函數。
箭頭函數是什么?
箭頭函數的語法我就不講了,相信大家都見識過。跟我一樣,大家喜歡箭頭函數90%的原因是它好看。除了好看,它是不是與function等價呢?肯定不等價,因為TC39不可能僅因為好看而引入一個語法糖(class除外)。
箭頭函數的淵源可以追溯到上古時期一個叫lambda演算的東西。lambda演算是數學家提出來的,有些數學家跟我們程序員一樣也很懶,數學定理那么多,今天要證三角定律,明天要證勾股定律,累不累!那能不能將所有的證明問題用一個統一的體系進行形式化描述,然后由機器來完成自動推導呢?lambda演算就是干這個的,圖靈也搞了一套體系叫圖靈機,兩者是等價的。
關于lambda演算說了這么多,好像跟今天要講的箭頭函數沒什么關系?其實是有關系的,lambda演算深刻影響了箭頭函數的設計。數學家們喜歡用純函數式編程語言,純函數的特點是沒有副作用,給予特定的輸入,總是產生確定的輸出,甚至有些情況下通過輸出能夠反推輸入。要實現純函數,必須使函數的執行過程不依賴于任何外部狀態,整個函數就像一個數學公式,給定一套輸入參數,不管是在地球上還是火星上執行都是同一個結果。
箭頭函數要實現類似純函數的效果,必須剔除外部狀態。所以當你定義一個箭頭函數,在普通函數里常見的 this 、 arguments 、 caller 是統統沒有的。
箭頭函數沒有 this
箭頭函數沒有 this ,那下面的代碼明顯可以取到 this 啊:
function foo() {
let a = 1
let b = () => console.log(this.a)
b()
}
foo() // 1
以上箭頭函數中的 this 其實是父級作用域中的 this ,即函數 foo 的 this 。箭頭函數引用了父級的變量,構成了一個閉包。以上代碼等價于:
function foo() {
let a = 1
let self = this
let b = () => console.log(self.a)
b()
}
foo() // 1
箭頭函數不僅沒有 this ,常用的 arguments 也沒有。如果你能獲取到 arguments ,那它一定是來自父作用域的。
function foo() {
return () => console.log(arguments[0])
}
foo(1, 2)(3, 4) // 1
上例中如果箭頭函數有 arguments ,就應該輸出的是3而不是1。
一個經常犯的錯誤是使用箭頭函數定義對象的方法,如:
let a = {
foo: 1,
bar: () => console.log(this.foo)
}
a.bar() //undefined
以上代碼中,箭頭函數中的 this 并不是指向 a 這個對象。對象 a 并不能構成一個作用域,所以再往上到達全局作用域, this 就指向全局作用域。如果我們使用普通函數的定義方法,輸出結果就符合預期,這是因為 a.bar() 函數執行時作用域綁定到了 a 對象。
let a = {
foo: 1,
bar: function() { console.log(this.foo) }
}
a.bar() //undefined
另一個錯誤是在原型上使用箭頭函數,如:
function A() {
this.foo = 1
}
A.prototype.bar = () => console.log(this.foo)
let a = new A()
a.bar() //undefined
同樣,箭頭函數中的 this 不是指向 A ,而是根據變量查找規則回溯到了全局作用域。同樣,使用普通函數就不存在問題。
通過以上說明,我們可以看出,箭頭函數除了傳入的參數之外,真的是什么都沒有!如果你在箭頭函數引用了 this 、 arguments 或者參數之外的變量,那它們一定不是箭頭函數本身包含的,而是從父級作用域繼承的。
什么情況下該使用箭頭函數
到這里,我們可以發現箭頭函數并不是萬金油,稍不留神就會踩坑。
至于什么情況該使用箭頭函數,《You Don’t Know About JS》給出了一個決策圖:
以上決策圖看起來有點復雜,我認為有三點比較重要:
- 箭頭函數適合于無復雜邏輯或者無副作用的純函數場景下,例如用在 map 、 reduce 、 filter 的回調函數定義中;
- 不要在最外層定義箭頭函數,因為在函數內部操作 this 會很容易污染全局作用域。最起碼在箭頭函數外部包一層普通函數,將 this 控制在可見的范圍內;
- 如開頭所述,箭頭函數最吸引人的地方是簡潔。在有多層函數嵌套的情況下,箭頭函數的簡潔性并沒有很大的提升,反而影響了函數的作用范圍的識別度,這種情況不建議使用箭頭函數。
來自:https://jingsam.github.io/2016/12/08/things-you-should-know-about-arrow-functions.html