
箭頭函數(shù)是ES6的API,相信很多人都知道,因為其語法上相對于普通函數(shù)更簡潔,深受大家的喜愛。就是這種我們?nèi)粘i_發(fā)中一直在使用的API,大部分同學(xué)卻對它的了解程度還是不夠深...
普通函數(shù)和箭頭函數(shù)的區(qū)別:
箭頭函數(shù)的this指向規(guī)則:
1. 箭頭函數(shù)沒有prototype(原型),所以箭頭函數(shù)本身沒有this
let a = () =>{};
console.log(a.prototype); // undefined
2. 箭頭函數(shù)的this指向在定義的時候繼承自外層第一個普通函數(shù)的this。
下面栗子中在一個函數(shù)中定義箭頭函數(shù),然后在另一個函數(shù)中執(zhí)行箭頭函數(shù)。
let a,
barObj = { msg: 'bar的this指向' };
fooObj = { msg: 'foo的this指向' };
bar.call(barObj); // 將bar的this指向barObj
foo.call(fooObj); // 將foo的this指向fooObj
function foo() {
a(); // 結(jié)果:{ msg: 'bar的this指向' }
}
function bar() {
a = () => {
console.log(this, 'this指向定義的時候外層第一個普通函數(shù)'); //
}; // 在bar中定義 this繼承于bar函數(shù)的this指向
}
從上面栗子中可以得出兩點
- 箭頭函數(shù)的this指向定義時所在的外層第一個普通函數(shù),跟使用位置沒有關(guān)系。
- 被繼承的普通函數(shù)的this指向改變,箭頭函數(shù)的this指向會跟著改變
3. 不能直接修改箭頭函數(shù)的this指向
上個栗子中的foo函數(shù)修改一下,嘗試直接修改箭頭函數(shù)的this指向。
let fnObj = { msg: '嘗試直接修改箭頭函數(shù)的this指向' };
function foo() {
a.call(fnObj); // 結(jié)果:{ msg: 'bar的this指向' }
}
很明顯,call顯示綁定this指向失敗了,包括aaply、bind都一樣。
它們(call、aaply、bind)會默認(rèn)忽略第一個參數(shù),但是可以正常傳參。
然后我又通過隱式綁定來嘗試同樣也失敗了,new 調(diào)用會報錯,這個稍后再說。
SO,箭頭函數(shù)不能直接修改它的this指向。
幸運的是,我們可以通過間接的形式來修改箭頭函數(shù)的指向:
去修改被繼承的普通函數(shù)的this指向,然后箭頭函數(shù)的this指向也會跟著改變,這在上一個栗子中有演示。
bar.call(barObj); // 將bar普通函數(shù)的this指向barObj 然后內(nèi)部的箭頭函數(shù)也會指向barObj
4. 箭頭函數(shù)外層沒有普通函數(shù),嚴(yán)格模式和非嚴(yán)格模式下它的this都會指向window(全局對象)
唔,這個問題實際上是面試官提出來的,當(dāng)時我認(rèn)為的箭頭函數(shù)規(guī)則就是:箭頭函數(shù)的this指向繼承自外層第一個普通函數(shù)的this,現(xiàn)在看來真是不嚴(yán)謹(jǐn)(少說一個定義的時候),要是面試官問我:定義和執(zhí)行不在同一個普通函數(shù)中,它又指向哪里,肯定歇菜...
既然箭頭函數(shù)的this指向在定義的時候繼承自外層第一個普通函數(shù)的this,那么:
當(dāng)箭頭函數(shù)外層沒有普通函數(shù),它的this會指向哪里?
這里跟我之前寫的this綁定規(guī)則不太一樣(不懂的可以點進(jìn)去看一下),普通函數(shù)的默認(rèn)綁定規(guī)則是:
在非嚴(yán)格模式下,默認(rèn)綁定的this指向全局對象,嚴(yán)格模式下this指向undefined
如果箭頭函數(shù)外層沒有普通函數(shù)繼承,它this指向的規(guī)則:
經(jīng)過測試,箭頭函數(shù)在全局作用域下,嚴(yán)格模式和非嚴(yán)格模式下它的this都會指向window(全局對象)。
Tip:測試的時候發(fā)現(xiàn)嚴(yán)格模式在中途聲明無效,必須在全局/函數(shù)的開頭聲明才會生效:
a = 1;
'use strict'; // 嚴(yán)格模式無效 必須在一開始就聲明嚴(yán)格模式
b = 2; // 不報錯
箭頭函數(shù)的
箭頭函數(shù)的arguments
箭頭函數(shù)的this指向全局,使用arguments會報未聲明的錯誤
如果箭頭函數(shù)的this指向window(全局對象)使用arguments會報錯,未聲明arguments。
let b = () => {
console.log(arguments);
};
b(1, 2, 3, 4); // Uncaught ReferenceError: arguments is not defined
PS:如果你聲明了一個全局變量為arguments,那就不會報錯了,但是你為什么要這么做呢?
箭頭函數(shù)的this指向普通函數(shù)時,它的argumens繼承于該普通函數(shù)
上面是第一種情況:箭頭函數(shù)的this指向全局對象,會報arguments未聲明的錯誤。
第二種情況是:箭頭函數(shù)的this如果指向普通函數(shù),它的argumens繼承于該普通函數(shù)。
function bar() {
console.log(arguments); // ['外層第二個普通函數(shù)的參數(shù)']
bb('外層第一個普通函數(shù)的參數(shù)');
function bb() {
console.log(arguments); // ["外層第一個普通函數(shù)的參數(shù)"]
let a = () => {
console.log(arguments, 'arguments繼承this指向的那個普通函數(shù)'); // ["外層第一個普通函數(shù)的參數(shù)"]
};
a('箭頭函數(shù)的參數(shù)'); // this指向bb
}
}
bar('外層第二個普通函數(shù)的參數(shù)');
那么應(yīng)該如何來獲取箭頭函數(shù)不定數(shù)量的參數(shù)呢?答案是:ES6的rest參數(shù)(...擴(kuò)展符)
rest參數(shù)獲取函數(shù)的多余參數(shù)
這是ES6的API,用于獲取函數(shù)不定數(shù)量的參數(shù)數(shù)組,這個API是用來替代arguments的,API用法如下:
let a = (first, ...abc) => {
console.log(first, abc); // 1 [2, 3, 4]
};
a(1, 2, 3, 4);
上面的栗子展示了,獲取函數(shù)除第一個確定的參數(shù),以及用一個變量接收其他剩余參數(shù)的示例。
也可以直接接收函數(shù)的所有參數(shù),rest參數(shù)的用法相對于arguments的優(yōu)點:
箭頭函數(shù)和普通函數(shù)都可以使用。
更加靈活,接收參數(shù)的數(shù)量完全自定義。
-
可讀性更好
參數(shù)都是在函數(shù)括號中定義的,不會突然出現(xiàn)一個
arguments,以前剛見到的時候,真的好奇怪了! -
rest是一個真正的數(shù)組,可以使用數(shù)組的API。
因為
arguments是一個類數(shù)組的對象,有些人以為它是真正的數(shù)組,所以會出現(xiàn)以下場景:arguments.push(0); // arguments.push is not a function如上,如果我們需要使用數(shù)組的API,需要使用擴(kuò)展符/Array.from來將它轉(zhuǎn)換成真正的數(shù)組:
arguments = [...arguments]; 或者 :arguments = Array.from(arguments);
rest參數(shù)有兩點需要注意:
-
rest必須是函數(shù)的最后一位參數(shù):
let a = (first, ...rest, three) => { console.log(first, rest,three); // 報錯:Rest parameter must be last formal parameter }; a(1, 2, 3, 4); -
函數(shù)的length屬性,不包括 rest 參數(shù)
(function(...a) {}).length // 0 (function(a, ...b) {}).length // 1
擴(kuò)展運算符還可以用于數(shù)組,這里是阮一峰老師的文檔
PS:感覺這里寫多了,但比較喜歡把一個知識點講清楚...
使用new調(diào)用箭頭函數(shù)會報錯
無論箭頭函數(shù)的thsi指向哪里,使用new調(diào)用箭頭函數(shù)都會報錯,因為箭頭函數(shù)沒有constructor
let a = () => {};
let b = new a(); // a is not a constructor
箭頭函數(shù)不支持new.target:
new.target是ES6新引入的屬性,普通函數(shù)如果通過new調(diào)用,new.target會返回該函數(shù)的引用。
此屬性主要:用于確定構(gòu)造函數(shù)是否為new調(diào)用的。
-
箭頭函數(shù)的this指向全局對象,在箭頭函數(shù)中使用箭頭函數(shù)會報錯
let a = () => { console.log(new.target); // 報錯:new.target 不允許在這里使用 }; a(); -
箭頭函數(shù)的this指向普通函數(shù),它的new.target就是指向該普通函數(shù)的引用。
new bb(); function bb() { let a = () => { console.log(new.target); // 指向函數(shù)bb:function bb(){...} }; a(); }
更多關(guān)于new.target可以看一下阮一峰老師關(guān)于這部分的解釋。
箭頭函數(shù)不支持重命名函數(shù)參數(shù),普通函數(shù)的函數(shù)參數(shù)支持重命名
如下示例,普通函數(shù)的函數(shù)參數(shù)支持重命名,后面出現(xiàn)的會覆蓋前面的,箭頭函數(shù)會拋出錯誤:
function func1(a, a) {
console.log(a, arguments); // 2 [1,2]
}
var func2 = (a,a) => {
console.log(a); // 報錯:在此上下文中不允許重復(fù)參數(shù)名稱
};
func1(1, 2); func2(1, 2);
箭頭函數(shù)相對于普通函數(shù)語法更簡潔優(yōu)雅:
講道理,語法上的不同,也屬與它們兩個的區(qū)別!
箭頭函數(shù)都是匿名函數(shù),并且都不用寫
function-
只有一個參數(shù)的時候可以省略括號:
var f = a => a; // 傳入a 返回a -
函數(shù)只有一條語句時可以省略
{}和returnvar f = (a,b,c) => a; // 傳入a,b,c 返回a 簡化回調(diào)函數(shù),讓你的回調(diào)函數(shù)更優(yōu)雅:
[1,2,3].map(function (x) {
return x * x;
}); // 普通函數(shù)寫法
[1,2,3].map(x => x * x); // 箭頭函數(shù)只需要一行
箭頭函數(shù)的注意事項及不適用場景
箭頭函數(shù)的注意事項
-
一條語句返回對象字面量,需要加括號,或者直接寫成多條語句的
return形式,否則像func中演示的一樣,花括號會被解析為多條語句的花括號,不能正確解析
var func1 = () => { foo: 1 }; // 想返回一個對象,花括號被當(dāng)成多條語句來解析,執(zhí)行后返回undefined
var func2 = () => ({foo: 1}); // 用圓括號是正確的寫法
var func2 = () => {
return {
foo: 1 // 更推薦直接當(dāng)成多條語句的形式來寫,可讀性高
};
};
- 箭頭函數(shù)在參數(shù)和箭頭之間不能換行!
var func = ()
=> 1; // 報錯: Unexpected token =>
- 箭頭函數(shù)的解析順序相對靠前
MDN: 雖然箭頭函數(shù)中的箭頭不是運算符,但箭頭函數(shù)具有與常規(guī)函數(shù)不同的特殊運算符優(yōu)先級解析規(guī)則
let a = false || function() {}; // ok
let b = false || () => {}; // Malformed arrow function parameter list
let c = false || (() => {}); // ok
箭頭函數(shù)不適用場景:
圍繞兩點:箭頭函數(shù)的this意外指向和代碼的可讀性。
- 定義字面量方法,this的意外指向。
因為箭頭函數(shù)的簡潔
const obj = {
array: [1, 2, 3],
sum: () => {
// 根據(jù)上文學(xué)到的:外層沒有普通函數(shù)this會指向全局對象
return this.array.push('全局對象下沒有array,這里會報錯'); // 找不到push方法
}
};
obj.sum();
上述栗子使用普通函數(shù)或者ES6中的方法簡寫的來定義方法,就沒有問題了:
// 這兩種寫法是等價的
sum() {
return this.array.push('this指向obj');
}
sum: function() {
return this.array.push('this指向obj');
}
還有一種情況是給普通函數(shù)的原型定義方法的時候,通常會在普通函數(shù)的外部進(jìn)行定義,比如說繼承/添加方法的時候。
這時候因為沒有在普通函數(shù)的內(nèi)部進(jìn)行定義,所以this會指向其他普通函數(shù),或者全局對象上,導(dǎo)致bug!
- 回調(diào)函數(shù)的動態(tài)this
下文是一個修改dom文本的操作,因為this指向錯誤,導(dǎo)致修改失敗:
const button = document.getElementById('myButton');
button.addEventListener('click', () => {
this.innerHTML = 'Clicked button'; // this又指向了全局
});
相信你也知道了,改成普通函數(shù)就成了。
-
考慮代碼的可讀性,使用普通函數(shù)
-
函數(shù)體復(fù)雜:
具體表現(xiàn)就是箭頭函數(shù)中使用多個三元運算符號,就是不換行,非要在一行內(nèi)寫完,非常惡心!
行數(shù)較多
函數(shù)內(nèi)部有大量操作
-
文章內(nèi)容小結(jié):
普通函數(shù)和箭頭函數(shù)的區(qū)別:
- 箭頭函數(shù)沒有
prototype(原型),所以箭頭函數(shù)本身沒有this - 箭頭函數(shù)的this在定義的時候繼承自外層第一個普通函數(shù)的this。
- 如果箭頭函數(shù)外層沒有普通函數(shù),嚴(yán)格模式和非嚴(yán)格模式下它的this都會指向
window(全局對象) - 箭頭函數(shù)本身的this指向不能改變,但可以修改它要繼承的對象的this。
- 箭頭函數(shù)的this指向全局,使用arguments會報未聲明的錯誤。
- 箭頭函數(shù)的this指向普通函數(shù)時,它的
argumens繼承于該普通函數(shù) - 使用
new調(diào)用箭頭函數(shù)會報錯,因為箭頭函數(shù)沒有constructor - 箭頭函數(shù)不支持
new.target - 箭頭函數(shù)不支持重命名函數(shù)參數(shù),普通函數(shù)的函數(shù)參數(shù)支持重命名
- 箭頭函數(shù)相對于普通函數(shù)語法更簡潔優(yōu)雅
箭頭函數(shù)的注意事項及不適用場景
箭頭函數(shù)的注意事項:
- 箭頭函數(shù)一條語句返回對象字面量,需要加括號
- 箭頭函數(shù)在參數(shù)和箭頭之間不能換行
- 箭頭函數(shù)的解析順序相對
||靠前
不適用場景:箭頭函數(shù)的this意外指向和代碼的可讀性。
結(jié)語
嘔心瀝血,可以說是很全了,反正第一次問到我的時候只能想到箭頭函數(shù)的this是繼承而來的,以及語法上的簡潔性,其他的我都不知道,希望這篇文章能夠幫助各位同學(xué)學(xué)到知識。
PS:目前找工作中,求大佬們內(nèi)推,中高級前端,偏JS,Vue,上海楊浦。
博客、前端積累文檔、公眾號、GitHub、wx:OBkoro1、郵箱:obkoro1@foxmail.com
以上2019.03.22
參考資料: