ES6系列之箭頭函數全解析

引言

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

基本語法

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

// 等同於
let func = function (name) {
    // 函數體
    return `Hello ${name}`;
};

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

箭頭函數的參數

  1. 若是箭頭函數沒有參數,直接寫一個空括號便可。
  2. 若是箭頭函數的參數只有一個,能夠省略包裹參數的括號。
  3. 若是箭頭函數有多個參數,將參數依次用逗號分隔,參數必須被包裹在括號中。

箭頭函數的函數體

若是箭頭函數的函數體只有一句代碼,即返回某個變量或者返回一個簡單的JS表達式,能夠省去函數體的大括號{ }。express

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;
}

箭頭函數返回一個對象

若是箭頭函數的函數體只有一句代碼且返回一個對象(對象字面量)時,直接寫一個表達式是不行的。app

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
    }
}

簡化回調函數

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

// 普通函數寫法
[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的指向在它被定義的時候就已經肯定了,以後永遠不會改變。prototype

const obj = {
    a: function() { console.log(this) }    
}
obj.a();  // 打印結果:obj對象

const obj = {
    a:() => {
        console.log(this);
    }    
}
obj.a();  // 打印結果: Window對象

上述代碼中,箭頭函數與外層的this保持一致,最外層的this就是Window對象。rest

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對象code

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 上使用
相關文章
相關標籤/搜索