新事物也是有兩面性的,箭頭函數有他的便捷有他的優點,但是他也有缺點,他的優點是代碼簡潔,this
提前定義,但他的缺點也是這些,比如代碼太過簡潔,導致不好閱讀,this
提前定義,導致無法使用JS
進行一些ES5
里面看起來非常正常的操作。
本質來說箭頭函數沒有自己的this
,它的this
是派生而來的,根據“詞法作用域”派生而來。
由於箭頭函數在調用時不會生成自身作用域下的this
和arguments
值,其持有外部包含它的函數的this
值,並且在調用的時候就定下來了,不可動態改變,下面我就總結一下什么情況下不該使用箭頭函數。
在對象上定義函數
const test = {
array: [1, 2, 3],
sum: () => {
console.log(this === window); // => true
return this.array.reduce((result, item) => result + item);
}
};
test.sum();
// TypeError: Cannot read property 'reduce' of undefined
原因就是,箭頭函數沒有它自己的this
值,箭頭函數內的this
值繼承自外圍作用域。
對象方法內的this
指向調用這個方法的對象,如果使用箭頭函數,this
和對象方法在調用的時候所處環境的this
值一致。因為 test.sum()
是在全局環境下進行調用,此時this
指向全局。
解決方法也很簡單,使用函數表達式或者方法簡寫(ES6 中已經支持)來定義方法,這樣能確保 this 是在運行時是由包含它的上下文決定的。
const test = {
array: [1, 2, 3],
sum() {
console.log(this === test); // => true
return this.array.reduce((result, item) => result + item);
}
};
test.sum();
// 6
定義原型方法
在對象原型上定義函數也是遵循着一樣的規則
function Person(name) {
this.name = name;
}
Person.prototype.sayName = () => {
console.log(this === window); // => true
return this.name;
};
const cat = new Person('Mew');
cat.sayName(); // => undefined
使用傳統的函數表達式就能解決問題
function Person(name) {
this.name = name;
}
Person.prototype.sayName = function() {
console.log(this === Person); // => true
return this.name;
};
const cat = new Person('Mew');
cat.sayName(); // => Mew
定義事件回調函數
this
是JS
中非常強大的特點,他讓函數可以根據其調用方式動態的改變上下文,然后箭頭函數直接在聲明時就綁定了this
對象,所以不再是動態的。
在客戶端,在DOM
元素上綁定事件監聽函數是非常普遍的行為,在DOM
事件被觸發時,回調函數中的this
指向該DOM
,但是,箭頭函數在聲明的時候就綁定了執行上下文,要動態改變上下文是不可能的,在需要動態上下文的時候它的弊端就凸顯出來:
const button = document.getElementById('myButton');
button.addEventListener('click', () => {
console.log(this === window); // => true
this.innerHTML = 'Clicked button';
});
因為這個回調的箭頭函數是在全局上下文中被定義的,所以他的this
是window
。換句話說就是,箭頭函數預定義的上下文是不能被修改的,這樣 this.innerHTML
就等價於 window.innerHTML
,而后者是沒有任何意義的。
使用函數表達式就可以在運行時動態的改變 this
:
const button = document.getElementById('myButton');
button.addEventListener('click', function() {
console.log(this === button); // => true
this.innerHTML = 'Clicked button';
});
定義構造函數
如果使用箭頭函數會報錯。
顯然,箭頭函數是不能用來做構造函數。
const Message = (text) => {
this.text = text;
};
const helloMessage = new Message('Hello World!');
// Throws "TypeError: Message is not a constructor"
理論上來說也是不能這么做的,因為箭頭函數在創建時this對象就綁定了,更不會指向對象實例。
太簡短的(難以理解)函數
箭頭函數可以讓語句寫的非常的簡潔,但是一個真實的項目,一般由多個開發者共同協作完成,箭頭函數有時候並不會讓人很好的理解:
const multiply = (a, b) => b === undefined ? b => a * b : a * b;
const double = multiply(2);
double(3); // => 6
multiply(2, 3); // => 6
代碼看起來很簡短,但大多數人第一眼看上去可能無法立即搞清楚它干了什么。
這個函數的作用就是當只有一個參數a時,返回接受一個參數b返回a*b的函數,接收兩個參數時直接返回乘積。
為了讓這個函數更好的讓人理解,我們可以為這個箭頭函數加一對花括號,並加上return語句,或者直接使用函數表達式:
function multiply(a, b) {
if (b === undefined) {
return function(b) {
return a * b;
}
}
return a * b;
}
const double = multiply(2);
double(3); // => 6
multiply(2, 3); // => 6
毫無疑問,箭頭函數帶來了很多便利。恰當的使用箭頭函數可以讓我們避免使用早期的.bind()
函數或者需要固定上下文的地方並且讓代碼更加簡潔。
箭頭函數也有一些不便利的地方。我們在需要動態上下文的地方不能使用箭頭函數:定義對象方法、定義原型方法、定義構造函數、定義事件回調函數。在其他情況下,請盡情的使用箭頭函數。