引言
ES6中允許使用箭頭=>來定義箭頭函式,是ES6中較受歡迎也較常使用的新增特性。本文將從箭頭函式的基本語法,與普通函式對比,箭頭函式不適用場景三個方面進行梳理。
基本語法
// 箭頭函式
let func = (name) => {
// 函式體
return `Hello ${name}`;
};
// 等同於
let func = function (name) {
// 函式體
return `Hello ${name}`;
};
複製程式碼
從上面可以看出,定義箭頭函式語法上要比普通函式簡潔得多。箭頭函式省去了function
關鍵字,採用箭頭=>
來定義函式。函式的引數放在=>
前面的括號中,函式體跟在=>
後的花括號中,箭頭函式在引數和箭頭之間不能換行。
箭頭函式的引數
- 如果箭頭函式沒有引數,直接寫一個空括號即可。
- 如果箭頭函式的引數只有一個,可以省略包裹引數的括號。
- 如果箭頭函式有多個引數,將引數依次用逗號分隔,引數必須被包裹在括號中。
箭頭函式的函式體
如果箭頭函式的函式體只有一句程式碼,即返回某個變數或者返回一個簡單的JS表示式,可以省去函式體的大括號{ }。
let func = val => val;
// 等同於
let func = function (val) { return val };
let sum = (num1, num2) => num1 + num2;
// 等同於
let sum = function(num1, num2) {
return num1 + num2;
};
let mulFunction = (num1, num2 ,num3) => num1 * num2 * num3;
// 等同於
let mulFunction = function(num1, num2 ,num3) {
return num1 * num2 * num3;
}
複製程式碼
箭頭函式返回一個物件
如果箭頭函式的函式體只有一句程式碼且返回一個物件(物件字面量)時,直接寫一個表示式是不行的。
let func = () => { foo: 1 };
console.log(func()); // 執行後返回undefined
// 如果是這樣還會直接報錯
let func = () => { foo: 1, bar: 2 };
複製程式碼
原因是花括號被解釋為函式體的大括號,解決辦法:用圓括號把物件字面量包起來
let func = () => ({ foo: 1 });
console.log(func()); // {foo: 1}
// 不過上面那樣解決的缺點是可讀性變差了,所以更推薦直接當成多條語句的形式來寫,可讀性高
let func = () => {
return {
foo: 1
}
}
複製程式碼
簡化回撥函式
這是箭頭函式比較常見的用法
// 普通函式寫法
[1, 2, 3, 4].map(function (x) {
return x * x;
});
let result = [5, 4, 1, 3, 2].sort(function (a, b) {
return a - b;
});
// 箭頭函式寫法
[1, 2, 3, 4].map(x => x * x);
let result = [5, 4, 1, 3, 2].sort((a, b) => a - b);
複製程式碼
跟普通函式的區別
1.沒有this繫結
箭頭函式沒有自己的this,它會捕獲自己在定義時)所處的外層執行環境的this,並繼承這個this值。所以,箭頭函式中this的指向在它被定義的時候就已經確定了,之後永遠不會改變。
const obj = {
a: function() { console.log(this) }
}
obj.a(); // 列印結果:obj物件
const obj = {
a:() => {
console.log(this);
}
}
obj.a(); // 列印結果: Window物件
複製程式碼
上述程式碼中,箭頭函式與外層的this保持一致,最外層的this就是Window物件。
2.沒有arguments
function func1(a, b) {
console.log(arguments);
}
let func2 = (a, b) => {
console.log(arguments);
}
func1(1, 2); // Arguments(2) [1, 2, callee: ƒ, Symbol(Symbol.iterator): ƒ]
func2(1, 2); // Uncaught ReferenceError: arguments is not defined
複製程式碼
如果非要列印函式引數,可以在箭頭函式中使用rest引數代替arguments物件
let func2 = (...rest) => {
console.log(rest); // (2) [1, 2]
}
複製程式碼
3.不能通過 new 關鍵字呼叫
在建構函式中,this指向新建立的物件例項
而箭頭函式沒有 [[Construct]]方法,箭頭函式不可以當作建構函式,如果這樣做會丟擲異常
var Person = (name) => {
this.name = name;
}
// Uncaught TypeError: Person is not a constructor
var person = new Person('jacky');
複製程式碼
箭頭函式在建立時this物件就繫結了,故不會指向物件例項。
4.沒有 new.target
new.target是ES6新引入的屬性,普通函式如果通過new呼叫,new.target會返回該函式的引用。
function Cat() {
console.log(new.target);
}
let cat = new Cat(); // ƒ Cat() { console.log(new.target); }
複製程式碼
此屬性主要:用於確定建構函式是否為new呼叫的。
箭頭函式的this指向全域性物件,在箭頭函式中使用箭頭函式會報錯。
// 普通函式
let a = function() {
console.log(new.target);
}
a(); // undefined
// 箭頭函式
let b = () => {
console.log(new.target); // 報錯:Uncaught SyntaxError: new.target expression is not allowed here
};
b();
複製程式碼
5.沒有原型
由於不能通過 new 關鍵字呼叫,不能作為建構函式,所以箭頭函式不存在 prototype 這個屬性。
let func = () => {};
console.log(func.prototype) // undefined
複製程式碼
6.沒有 super
箭頭函式沒有原型,故也不能通過 super 來訪問原型的屬性,所以箭頭函式也是沒有 super 的。同this、arguments、new.target 一樣,這些值由外圍最近一層非箭頭函式決定。
7.call/apply/bind方法無法改變箭頭函式中this的指向
call()、apply()、bind()方法的共同特點是可以改變this的指向,用來動態修改函式執行時this的指向。但由於箭頭函式的this定義時就已經確定了且不會改變。所以這三個方法永遠也改變不了箭頭函式this的指向。
var name = 'global name';
var obj = {
name: 'jacky'
}
// 箭頭函式定義在全域性作用域
let func = () => {
console.log(this.name);
};
func(); // global name
// this的指向不會改變,永遠指向Window物件,放到到window下的全域性變數
func.call(obj); // global name
func.apply(obj); // global name
func.bind(obj)(); // global name
複製程式碼
8.箭頭函式的解析順序相對靠前
雖然箭頭函式中的箭頭不是運算子,但箭頭函式具有與常規函式不同的特殊運算子優先順序解析規則。
let callback;
callback = callback || function() {}; // ok
callback = callback || () => {};
// SyntaxError:非法箭頭函式屬性
callback = callback || (() => {}); // ok
複製程式碼
9.箭頭函式不支援重名引數
function foo(a, a) {
console.log(a, arguments); // 2 Arguments(2) [1, 2, callee: ƒ, Symbol(Symbol.iterator): ƒ]
}
var boo = (a, a) => { // 直接報錯:Uncaught SyntaxError: Duplicate parameter name not allowed in this context
console.log(a);
};
foo(1, 2);
boo(1, 2);
複製程式碼
10.使用 yield 關鍵字
yield 關鍵字通常不能在箭頭函式中使用(除非是巢狀在允許使用的函式內)。因此,箭頭函式不能用作生成器( Generator )。
箭頭函式不適用的場景
1.不應被用在定義物件的方法上
var obj = {
x: 10,
b: function() {
console.log( this.x, this)
},
c: () => console.log(this.x, this)
}
obj.b(); // 10 {x: 10, b: ƒ, c: ƒ}
obj.c(); // undefined Window
複製程式碼
因為它內部this的指向原因,當使用obj.c()的時候,我們希望c方法裡面的this指向obj,但是它卻指向了obj所在上下文中的this(即window),違背了我們的需求,所以箭頭函式不適合作為物件的方法。
2.具有動態上下文的回撥函式,也不應使用箭頭函式
var btn = document.getElementById('btn');
btn.addEventListener('click', () => {
console.log(this);
});
為btn的監聽函式是一個箭頭函式,導致裡面的this就是全域性物件,而不符合我們想操作按鈕本身的需求。如果改成普通函式,this就會動態指向被點選的按鈕物件
複製程式碼
除了前面兩點,剩下的跟上面講的與普通函式的區別重複了,故只作總結不貼程式碼了:
- 不應被用在定義物件的方法上
- 具有動態上下文的回撥函式,也不應使用箭頭函式
- 不能應用在建構函式中
- 避免在 prototype 上使用
- 避免在需要 arguments 上使用