ES6系列之箭頭函式全解析

JackySummer發表於2020-01-07

引言

ES6中允許使用箭頭=>來定義箭頭函式,是ES6中較受歡迎也較常使用的新增特性。本文將從箭頭函式的基本語法,與普通函式對比,箭頭函式不適用場景三個方面進行梳理。

基本語法

// 箭頭函式
let func = (name) => {
    // 函式體
    return `Hello ${name}`;
};

// 等同於
let func = function (name) {
    // 函式體
    return `Hello ${name}`;
};   
複製程式碼

從上面可以看出,定義箭頭函式語法上要比普通函式簡潔得多。箭頭函式省去了function關鍵字,採用箭頭=>來定義函式。函式的引數放在=>前面的括號中,函式體跟在=>後的花括號中,箭頭函式在引數和箭頭之間不能換行。

箭頭函式的引數

  1. 如果箭頭函式沒有引數,直接寫一個空括號即可。
  2. 如果箭頭函式的引數只有一個,可以省略包裹引數的括號。
  3. 如果箭頭函式有多個引數,將引數依次用逗號分隔,引數必須被包裹在括號中。

箭頭函式的函式體

如果箭頭函式的函式體只有一句程式碼,即返回某個變數或者返回一個簡單的JS表示式,可以省去函式體的大括號{ }。

let func = val => val;
// 等同於
let func = function (val) { return val };

let sum = (num1, num2) => num1 + num2;
// 等同於
let sum = function(num1, num2) {
  return num1 + num2;
};

let mulFunction = (num1, num2 ,num3) => num1 * num2 * num3;
// 等同於
let mulFunction = function(num1, num2 ,num3) {
    return num1 * num2 * num3;
}
複製程式碼

箭頭函式返回一個物件

如果箭頭函式的函式體只有一句程式碼且返回一個物件(物件字面量)時,直接寫一個表示式是不行的。

let func = () => { foo: 1 }; 
console.log(func()); // 執行後返回undefined

// 如果是這樣還會直接報錯
let func = () => { foo: 1, bar: 2 };
複製程式碼

原因是花括號被解釋為函式體的大括號,解決辦法:用圓括號把物件字面量包起來

let func = () => ({ foo: 1 });
console.log(func()); // {foo: 1}

// 不過上面那樣解決的缺點是可讀性變差了,所以更推薦直接當成多條語句的形式來寫,可讀性高  
let func = () => {
    return {
        foo: 1
    }
}
複製程式碼

簡化回撥函式

這是箭頭函式比較常見的用法

// 普通函式寫法
[1, 2, 3, 4].map(function (x) {
    return x * x;
});

let result = [5, 4, 1, 3, 2].sort(function (a, b) {
    return a - b;
});

// 箭頭函式寫法
[1, 2, 3, 4].map(x => x * x);

let result = [5, 4, 1, 3, 2].sort((a, b) => a - b);
複製程式碼

跟普通函式的區別

1.沒有this繫結

箭頭函式沒有自己的this,它會捕獲自己在定義時)所處的外層執行環境的this,並繼承這個this值。所以,箭頭函式中this的指向在它被定義的時候就已經確定了,之後永遠不會改變。

const obj = {
	a: function() { console.log(this) }    
}
obj.a();  // 列印結果:obj物件

const obj = {
	a:() => {
        console.log(this);
    }    
}
obj.a();  // 列印結果: Window物件
複製程式碼

上述程式碼中,箭頭函式與外層的this保持一致,最外層的this就是Window物件。

2.沒有arguments

function func1(a, b) {
    console.log(arguments);
}
let func2 = (a, b) => {
    console.log(arguments);
}
func1(1, 2); // Arguments(2) [1, 2, callee: ƒ, Symbol(Symbol.iterator): ƒ]
func2(1, 2); // Uncaught ReferenceError: arguments is not defined
複製程式碼

如果非要列印函式引數,可以在箭頭函式中使用rest引數代替arguments物件

let func2 = (...rest) => {
    console.log(rest); // (2) [1, 2]
}
複製程式碼

3.不能通過 new 關鍵字呼叫

在建構函式中,this指向新建立的物件例項

而箭頭函式沒有 [[Construct]]方法,箭頭函式不可以當作建構函式,如果這樣做會丟擲異常

var Person = (name) => {
    this.name = name;
}

// Uncaught TypeError: Person is not a constructor
var person = new Person('jacky'); 
複製程式碼

箭頭函式在建立時this物件就繫結了,故不會指向物件例項。

4.沒有 new.target

new.target是ES6新引入的屬性,普通函式如果通過new呼叫,new.target會返回該函式的引用。

function Cat() {
    console.log(new.target); 
}
let cat = new Cat(); // ƒ Cat() { console.log(new.target); }
複製程式碼

此屬性主要:用於確定建構函式是否為new呼叫的。

箭頭函式的this指向全域性物件,在箭頭函式中使用箭頭函式會報錯。

// 普通函式
let a = function() {
    console.log(new.target);
}
a(); // undefined

// 箭頭函式
let b = () => {
    console.log(new.target); // 報錯:Uncaught SyntaxError: new.target expression is not allowed here
};
b();
複製程式碼

5.沒有原型

由於不能通過 new 關鍵字呼叫,不能作為建構函式,所以箭頭函式不存在 prototype 這個屬性。

let func = () => {};
console.log(func.prototype) // undefined
複製程式碼

6.沒有 super

箭頭函式沒有原型,故也不能通過 super 來訪問原型的屬性,所以箭頭函式也是沒有 super 的。同this、arguments、new.target 一樣,這些值由外圍最近一層非箭頭函式決定。

7.call/apply/bind方法無法改變箭頭函式中this的指向

call()、apply()、bind()方法的共同特點是可以改變this的指向,用來動態修改函式執行時this的指向。但由於箭頭函式的this定義時就已經確定了且不會改變。所以這三個方法永遠也改變不了箭頭函式this的指向。

var name = 'global name';
var obj = {
    name: 'jacky'
}
// 箭頭函式定義在全域性作用域
let func = () => {
    console.log(this.name);
};

func();     // global name
// this的指向不會改變,永遠指向Window物件,放到到window下的全域性變數
func.call(obj);     // global name
func.apply(obj);    // global name
func.bind(obj)();   // global name
複製程式碼

8.箭頭函式的解析順序相對靠前

雖然箭頭函式中的箭頭不是運算子,但箭頭函式具有與常規函式不同的特殊運算子優先順序解析規則。

let callback;

callback = callback || function() {}; // ok

callback = callback || () => {};      
// SyntaxError:非法箭頭函式屬性

callback = callback || (() => {});    // ok
複製程式碼

9.箭頭函式不支援重名引數

function foo(a, a) {
    console.log(a, arguments); // 2 Arguments(2) [1, 2, callee: ƒ, Symbol(Symbol.iterator): ƒ]
}

var boo = (a, a) => { // 直接報錯:Uncaught SyntaxError: Duplicate parameter name not allowed in this context
    console.log(a);
};
foo(1, 2);
boo(1, 2);
複製程式碼

10.使用 yield 關鍵字

yield 關鍵字通常不能在箭頭函式中使用(除非是巢狀在允許使用的函式內)。因此,箭頭函式不能用作生成器( Generator )。

箭頭函式不適用的場景

1.不應被用在定義物件的方法上

var obj = {
  x: 10,
  b: function() {
    console.log( this.x, this)
  },
  c: () => console.log(this.x, this)
}
obj.b(); // 10  {x: 10, b: ƒ, c: ƒ}

obj.c(); // undefined Window
複製程式碼

因為它內部this的指向原因,當使用obj.c()的時候,我們希望c方法裡面的this指向obj,但是它卻指向了obj所在上下文中的this(即window),違背了我們的需求,所以箭頭函式不適合作為物件的方法。

2.具有動態上下文的回撥函式,也不應使用箭頭函式

var btn = document.getElementById('btn');
btn.addEventListener('click', () => {
  console.log(this);
});
為btn的監聽函式是一個箭頭函式,導致裡面的this就是全域性物件,而不符合我們想操作按鈕本身的需求。如果改成普通函式,this就會動態指向被點選的按鈕物件
複製程式碼

除了前面兩點,剩下的跟上面講的與普通函式的區別重複了,故只作總結不貼程式碼了:

  1. 不應被用在定義物件的方法上
  2. 具有動態上下文的回撥函式,也不應使用箭頭函式
  3. 不能應用在建構函式中
  4. 避免在 prototype 上使用
  5. 避免在需要 arguments 上使用

相關文章