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