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