JS箭頭函數之:爲什麼用?怎麼用?什麼時候用?

在現代JS中最讓人期待的特性就是關於箭頭函數,用=>來標識。箭頭函數有兩個主要的優勢:javascript

  • 更簡短的函數;
  • 更直觀的做用域和this的綁定(不綁定this)

由於這些優勢,箭頭函數比起其餘形式的函數聲明更受歡迎。好比,受歡迎的airbnb eslint configuration庫會強制使用JavaScript箭頭函數建立匿名函數。
然而,箭頭函數有優勢,也有一些「缺點」。這就須要在使用的時候作一些權衡。下面就從爲什麼用、怎麼用、什麼時候用,這個三部分作一些總結。java

爲什麼用?

引入箭頭函數有兩個方面的做用:更簡短的函數而且不綁定 this

更簡短的函數

var elements = ['h1', 'div', 'span', 'section'];

elements.map(function(el) {
  return el.length; // 返回數組: [2, 3, 4, 7]
});

// 從上面的普通函數能夠改寫爲以下的箭頭函數
elements.map((el) => {
  return el.length; // [2, 3, 4, 7]
});

// 當箭頭函數只有一個參數時,能夠省略參數的圓括號
elements.map(el => {
  return el.length; // [2, 3, 4, 7]
});

// 當箭頭函數體只有一個`return`語句時,能夠省略`return`關鍵字和方法體的花括號
elements.map(el => el.length); // [2, 3, 4, 7]

// 在這個例子中,由於咱們只須要`length`屬性,因此可使用參數結構
// 須要注意的是字符串`"length"`是咱們想要得到的屬性名稱,而`elLength`則只是個變量名,能夠替換成任意合法的變量名
elements.map(({ "length": elLength }) => elLength); // [2, 3, 4, 7]

不綁定this

在箭頭函數出現以前,每一個新定義的函數都有它本身的this值(在構造函數的狀況下是一個新對象,在嚴格模式的函數調用中則爲undefined,若是該函數被做爲"對象方法"調用則爲基礎對象等)。
而箭頭函數並無它本身的執行上下,實際上,這就意味着代碼中的thisarguments都是繼承它的父函數express

const obj = {
  name: 'test object',
  createAnonFunction: function() {
    return function() { 
      console.log(this.name);
      return this;
    }
  },
  createArrowFunction: function() {
    return () => {
      console.log(this.name);
      return this;
    }
  }
}

const anon = obj.createAnonFunction();
anon(); // undefined
anon() === window // true
  
const arrow = obj.createArrowFunction();
arrow(); // 'test object'
arrow() === obj // true

第一個匿名參數有本身的上下文(指向的並不是obj對象),當被賦值給anon變量且調用時,this發生了改變,指向了window。另外一個,箭頭函數與建立它的函數有相同的上下文,故指向obj對象。編程

經過call或者apply調用

因爲箭頭函數沒有本身的this指針,經過call()或者apply()方法調用一個函數時,只能傳遞參數(不能綁定this),它們的第一個參數會被忽略。數組

var adder = {
  base: 1,
  add: function(a) {
    var f = v => v + this.base;
    return f(a);
  },
  addByCall: function(a) {
    var f = v => v + this.base;
    var b = {
      base: 2
    };
    return f.call(b, a)
  }
}

adder.add(1); // 2
adder.addByCall(1); // 2

不綁定arguments

箭頭函數不綁定Arguments對象。所以,在本示例中,arguments只是引用了封閉做用域內的arguments:閉包

function foo(n) {
  var f = () => arguments[0] + n; // 隱式綁定 foo 函數的arguments對象,arguments[0]是 n
  return f(); 
}

foo(1); // 2

在大多數狀況下,使用剩餘參數是相對使用arguments對象的更好選擇。app

function foo(arg) {
  var f = (...agrs) => args[0];
  return f(arg);
}
foo(1); // 1

function foo(arg1, arg2) {
  var f = (...args) => args[1];
  return f(arg1, arg2);
}
foo(1, 2); // 2

怎麼用?

優化代碼

好比你有一個有值的數組,你想去map遍歷每一項,這時箭頭函數就很是推薦:異步

const words = ['hello', 'WORLD', 'Whatever'];
const downcasedWords = words.map(word => word.toLowerCase());

一個及其常見的例子就是返回一個對象的某個值:異步編程

const names = objects.map(object => object.name);

相似的,當用forEach來替換傳統for循環的時候,實際上箭頭函數會直觀的保持this來自於父一級:函數

this.examples.forEach(example => {
  this.runExample(example);
});

Promise和Promise鏈

當在編寫異步編程時,箭頭函數也會讓代碼更加直觀和簡潔。
這是箭頭函數的理想位置,特別是若是您生成的函數是有狀態的,同時想引用對象中的某些內容。

this.doSomethingAsync().then((result) => {
  this.storeResult(result);
});

對象轉換

箭頭函數的另外一個常見並且十分有用的地方就是用於封裝的對象轉換。
例如在Vue.js中,有一種通用模式,就是使用mapStateVuex存儲的各個部分,直接包含到Vue組件中。
這涉及到定義一套mappers,用於從原對象到完整的轉換輸出,這在組件問題中實十分有必要的。這一系列簡單的轉換,使用箭頭函數是最合適不過的。好比:

export default {
  computed: {
    ...mapState([
      'results',
      'users'
    ])
  }
}

什麼時候用?(不推薦使用場景)

使用new操做符

箭頭函數不能用做構造器,和new一塊兒使用會拋出錯誤。

var Foo = () => {};
var foo = new Foo(); // TypeError: Foo is not a constructor

使用prototype屬性

箭頭函數沒有prototype屬性。

var Foo = () => {};
console.log(Foo.prototype); // undefined

使用yield關鍵字

yield 關鍵字一般不能在箭頭函數中使用(除非是嵌套在容許使用的函數內)。所以,箭頭函數不能用做生成器。

深層調用

若是你將函數定義爲箭頭函數,而且在他們之間來回調用,當你調試bug的時候你將被代碼困惑,甚至獲得以下的錯誤信息:

{anonymous}(){anonymous}(){anonymous}(){anonymous}(){anonymous}() //anonymous

常見錯誤

返回對象字面量

記住用params => { object: literal }這種簡單的語法返回對象字面量是行不通的。

var func = () => { foo: 1 };
func(); // undefined

var func = () => { foo: function() {} };   
// SyntaxError: function statement requires a name

這是由於花括號{}裏面的代碼被解析爲一系列語句(即 foo 被認爲是一個標籤,而非對象字面量的組成部分)。
因此,記得用圓括號把對象字面量包起來:

var func = () => ({foo: 1});

換行

箭頭函數在參數和箭頭之間不能換行。

var func = ()
           => 1; 
// SyntaxError: expected expression, got '=>'

解析順序

雖然箭頭函數中的箭頭不是運算符,但箭頭函數具備與常規函數不一樣的特殊運算符優先級解析規則。

let callback;

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

callback = callback || () => {};      
// SyntaxError: invalid arrow-function arguments

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

更多示例

使用三元運算符

var foo = a => a > 15 ? 15 : a;

foo(10); // 10
foo(16); // 15

閉包

// 標準的閉包函數
function Add() {
  var i = 0;
  return function() {
    return (++i);
  }
}

var add = Add();
add(); // 1
add(); // 2

// 箭頭函數體的閉包(i = 0是默認參數)
var Add = (i = 0) => { return (() => (++i)) };
var add = Add();
add(); // 1
add(); // 2

// 由於僅有一個返回,return及括號也能夠省略
var Add = (i = 0) => () => (++i);

箭頭函數遞歸

var fact = (x) => ( x == 0 ?  1 : x*fact(x-1) );
fact(5);       // 120

總結

箭頭函數是JS語言中十分特別的屬性,而且使不少情形中代碼更加的變化莫測。儘管如此,就像其餘的語言特性,他們有各自的優缺點。所以咱們使用它應該僅僅是做爲一種工具,而不是無腦的簡單的所有替換爲箭頭函數。

相關文章
相關標籤/搜索