箭頭函數有兩個好處。☕
1.他們比傳統函數表達式簡潔。
const arr = [1, 2, 3]; const squares = arr.map(x => x * x); // 傳統函數表達式: const squares = arr.map(function (x) { return x * x });
2.箭頭函數不會綁定關鍵字this,我們不需要用bind()或者that = this這種方法了
function UiComponent() { const button = document.getElementById('myButton'); button.addEventListener('click', () => { console.log('CLICK'); this.handleClick(); // this不再是button }); }
和this同樣沒有被箭頭函數綁定的參數有
arguments
super
this
new.target
例如:
function foo() { setTimeout( () => { console.log("args:", arguments); },100); } foo( 2, 4, 6, 8 ); // args: [2, 4, 6, 8]
=>箭頭函數並沒有綁定 arguments,所以它會以 foo() 的 arguments 來取而代之,而 super 和 new.target 也是一樣的情況
傳統的函數不好的地方在於有一些非方法的函數,劫持了this
在JavaScript中,傳統函數有3種角色:
1.非方法函數
2.方法
3.構造函數
這三種函數在使用中會有角色沖突,角色2、3的函數含有他們自己的this指向,但是角色1會對this進行劫持
你可以看這段傳統JS代碼:
function Prefixer(pp) { this.prefix = pp; } Prefixer.prototype.prefixArray = function (arr) { // (A) return arr.map(function (x) { // (B) // 無法正常工作: return this.prefix + x; // (C) }); }; var pre = new Prefixer('Hi '); pre.prefixArray(['Joe', 'Alex']);//TypeError: Cannot read property 'prefix' of undefined
在C行,我們希望訪問this.prefix,但是卻無法訪問,因為this在非方法函數里是undefined,所以我們會得到報錯。
在ECMAScript5中,有三種辦法可以繞過這個問題:
方案1 that = this
function Prefixer(prefix) { this.prefix = prefix; } Prefixer.prototype.prefixArray = function (arr) { var that = this; // (A) return arr.map(function (x) { return that.prefix + x; }); }; var pre = new Prefixer('Hi '); pre.prefixArray(['Joe', 'Alex']); //[ 'Hi Joe', 'Hi Alex' ]
方案2 直接指定this的值
一部分數組方法會提供一個額外的參數值來修改this,注意A行
function Prefixer(prefix) { this.prefix = prefix; } Prefixer.prototype.prefixArray = function (arr) { return arr.map(function (x) { return this.prefix + x; }, this); // (A) };
方案3 綁定this
你可以使用bind()方法來告訴函數是誰調用的
function Prefixer(prefix) { this.prefix = prefix; } Prefixer.prototype.prefixArray = function (arr) { return arr.map(function (x) { return this.prefix + x; }.bind(this)); // (A) };
ECMAScript6 方案:箭頭函數
箭頭函數的工作方式很像方案3,但箭頭函數壓根不會修改this的作用域,而且你不需要給正常的函數加綁定。
下面是用了箭頭函數后的代碼:
function Prefixer(prefix) { this.prefix = prefix; } Prefixer.prototype.prefixArray = function (arr) { return arr.map((x) => { return this.prefix + x; }); };
如果完全用ES6寫這段代碼,可以寫成這樣,可以用一個類和一個更節省的方法寫函數:
class Prefixer { constructor(prefix) { this.prefix = prefix; } prefixArray(arr) { return arr.map(x => this.prefix + x); // (A) } }
在A行,我們通過箭頭函數省略了一些字符
- 如果函數只有一個參數(x),那么可以省略括號
- 如果函數體只有一個表達式,那么可以省略return
箭頭函數語法
參數:
() => { ... } // 沒有參數 x => { ... } // 一個參數 (x, y) => { ... } // 多個參數
函數體:
x => { return x * x } // 語句塊 x => x * x // 表達式,和上一行作用相同
如果函數體是一個表達式,會隱式返回結果,對比一下:
const squares = [1, 2, 3].map(function (x) { return x * x }); const squares = [1, 2, 3].map(x => x * x);
單個參數時括號可以省略
只有一個函數參數的時候,才可以省略小括號:
[1,2,3].map(x => 2 * x) //[ 2, 4, 6 ]
如果有多個參數的時候,就必須加上小括號:
[[1,2], [3,4]].map(([a,b]) => a + b) //[ 3, 7 ]
當你使用ES6函數參數默認值的時候也必須加括號:
[1, undefined, 3].map((x='yes') => x) //[1, yes', 3 ]
箭頭函數的優先級比較低
如果你把箭頭函數當成一個運算符的話,它會有比較低的優先級。
所以當箭頭函數和其他運算符相遇時,其他運算符通常會贏。
例如下面示例,箭頭函數和條件表達式挨在一起了:
const f = x => (x % 2) === 0 ? x : 0;
換句話說等同於下面的語句:
const f = x => ((x % 2) === 0 ? x : 0);
如果想提高箭頭函數的優先級,可以使用括號括住函數,讓函數返回結果作為條件:
const f = (x => ((x % 2) === 0)) ? x : 0;
另一方面,當你使用使用typeof這樣的表達式作為函數體時,不需要添加括號
const f = x => typeof x;
關於箭頭函數換行
es6禁止參數定義和箭頭之間的換行符
const func1 = (x, y) // SyntaxError => { return x + y; }; const func2 = (x, y) => // OK { return x + y; }; const func3 = (x, y) => { // OK return x + y; }; const func4 = (x, y) // SyntaxError => x + y; const func5 = (x, y) => // OK x + y;
在參數中換行是可以的:
const func6 = ( // OK x, y ) => { return x + y; };
關於箭頭函數的函數體
函數體內只有一個表達式是可以省略大括號的:
asyncFunc.then(x => console.log(x));
但是聲明必須放在大括號中:
asyncFunc.catch(x => { throw x });
返回對象變量
如果想讓函數返回一個花括號對象,就要加小括號:
const f1 = x => ({ bar: 123 }); f1();//{ bar: 123 }
不然的話,程序會認為這是一個塊級作用域
const f2 = x => { bar: 123 }; > f2() //undefined
立即調用的箭頭函數
還記得立即調用函數表達式(IIFE)嗎? 它們如下所示,用於模擬ECMAScript 5中的塊范圍和值返回塊:
(function () { // open IIFE // inside IIFE })(); // close IIFE
你也可以用箭頭函數模擬:
(() => { return 123 })()
提取方法
<a id="myButton">按鈕</a> <script type="text/javascript" src="js/babel.min.js"></script> <script type="text/babel"> class KK { constructor() { var myButton = document.getElementById('myButton'); myButton.addEventListener('click', event => this.handleEvent(event)); //es5 myButton.addEventListener('click', this.handleEvent.bind(this)); } handleEvent(event) { console.log(event); } } var kk = new KK(); </script>
箭頭函數還可以用於參數指定技巧
比如使用array.filter需要傳入函數,而且需要指定this對象為bs,只能通過額外參數來指定
const as = new Set([1, 2, 3]); const bs = new Set([3, 2, 4]); const intersection = [...as].filter(bs.has, bs);// [2, 3]
可以用箭頭函數優化寫法:
const as = new Set([1, 2, 3]); const bs = new Set([3, 2, 4]); const intersection = [...as].filter(a => bs.has(a)); // [2, 3]
部分求值
以前可以利用bind來產生一個新的函數來求值,但是尷尬的是第一個參數必須要傳入undefined,這變得難以理解:
function add(x, y) { return x + y; } const plus1 = add.bind(undefined, 1); console.log(plus1(2));//3
如果用箭頭函數,就變得容易理解:
const plus1 = y => add(1, y);
箭頭函數和普通函數對比
1.箭頭函數中不會干涉下列關鍵字:super, this, new.target
2.箭頭函數沒有構造函數:常規函數有Construct和函數屬性,而箭頭函數沒有,所以 new (() => {}) 會報錯
除了以上兩點,箭頭函數和普通函數沒有什么區別,typeof和instanceof輸出的結果相同:
typeof (() => {}) //'function' () => {} instanceof Function //true typeof function () {} //'function' function () {} instanceof Function //true
參考資料:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions