轉的es6 =>函數
阿新 • • 發佈:2017-07-05
nts 特性 準則 的人 都是 作業 綁定 事物 ext 原文地址
箭頭函數=>無疑是ES6中最受關註的一個新特性了,通過它可以簡寫 function 函數表達式,你也可以在各種提及箭頭函數的地方看到這樣的觀點——“=> 就是一個新的 function”。
箭頭函數的句法規則甚至早已延伸到各項標準和技術文檔中去了,雖然它早已不稀奇,卻給我們一種剛剛發現的新鮮感。
粉我的人都知道俺因為某些原因不怎麽喜歡 => 的語法,不過別擔心,本文並非講述我為何不喜歡它,如果你對這個觀點感興趣,可以查看我《YDKJS:ES6 & Beyonf》一書的第二章。
我想在這裏理清一下箭頭函數到底對 this 和 arguments 等東東做了些啥,事實上我在之前從未準確解釋過這一點,對此感到有點愧疚,於是乎想洗白下自己。你可以在這裏看到我對於該話題的第一次陳述。
是否局部(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
一個描述 this 行為觀察的常用伎倆是:
復制代碼
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的時候更加嚴格地確保其正確性,也會讓自己更加小心謹慎。
----------------------------------------------以下給出2個例子----------------------------
function a(){
this.a = 1;
setTimeout(()=>{
setTimeout(()=>{
console.log(this.a)
})
})
}
--------------------------------------------------------------------------------------------------
function a(){
this.a = 1;
setTimeout(functiion(){
setTimeout(()=>{
console.log(this.a)
})
})
}
---------------------------------------------------------------------------
這裏要說的是,普通匿名函數也是函數,它也會函數提升的,還有就是沿著作用域鏈往上找,找到this就停止了。。。。。
轉的es6 =>函數