箭頭函式
Baseline Widely available
This feature is well established and works across many devices and browser versions. It’s been available across browsers since July 2015.
箭頭函式運算式(arrow function expression)擁有比函式運算式還簡短的語法。它沒有自己的 this
、arguments、super、new.target 等語法。本函式運算式適用於非方法的函式,但不能被用作建構式(constructor)。
嘗試一下
基本語法
(參數1, 參數2, …, 參數N) => { 陳述式; } (參數1, 參數2, …, 參數N) => 表示式; // 等相同(參數1, 參數2, …, 參數N) => { return 表示式; } // 只有一個參數時,括號才能不加: (單一參數) => { 陳述式; } 單一參數 => { 陳述式; } //若無參數,就一定要加括號: () => { statements }
進階語法
// 用大括號將內容括起來,返回一個物件字面值表示法: params => ({foo: bar}) // 支援其餘參數與預設參數 (param1, param2, ...rest) => { statements } (param1 = defaultValue1, param2, …, paramN = defaultValueN) => { statements } // 也支援 parameter list 的解構 var f = ([a, b] = [1, 2], {x: c} = {x: a + b}) => a + b + c; f(); // 6
說明
也可參閱 "ES6 In Depth: Arrow functions" on hacks.mozilla.org。
箭頭函式有兩個重要的特性:更短的函式寫法與 this
變數的非綁定。
更短的函式寫法
var elements = ["Hydrogen", "Helium", "Lithium", "Beryllium"];
// 這段函式會輸出[8, 6, 7, 9]這個陣列
elements.map(function (element) {
return element.length;
});
// 上方這種一般的函式,可以被改寫成下方的箭頭函式
elements.map((element) => {
return element.length;
}); // [8, 6, 7, 9]
// 如果輸入的參數只有一個,我們可以移除掉外面的括號
elements.map((element) => {
return element.length;
}); // [8, 6, 7, 9]
// 當箭頭函式裡的內容只有'return'的時候,我們可以拿掉return和外面的大括號
elements.map((element) => element.length); // [8, 6, 7, 9]
// 在這個範例中,因為我們只需要length這個屬性,所以也可以使用解構賦值:
// 下方的'length'對應到我們想取得的屬性,而'lengthFooBArX'只是很普通的變數名稱,
// 可以被任意修改成你想要的名字
elements.map(({ length: lengthFooBArX }) => lengthFooBArX); // [8, 6, 7, 9]
// 上面這種解構賦值之後的參數也可以被改寫為下面這樣。但要注意的是,在這個範例中,
// 我們不是要指定'length'這個值給一個虛構的屬性,而是這個變數的名稱'length'本身就是
// 用來當成我們想從物件上取得的屬性
elements.map(({ length }) => length); // [8, 6, 7, 9]
this
不分家
在有箭頭函數之前,每個新函式是依據如何被呼叫來定義自己的 this
變數
例如:
- 在建構子時是一個新物件
- 在呼叫嚴格模式函數時是 undefined
- 以物件方法呼叫時則為基礎物件
- 等等....
事實證明這對物件導向程式設計來說並不理想。
function Person() {
// Person() 建構式將 this 定義為它自己的一個實體
this.age = 0;
setInterval(function growUp() {
// 在非嚴格模式下, growUp() 函式把 this 定義為全域物件
// (因為那是 growUp()執行的所在),
// 與 Person() 建構式所定義的 this 有所不同
this.age++;
}, 1000);
}
var p = new Person();
在 ECMAScript 3/5 裡面,有關 this
的問題,可以透過指派 this
值給可以關閉的變數解決。
function Person() {
var self = this; // 有些人喜歡 `that` 而不是 `self`.
// 選好一種取法後始終如一
self.age = 0;
setInterval(function growUp() {
// 這個 callback 參考 `self` 變數,為預期中的物件。
self.age++;
}, 1000);
}
或者透過 bind 函式來綁定 this
變數到指定函式(以上面為例,就是 growUp()
函式)。
箭頭函式並不擁有自己的 this 變
數;
使用的 this 值來自
封閉的文本上下文,也就是說,箭頭函式遵循常規變量查找規則。因此,如果在當前範圍中搜索不到 this 變量時,他們最終會尋找其封閉範圍。
因此,在以下程式碼內,傳遞給 setInterval
的 箭頭函式中的this
,會與封閉函式的 this
值相同:
function Person() {
this.age = 0;
setInterval(() => {
this.age++; // |this| 適切的參考了Person建構式所建立的物件
}, 1000);
}
var p = new Person();
和嚴格模式的關係
由於 this
變數具有詞彙上綁定意義,所以嚴格模式的宣告對 this
的作用將被忽略。
var f = () => {
"use strict";
return this;
};
f() === window; // 或是 global 物件
但嚴格模式的其他作用依舊存在。
由 call 與 apply 函式呼叫
由於箭頭函式並沒有自己的 this
,所以透過 call()
或 apply()
呼叫箭頭函式只能傳入參數。thisArg
將會被忽略。
var adder = {
base: 1,
add: function (a) {
var f = (v) => v + this.base;
return f(a);
},
addThruCall: function (a) {
var f = (v) => v + this.base;
var b = {
base: 2,
};
return f.call(b, a);
},
};
console.log(adder.add(1)); // 顯示 2
console.log(adder.addThruCall(1)); // 依舊顯示 2
不綁定 arguments
箭頭函式並沒有自己的 arguments
物件。所以在此例中,arguments
只是參考到 enclosing 作用域裡面的相同變數:
var arguments = [1, 2, 3];
var arr = () => arguments[0];
arr(); // 1
function foo(n) {
var f = () => arguments[0] + n; // foo's implicit arguments binding. arguments[0] is n
return f();
}
foo(1); // 2
大多時候,使用其餘參數 是取代 arguments
物件的較好方式。
function foo(n) {
var f = (...args) => args[0] + n;
return f(10);
}
foo(1); // 11
將箭頭函式撰寫為方法
如同前述,箭頭函式運算式最適合用在非方法的函式。來看看如果要把它們當成方法來用,會發生什麼事:
"use strict";
var obj = {
i: 10,
b: () => console.log(this.i, this),
c: function () {
console.log(this.i, this);
},
};
obj.b(); // 印出 undefined, Window {...} (or the global object)
obj.c(); // 印出 10, Object {...}
箭頭函式並沒有自己的 this
。另一個例子與 Object.defineProperty()
有關:
"use strict";
var obj = {
a: 10,
};
Object.defineProperty(obj, "b", {
get: () => {
console.log(this.a, typeof this.a, this); // undefined, 'undefined', Window {...} (or the global object)
return this.a + 10; // represents global object 'Window', therefore 'this.a' returns 'undefined'
},
});
使用 new
運算子
箭頭函式不可作為建構式使用;若使用於建構式,會在使用 new
時候拋出錯誤。
var Foo = () => {};
var foo = new Foo(); // TypeError: Foo is not a constructor
使用 prototype
屬性
箭頭函式並沒有原型(prototype
)屬性。
var Foo = () => {};
console.log(Foo.prototype); // undefined
使用關鍵字 yield
yield
關鍵字無法用於箭頭函式的 body(except when permitted within functions further nested within it)。因此,箭頭函式無法使用 generator。
函式主體(Function body)
箭頭函式可以變成 concise body 或者平常使用的 block body。
在 concise body 裡面只需要輸入運算式,就會附上內建的回傳。在 block body 裡面就必須附上明確的 return
宣告。
var func = (x) => x * x; // concise 語法會內建 "return"
var func = (x, y) => {
return x + y;
}; // block body 需要明確的 "return"
回傳物件字面值
請注意只使用 params => {object:literal}
並不會按照期望般回傳物件字面值(object literal)。
var func = () => { foo: 1 }; // Calling func() returns undefined!
var func = () => { foo: function() {} }; // SyntaxError: Unexpected token (
因為在大括弧({}
)裡面的文字會被解析為有序宣告(例如 foo
會被當作標記(label)、而不是物件的 key )
要記得把物件字面值包在圓括弧內。
var func = () => ({ foo: 1 });
var func = () => ({ foo: function () {} });
換行
箭頭函式不可以在參數及箭頭間包含換行。
var func = ()
=> 1; // SyntaxError: expected expression, got '=>'
Parsing order
箭頭函式的箭頭儘管不是操作符,但藉著運算子優先等級,箭頭函式有著和普通函式不相同的特殊解析規則。
let callback;
callback = callback || function() {}; // ok
callback = callback || () => {}; // SyntaxError: invalid arrow-function arguments
callback = callback || (() => {}); // ok
更多範例
// 回傳 undefined 的箭頭函式
let empty = () => {};
(() => "foobar")(); // 回傳 "foobar"
var simple = (a) => (a > 15 ? 15 : a);
simple(16); // 15
simple(10); // 10
let max = (a, b) => (a > b ? a : b);
// Easy array filtering, mapping, ...
var arr = [5, 6, 13, 0, 1, 18, 23];
var sum = arr.reduce((a, b) => a + b); // 66
var even = arr.filter((v) => v % 2 == 0); // [6, 0, 18]
var double = arr.map((v) => v * 2); // [10, 12, 26, 0, 2, 36, 46]
規範
Specification |
---|
ECMAScript Language Specification # sec-arrow-function-definitions |
瀏覽器相容性
BCD tables only load in the browser