ES6+ 箭頭函數(shù)
1. 前言
在編程中使用最多的就是函數(shù),在 ES5 中是用 function
關(guān)鍵字來定義函數(shù)的,由于歷史原因 function
定義的函數(shù)存在一些問題,如 this
的指向、函數(shù)參數(shù) arguments
等。
ES6 規(guī)定了可以使用 “箭頭” =>
來定義一個函數(shù),語法更加簡潔。它沒有自己的 this
、arguments
、super
或 new.target
,箭頭函數(shù)表達(dá)式更適用于那些本來需要匿名函數(shù)的地方,但它不能用作構(gòu)造函數(shù)。
2. this 指向
在 JavaScript 中,要說讓人最頭疼的知識點中,this 綁定絕對算一個,這是因為 this 的綁定 ‘難以捉摸’,出錯的時候還往往不知道為什么,相當(dāng)反邏輯。下面我們來看一個示例:
var title = "全局標(biāo)題";
var imooc = {
title: "慕課網(wǎng) ES6 Wiki",
getTitle : function(){
console.log(this.title);
}
};
imooc.getTitle(); // 慕課網(wǎng) ES6 Wiki
var bar = imooc.getTitle;
bar(); // 全局標(biāo)題
通過上面的小例子的打印結(jié)果可以看出 this
的問題,說明 this
的指向是不固定的。
這里簡單說明一下 this
的指向,this
指向的是調(diào)用它的對象。例子中的 this
是在 getTitle 的函數(shù)中的,執(zhí)行 imooc.getTitle()
這個方法時,調(diào)用它的對象是 imooc
,所以 this 的指向是 imooc
。
之后把 imooc.getTitle
方法賦給 bar
,這里要注意的是,只是把地址賦值給了 bar
,并沒有調(diào)用。 而 bar
是全局對象 window
下的方法,所以在執(zhí)行 bar
方法時,調(diào)用它的是 Window 對象,所以這里打印的結(jié)果是 window 下的 title——“全局標(biāo)題”。
TIPS: 上面的示例只是簡單的
this
指向問題,還有很多更加復(fù)雜的,在面試中經(jīng)常會被問到,所以還不清楚的同學(xué)可以去研究一下this
的問題。
ES6 為了規(guī)避這樣的問題,提出了箭頭函數(shù)的解決方案,在箭頭函數(shù)中沒有自己的 this
指向,所有的 this 指向都指向它的上一層 this
,這樣規(guī)定就比較容易理解了。下面看使用箭頭函數(shù)下的 this
指向:
var title = "全局標(biāo)題";
var imooc = {
title: "慕課網(wǎng) ES6 Wiki",
getTitle : () => {
console.log(this.title);
}
};
imooc.getTitle(); // 全局標(biāo)題
var bar = imooc.getTitle;
bar(); // 全局標(biāo)題
上面的打印結(jié)果可以看出來,所有的 this
指向都指向了 window 對象下的 title,本身的 imooc 對象下沒有了 this
,它的上一層就是 window。
3. 語法詳解
3.1 基本語法
箭頭函數(shù)的使用很簡單,使用 =>
來定義函數(shù),下面對比 ES5 和 ES6 定義函數(shù)的對比。
// ES5
var sum = function () {
// todo
};
// ES6
var sum = () => {
// todo
}
3.2 有返回值
當(dāng)函數(shù)體內(nèi)有返回值時,ES6 的箭頭函數(shù)可以省略大括號:
var sum = (num1, num2) => num1 + num2;
當(dāng)傳遞的參數(shù)只有一個時,圓括號也可以省略:
var sum = num => num + 10;
下面看個使用 map 求和的例子:
// ES5
[1,2,3].map(function (x) {
return x * x;
});
// 等同于ES6
[1,2,3].map(x => x * x);
對比 ES5 可以看出箭頭函數(shù)的簡潔表達(dá),更加準(zhǔn)確明了。
3.3 返回值是對象
如果函數(shù)體返回對象字面量表達(dá)式,可以省略大括號,使用圓括號的形式包裹對象。
var getimooc = () => ({a: 1, b: 2});
getimooc() // {a: 1, b: 2}
3.4 默認(rèn)參數(shù)
在定義函數(shù)時,往往需要給參數(shù)添加默認(rèn)值,ES6 中可以直接在圓括號中進行賦值。
var sum = (num1, num2 = 2) => num1 + num2;
console.log(sum(1)) // 3
在使用 function
關(guān)鍵字定義函數(shù)時,如果要給傳遞的參數(shù)設(shè)置默認(rèn)參數(shù),只能在函數(shù)體內(nèi)進行賦值操作,ES6 簡化了默認(rèn)參數(shù)的賦值過程。
3.5 剩余參數(shù)
函數(shù)在接收不定參數(shù)時,可以使用剩余運算符把調(diào)用函數(shù)時傳入的參數(shù)聚攏起來成為一個參數(shù)數(shù)組(類似 function
中的 arguments
對象,但 arguments
不是數(shù)組,不能直接使用)。
下面是剩余參數(shù)的例子:
var fun = (param1, param2, ...rest) => {
console.log(param1)
console.log(param2)
console.log(rest)
};
fun(1, 2, 3, 4, 5);
// 1
// 2
// [3, 4, 5]
4. 沒有 this
箭頭函數(shù)不會創(chuàng)建自己的 this
,它只會從自己的作用域鏈的上一層繼承 this
,setTimeout
會改變 this
的指向,看下面的示例:
// 在構(gòu)造函數(shù)中
function Person(){
this.age = 0;
setTimeout(function(){
console.log(this);
}, 1000)
}
var p = new Person(); // Window: {parent: Window, opener: null, top: Window, length: 0, frames: Window, …}
function Person(){
this.age = 0;
setTimeout(() => {
console.log(this);
}, 1000);
}
var p = new Person(); // Person: {age: 0}
第一個例子中的 setTimeout
的回調(diào)函數(shù)使用 function
來定義的,從打印的結(jié)果可以看出 this
的指向是 window
對象也就是全局作用域。而第二個示例中 setTimeout
的回調(diào)函數(shù)使用箭頭函數(shù)來定義,打印的結(jié)果可以看到,this
的指向是 Person
.
一個實例: 定義為一個構(gòu)造函數(shù) Person
,在函數(shù)中定義一個 imooc 對象,使用 function
關(guān)鍵字和箭頭函數(shù)的方式給 imooc 上添加 getValue
方法,最后返回 imooc 對象,這時候我們來觀察 getValue
內(nèi)的 this
指向問題。
function Person(){
var imooc = {};
imooc.num = 10;
imooc.getValue = () => {
console.log(this)
}
return imooc;
}
var p = new Person();
p.getValue()
// person {}
上面的示例中,構(gòu)造函數(shù)中 imooc.getValue
方法使用的是箭頭函數(shù)定義的,所以 getValue
方法不會有 this 的指向,它會根據(jù)作用域鏈向上查找到 Person
構(gòu)造函數(shù),所以這里的 this
的指向是 Person
。
function Person(){
var imooc = {};
imooc.num = 10;
imooc.getValue = function() {
console.log(this)
}
return imooc;
}
var p = new Person();
p.getValue()
// {num: 10, getValue: ?} this指向的是 p 的返回值
上面的示例中,構(gòu)造函數(shù)中 imooc.getValue
方法是使用 function
定義的,所以 getValue
中 this 的指向是動態(tài)的,指向調(diào)用它的那個對象。在 new Person()
時,會返回 imooc 對象賦給實例 ,在使用 p 去調(diào)用 getValue()
時 this
的指向就是 p 實例。
總結(jié): 箭頭函數(shù)的 this 永遠(yuǎn)指向的是父級作用域。
5. 不綁定 arguments
箭頭函數(shù)不綁定 Arguments 對象。所以在使用箭頭函數(shù)定義的函數(shù)體內(nèi)是取不到 arguments
的。
var fun = function() {
console.log(arguments)
};
fun(1,2,3); // Arguments(3) [1, 2, 3, callee: ?, Symbol(Symbol.iterator): ?]
var fun = () => {
console.log(arguments)
};
fun(1,2,3); // Uncaught ReferenceError: arguments is not defined
上面的示例中,對比兩種定義函數(shù)的方法可以明顯的看出,在箭頭函數(shù)中去取 arguments
時會報引用錯誤,沒有定義的 arguments
。
arguments
的主要作用是獲取所有調(diào)用函數(shù)時所需要傳入的參數(shù),在箭頭函數(shù)中使用剩余參數(shù) ...args
,在函數(shù)內(nèi)可以直接使用。
function foo(...args) {
console.log(args)
}
foo(1); // [1]
foo(1, 2, 3); // [1, 2, 3]
6. 其他注意點
6.1 不能用作構(gòu)造器
箭頭函數(shù)不能用作構(gòu)造器,和 new 一起用會拋出錯誤。
var Foo = () => {};
var foo = new Foo(); // TypeError: Foo is not a constructor
6.2 沒有 prototype 屬性
箭頭函數(shù)沒有 prototype 屬性。
var Foo = () => {};
console.log(Foo.prototype); // undefined
6.3 不能使用 yield 命令
yield 關(guān)鍵字通常不能在箭頭函數(shù)中使用,因此箭頭函數(shù)不能用作 Generator 函數(shù)。
7. 小結(jié)
本節(jié)主要講解了 ES6 的箭頭函數(shù),總結(jié)了以下幾點:
- 更短的函數(shù),優(yōu)雅簡潔;
- 箭頭函數(shù)不會創(chuàng)建自己的 this,它只會從自己的作用域鏈的上一層繼承 this;
- 不能綁定 arguments, 只能使用
...args
展開運算來獲取當(dāng)前參數(shù)的數(shù)組。