ES6 新特性之箭頭函數

「箭頭函數」是 ECMAScript6 中很是重要的性特性。不少文章都在描述它的上下文透明性以及短語法。新特性必然會帶來不少好處,但凡事都有兩面性。javascript

本篇文章會經過情景引導,讓你知曉哪些情景下應該繞過箭頭函數,哪些情景下使用短語法讓代碼更加精煉。java

場景一:在對象上定義方法

在 JavaScript 中,方法做爲一個函數存儲爲對象的一個屬性。當調用方法時,this 指向該方法的從屬對象。編程

對象字面量

箭頭函數簡短的書寫風格很是吸引人用它來定義方法。讓咱們試試看:數組

var calculate = {
    array: [1, 2, 3],
    sum: () => {
        console.log(this === window); // true
        return this.array.reduce((result, item) => result + item);
    }
}; 

calculate.sum(); // Throws "TypeError: Cannot read property 'reduce' of undefined"

calculate.sum() 方法是經過箭頭函數定義的。可是在調用 calculate.sum() 的時候拋出了TypeError 異常,緣由是 this.array 的值是 undefined瀏覽器

當調用 calculate.sum() 方法時,this 不是指向 calculate 實例,而是指向 window 對象。那麼執行 this.array 等同於 window.array,array 是 calculate 對象的屬性,故 window.arrayundefined閉包

解決方案是使用函數表達式或者方法定義的短語法(ECMAScript6可用)。在這種狀況下 this 決定於調用的對象,而不是緊鄰上下文。 讓咱們看看修正的版本:app

var calculate = {
    array: [1, 2, 3],
    sum() {
        console.log(this === calculate); // true
        return this.array.reduce((result, item) => result + item);
    }
};

calculate.sum(); // 6

由於 sum 是一個普通函數,調用 calculate.sum() 時 this 指向 calculate 對象。 this.array 是數組的引用,所以元素之和計算正確,結果是: 6.函數

對象原型

相同的規則也適用於在 prototype 對象上定義方法。this

用箭頭函數來定義 sayCatName 方法,會帶來一個不正確的上下文 window:prototype

function MyCat(name) {
    this.catName = name;
}

MyCat.prototype.sayCatName = () => {
    console.log(this === window); // true
    return this.catName;
};

var cat = new MyCat('Mew');
cat.sayCatName(); // undefined

使用函數表達式:

function MyCat(name) {
    this.catName = name;
}

MyCat.prototype.sayCatName = function() {
    console.log(this === cat); // true
    return this.catName;
};

var cat = new MyCat('Mew');
cat.sayCatName(); // 'Mew'

sayCatName 普通函數被當作方法調用的時候 cat.sayCatName() 會把上下文改變爲 cat 對象。

場景二:結合動態上下文的回調函數

this 在 JavaScript 中是一個很強大的特性。它容許利用函數調用的方式改變上下文。一般來講,上下文是一個調用發生時候的目標對象,讓代碼更加天然化。這就好像 「某些事情正發生在該對象上」。

不管如何,箭頭函數在聲明的時候都會綁定靜態的上下文,而不會是動態的。這是詞素 this 不是很必要的一種狀況。

給 DOM 元素裝配事件監聽器是客戶端編程的一個一般的任務。一個事件用 this 做爲目標元素去觸發處理函數。這是一個動態上下文的簡便用法。

接下來的例子試圖使用一個箭頭函數觸發一個處理函數:

var button = document.getElementById('myButton');
button.addEventListener('click', () => {
    console.log(this === window); // true
    this.innerHTML = 'Clicked button';
});

this 在箭頭函數中是 window,也就是被定義爲全局上下文(譯者注:這裏應該描述的就是上文的例子)。當一個點擊事件發生的時候,瀏覽器試着用 button 上下文去調用處理函數,可是箭頭函數並不會改變它已經預約義的上下文。

this.innerHTML 等價於 window.innerHTML ,並無什麼意義。

你不得不該用一個函數表達式,去容許目標元素改變其上下文。

var button = document.getElementById('myButton');  

button.addEventListener('click', function() {  
  console.log(this === button); // => true
  this.innerHTML = 'Clicked button';
});

當用戶點擊該按鈕,this 在處理函數中是 button。 從而 this.innerHTML = 'Clicked button' 正確地修改了按鈕的文本去反映點擊狀態。

場景三:調用構造器

this 在一個構造調用過程當中是一個新建立的對象。 當執行 new MyFunction(),該構造器的上下文 MyFunction 是一個新的對象: this instanceof MyFunction === true

注意一個箭頭函數不能做爲構造器。 JavaScript 會經過拋出異常的方式進行隱式地預防。

不管怎樣,this 仍是會從緊鄰上下文中獲取,而不是那個新建立的對象。 換句話說,一個箭頭函數構造器的調用過程沒有什麼意義,反而會產生歧義。

讓咱們看看,若是試圖去嘗試使用箭頭函數做爲構造器,會發生什麼:

var Message = (text) => {  
  this.text = text;
};

// Throws "TypeError: Message is not a constructor"
var helloMessage = new Message('Hello World!');

執行 new Message('Hello World!'), Message 是一個箭頭函數, JavaScript 拋出了一個 TypeError ,這意味着 Message 不能被用做於構造器。

與一些以前特定版本的 JavaScript 靜默失敗相比,我認爲 ECMAScript 6 在這些狀況下提供含有錯誤信息的失敗會更加高效。

上面的例子可使用一個函數表達式來修正,這纔是建立構造器正確的方式 (包括函數聲明):

var Message = function(text) {  
  this.text = text;
};

var helloMessage = new Message('Hello World!');  
console.log(helloMessage.text); // => 'Hello World!'

場景四:最短語法

箭頭函數有一個很是棒的屬性,若是函數體只有一條語句的話,能夠省略參數的括號 () ,代碼塊的花括號 {} 以及 return (譯者注:此處省略參數的括號,與函數體只有一條語句不要緊)。這對寫特別短的函數頗有幫助。

單個入參時可省略(),箭頭函數代碼塊部分只有單條語句返回時,可省略{}return

var f = v => v;
// 等同於
var f = function(v) {
    return v;
}

var f = () => 5;
// 等同於
var f = function() {
    return 5;
}

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

// 返回值類型爲object,須要使用{}包裹
var getTempItem = id => ({id: id, name: 'Temp'});

箭頭函數配合變量解構使用。

const full = ({ first, last }) => first + ' ' + last;
// 等同於
function full(person) {
    return person.first + ' ' + person.last;
}

在日常的工做中應用程序的代碼是會被許多其餘的開發者進行閱讀的。最短語法不太適合去幫助你的同事快速理解函數的意義。在某些程度上來講,壓縮的函數會變得閱讀困難,因此最好別走入憤怒的深淵。讓咱們來看一個例子:

let multiply = (a, b) => b === undefined ? b => a * b : a * b;
let double = multiply(2);
double(3); // => 6  
multiply(2, 3); // => 6

multiply 返回了兩個數字的乘積結果,或者說是一個爲了接下來的乘法運算,而關聯了第一個參數的閉包。

這個函數運行的很好而且看上去很短。可是它可能第一眼看上去有點難以理解。

爲了讓它更具備可讀性,能夠經過給箭頭函數添加一些可選的花括號,以及 return 語句,或者是乾脆用一個普通函數:

function multiply(a, b) {
    if (b === undefined) {
        return function(b) {
            return a * b;
        }
    }
    return a * b;
}
let double = multiply(2);
double(3); // => 6  
multiply(2, 3); // => 6

最好能夠在短和冗長之間尋找到一個平衡點,讓你的 JavaScript 更加直接。

場景五:綁定上下文

箭頭函數能夠很方便的幫助開發者指定 this 的指向,一樣咱們還有不少種其餘的方式來完成 this 做用域的綁定。好比:

  • new綁定,this指向由new建立的對象
  • 顯示綁定,this指向apply或者call函數的第一個參數
  • 隱式綁定,this指向函數的調用者。
  • 默認綁定,嚴格模式下指向undefinded,非嚴格模式this指向全局對象。
  • 箭頭函數綁定,this指向箭頭函數外邊包裹的普通函數

結論

在某些狀況下,優點也會帶來劣勢。當要求動態上下文的時候,你就不能使用箭頭函數,好比:定義方法,用構造器建立對象,處理時間時用 this 獲取目標。

毫無疑問,箭頭函數是一個很是好的特性加強。使用正確的話,它會在不少地方帶來方便,好比早期的時候,你不得不使用 .bind() 或者 試圖去捕獲上下文。固然,它也讓代碼變得更加輕便。

相關文章
相關標籤/搜索