箭頭函數=>無疑是ES6中最受關注的一個新特性了,經過它能夠簡寫 function 函數表達式,你也能夠在各類說起箭頭函數的地方看到這樣的觀點——「=> 就是一個新的 function」。git
粉個人人都知道俺由於某些緣由不怎麼喜歡 => 的語法,不過別擔憂,本文並不是講述我爲什麼不喜歡它,若是你對這個觀點感興趣,能夠查看我《YDKJS:ES6 & Beyonf》一書的第二章。es6
我想在這裏理清一下箭頭函數到底對 this 和 arguments 等東東作了些啥,事實上我在以前從未準確解釋過這一點,對此感到有點愧疚,因而乎想洗白下本身。你能夠在這裏看到我對於該話題的第一次陳述。github
是否局部(Lexical)?閉包
包括我在內的許多人,都會這麼描述箭頭函數裏 this 的行爲:局部的 this。函數
什麼意思呢?測試
function foo() { setTimeout( () => { console.log("id:", this.id); },100); } foo.call( { id: 42 } ); // id: 42
這裏的 => 箭頭函數看起來把它內部的 this 綁定爲父函數 foo() 裏的 this。若是這個內部函數是一個常規的函數(聲明或表達式),它的 this 將相似 setTimeout 如何調用函數同樣被控制着。若是你對 this 綁定的規則還不清楚,能夠查閱我《YDKJS:this & Object Prototypes》一書的第二章。this
局部變量 thisspa
一個描述 this 行爲觀察的經常使用伎倆是:prototype
function foo() { var self = this; setTimeout(function() { console.log("id:", self.id); },100); } foo.call( { id: 42 } ); // id: 42
旁註:上方「self」的變量名實際上是一個很是糟糕、容易誤解的名字,它意味着把 this 指向函數本身,而它並無這麼作。調試
var that = this 也是一個一樣不妥的語義,特別當存在多個做用域而使用(that1, that2, ...)的時候更糟糕。若是你想起個語義穩當的好名字,能夠試試 var context = this,由於它能準確描述 this 是什麼——一個動態的上下文。
從上方的代碼段咱們能夠看到,咱們並無在內部函數中使用到 this,取而代之的是一個更具預見性的局部變量。咱們在外部函數中聲明瞭變量 self,簡單地關聯了內部函數裏用到的變量。
這麼一來咱們經過使用局部做用域以及閉包的原理,完全地繞過方程式(示例代碼中的內部函數)中綁定 this 的規則。
這樣的結果看起來跟 => 箭頭函數是同樣的,換句話說,咱們會(錯誤地)認爲 => 箭頭函數有着一個跟局部變量/閉包機制同樣的「局部 this」行爲。
但這種觀點並不正確,坑爹了。
箭頭函數的this綁定
咱可經過另外一個方法來觀察箭頭函數中 this 的行爲——給內部函數作一個強制綁定:
function foo() { setTimeout(function() { console.log("id:", this.id); }.bind(this),100); } foo.call( { id: 42 } ); // id: 42
你能夠看到咱們使用了 .bind(this) 來把內部函數中的 this 綁定到了外部函數去,這樣一來不管 setTimeout 會選擇如何調用賦予它的函數,該函數都會使用 foo() 裏所使用到的 this。
是的,這個版本的代碼中咱們觀測到的行爲跟以前兩段示例代碼所要論述的同樣,它更準確麼?許多童鞋都認爲 => 箭頭函數就是這麼工做的。
嘖嘖~圖樣圖森破了~
生來局部
TC39的常客 Dave Herman 曾更仔細、準確地向我闡述過這個問題,但我很愧疚一直沒能徹底瞭解他所陳述的含義,所以對於我往日不許確的言論我就更感歉意了,也更能接納他人的觀點。
Dave 主要對我這麼說,「你說起的'局部 this'的描述很蹩腳,由於 this 不管如何都是局部的」。
真的麼?嗯哼~
他繼續說道,「箭頭函數 => 所改變的並不是把 this 局部化,而是徹底不把 this 綁定到裏面去」。
等等,這樣合理麼?我明明能夠在 => 箭頭函數裏使用 this 的不是麼?
固然能夠,不過一切是這麼發生的 —— 雖然 => 箭頭函數沒有一個本身的 this,但當你在內部使用了 this,常規的局部做用域準則就起做用了,它會指向最近一層做用域內的 this。
來個示例:
function foo() { return () => { return () => { return () => { console.log("id:", this.id); }; }; }; } foo.call( { id: 42 } )()()(); // id: 42
思考下,在這段代碼中,
有多少次 this 的綁定執行了呢?大部分人會認爲有4次——每一個函數裏各一次。
事實上更準確地說,只有一次纔對,它發生於 foo() 函數中。
這些接連內嵌的函數們都沒有聲明它們本身的 this,因此 this.id 的引用會簡單地順着做用域鏈查找,一直查到 foo() 函數,它是第一處能找到一個確切存在的 this 的地方。
說白了跟其它局部變量的常規處理是一致的!
換句話說,正如同 Dave 說的同樣,this 生來局部,並且一直都保持局部態。=>箭頭函數並不會綁定一個 this 變量,它的做用域會如同尋常所作的同樣一層層地去往上查找。
不只僅是this
若是你貿貿然地贊成了「箭頭函數就是常規function的語法糖」這樣的觀點,那是不正確的,由於事實並不是如此——箭頭函數裏並不按常規支持 var self = this 或者 .bind(this) 這樣的糖果。
那些錯誤的解釋都是典型的「給對了答案卻講錯了緣由」,就像你在高中代數課的測試上明明寫對了答案,但老師仍會畫圈圈告訴你用錯方法了——如何解得答案纔是最重要的!
另外,關於「=>箭頭函數不綁定自身的 this,而容許局部做用域的方案來沿襲處理之」的正確描述,也解釋了箭頭函數的另外一個狀況——它們在函數內部不走尋常路的孩子不只僅是 this。
事實上 =>箭頭函數並不綁定 this,arguments,super(ES6),抑或 new.target(ES6)。
這是真的,對於上述的四個(將來可能有更多)地方,箭頭函數不會綁定那些局部變量,全部涉及它們的引用,都會沿襲向上查找外層做用域鏈的方案來處理。
思考下這段代碼:
function foo() { setTimeout( () => { console.log("args:", arguments); },100); } foo( 2, 4, 6, 8 ); // args: [2, 4, 6, 8]
這段代碼中,=>箭頭函數並無綁定 arguments,因此它會以 foo() 的 arguments 來取而代之,而 super 和 new.target 也是同樣的狀況。
總結
不要不經思考就輕易接受那些不許確的答案,不用知足於那些經過錯誤形式獲取到的正確答案。
這關係到了事物是怎樣做業的,以及你使用了怎樣的心智模型(mental model),你會使用這種心智模型去分析、描述和調試其它的行爲,若是你在一開始的時候就偏離了軌道,那麼在以後你也只會一直停留在錯誤的軌道上。
我後悔當初沒有更仔細地聆聽 Dave 的觀點,也好但願當初本身木有發表過關於=>箭頭函數的錯誤言論。我會在從此思考、提筆、傳道JS的時候更加嚴格地確保其正確性,也會讓本身更加當心謹慎。