ES6 系列之箭頭函式

冴羽發表於2018-06-04

回顧

我們先來回顧下箭頭函式的基本語法。

ES6 增加了箭頭函式:

let func = value => value;
複製程式碼

相當於:

let func = function (value) {
    return value;
};
複製程式碼

如果需要給函式傳入多個引數:

let func = (value, num) => value * num;
複製程式碼

如果函式的程式碼塊需要多條語句:

let func = (value, num) => {
    return value * num
};
複製程式碼

如果需要直接返回一個物件:

let func = (value, num) => ({total: value * num});
複製程式碼

與變數解構結合:

let func = ({value, num}) => ({total: value * num})

// 使用
var result = func({
    value: 10,
    num: 10
})

console.log(result); // {total: 100}
複製程式碼

很多時候,你可能想不到要這樣用,所以再來舉個例子,比如在 React 與 Immutable 的技術選型中,我們處理一個事件會這樣做:

handleEvent = () => {
  this.setState({
    data: this.state.data.set("key", "value")
  })
};
複製程式碼

其實就可以簡化為:

handleEvent = () => {
  this.setState(({data}) => ({
    data: data.set("key", "value")
  }))
};
複製程式碼

比較

本篇我們重點比較一下箭頭函式與普通函式。

主要區別包括:

1.沒有 this

箭頭函式沒有 this,所以需要通過查詢作用域鏈來確定 this 的值。

這就意味著如果箭頭函式被非箭頭函式包含,this 繫結的就是最近一層非箭頭函式的 this。

模擬一個實際開發中的例子:

我們的需求是點選一個按鈕,改變該按鈕的背景色。

為了方便開發,我們抽離一個 Button 元件,當需要使用的時候,直接:

// 傳入元素 id 值即可繫結該元素點選時改變背景色的事件
new Button("button")
複製程式碼

HTML 程式碼如下:

<button id="button">點選變色</button>
複製程式碼

JavaScript 程式碼如下:

function Button(id) {
    this.element = document.querySelector("#" + id);
    this.bindEvent();
}

Button.prototype.bindEvent = function() {
    this.element.addEventListener("click", this.setBgColor, false);
};

Button.prototype.setBgColor = function() {
    this.element.style.backgroundColor = '#1abc9c'
};

var button = new Button("button");
複製程式碼

看著好像沒有問題,結果卻是報錯 Uncaught TypeError: Cannot read property 'style' of undefined

這是因為當使用 addEventListener() 為一個元素註冊事件的時候,事件函式裡的 this 值是該元素的引用。

所以如果我們在 setBgColor 中 console.log(this),this 指向的是按鈕元素,那 this.element 就是 undefined,報錯自然就理所當然了。

也許你會問,既然 this 都指向了按鈕元素,那我們直接修改 setBgColor 函式為:

Button.prototype.setBgColor = function() {
    this.style.backgroundColor = '#1abc9c'
};
複製程式碼

不就可以解決這個問題了?

確實可以這樣做,但是在實際的開發中,我們可能會在 setBgColor 中還呼叫其他的函式,比如寫成這種:

Button.prototype.setBgColor = function() {
    this.setElementColor();
    this.setOtherElementColor();
};
複製程式碼

所以我們還是希望 setBgColor 中的 this 是指向例項物件的,這樣就可以呼叫其他的函式。

利用 ES5,我們一般會這樣做:

Button.prototype.bindEvent = function() {
    this.element.addEventListener("click", this.setBgColor.bind(this), false);
};
複製程式碼

為避免 addEventListener 的影響,使用 bind 強制繫結 setBgColor() 的 this 為例項物件

使用 ES6,我們可以更好的解決這個問題:

Button.prototype.bindEvent = function() {
    this.element.addEventListener("click", event => this.setBgColor(event), false);
};
複製程式碼

由於箭頭函式沒有 this,所以會向外層查詢 this 的值,即 bindEvent 中的 this,此時 this 指向例項物件,所以可以正確的呼叫 this.setBgColor 方法, 而 this.setBgColor 中的 this 也會正確指向例項物件。

在這裡再額外提一點,就是注意 bindEvent 和 setBgColor 在這裡使用的是普通函式的形式,而非箭頭函式,如果我們改成箭頭函式,會導致函式裡的 this 指向 window 物件 (非嚴格模式下)。

最後,因為箭頭函式沒有 this,所以也不能用 call()、apply()、bind() 這些方法改變 this 的指向,可以看一個例子:

var value = 1;
var result = (() => this.value).bind({value: 2})();
console.log(result); // 1
複製程式碼

2. 沒有 arguments

箭頭函式沒有自己的 arguments 物件,這不一定是件壞事,因為箭頭函式可以訪問外圍函式的 arguments 物件:

function constant() {
    return () => arguments[0]
}

var result = constant(1);
console.log(result()); // 1
複製程式碼

那如果我們就是要訪問箭頭函式的引數呢?

你可以通過命名引數或者 rest 引數的形式訪問引數:

let nums = (...nums) => nums;
複製程式碼

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

JavaScript 函式有兩個內部方法:[[Call]] 和 [[Construct]]。

當通過 new 呼叫函式時,執行 [[Construct]] 方法,建立一個例項物件,然後再執行函式體,將 this 繫結到例項上。

當直接呼叫的時候,執行 [[Call]] 方法,直接執行函式體。

箭頭函式並沒有 [[Construct]] 方法,不能被用作建構函式,如果通過 new 的方式呼叫,會報錯。

var Foo = () => {};
var foo = new Foo(); // TypeError: Foo is not a constructor
複製程式碼

4. 沒有 new.target

因為不能使用 new 呼叫,所以也沒有 new.target 值。

關於 new.target,可以參考 es6.ruanyifeng.com/#docs/class…

5. 沒有原型

由於不能使用 new 呼叫箭頭函式,所以也沒有構建原型的需求,於是箭頭函式也不存在 prototype 這個屬性。

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

6. 沒有 super

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

總結

最後,關於箭頭函式,引用 MDN 的介紹就是:

An arrow function expression has a shorter syntax than a function expression and does not have its own this, arguments, super, or new.target. These function expressions are best suited for non-method functions, and they cannot be used as constructors.

翻譯過來就是:

箭頭函式表示式的語法比函式表示式更短,並且不繫結自己的this,arguments,super或 new.target。這些函式表示式最適合用於非方法函式(non-method functions),並且它們不能用作建構函式。

那麼什麼是 non-method functions 呢?

我們先來看看 method 的定義:

A method is a function which is a property of an object.

物件屬性中的函式就被稱之為 method,那麼 non-mehtod 就是指不被用作物件屬性中的函式了,可是為什麼說箭頭函式更適合 non-method 呢?

讓我們來看一個例子就明白了:

var obj = {
  i: 10,
  b: () => console.log(this.i, this),
  c: function() {
    console.log( this.i, this)
  }
}
obj.b();
// undefined Window
obj.c();
// 10, Object {...}
複製程式碼

自執行函式

自執行函式的形式為:

(function(){
    console.log(1)
})()
複製程式碼

或者

(function(){
    console.log(1)
}())
複製程式碼

利用箭頭簡化自執行函式的寫法:

(() => {
    console.log(1)
})()
複製程式碼

但是注意:使用以下這種寫法卻會報錯:

(() => {
    console.log(1)
}())
複製程式碼

為什麼會報錯呢?嘿嘿,如果你知道,可以告訴我~

ES6 系列

ES6 系列目錄地址:github.com/mqyqingfeng…

ES6 系列預計寫二十篇左右,旨在加深 ES6 部分知識點的理解,重點講解塊級作用域、標籤模板、箭頭函式、Symbol、Set、Map 以及 Promise 的模擬實現、模組載入方案、非同步處理等內容。

如果有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝。如果喜歡或者有所啟發,歡迎 star,對作者也是一種鼓勵。

相關文章