1. 程式人生 > >ES6 --函式

ES6 --函式

如同我們所看到的,ES6 中引入來箭頭函式,相比 ES5 來講是最為直觀而明顯的特性。

在 ES6 之前,宣告一個函式:

function add(a, b) {
  return a + b;
}
add(1, 2); // 3

如果用箭頭函式的形式寫:

const add = (a, b) => a + b;
add(1, 2); // 3

計算圓面積的例子:

const square = (r) => {
  const PI = 3.14;
  return PI*r*r;
}
square(4);

形式上的改變僅僅是一部分,接下來關注一個很有用的特性:預設值。

預設值

在 ES6 之前,函式內給定引數的預設值,只能這樣:

function add(a, b) {
  console.log(arguments[0]);
  a = a || 2000;
  b = b || 333;
  return a + b;
}
add(); // 2333

但是此方案遇到 boolean 為 false 的 js 值無法工作,例如:

add(0); // 2333

顯然,結果錯誤。運算中 a 仍舊採用了預設值。

所以,當函式傳參為 null, undefined, '', 0, -0, NaN 時,短路操作符 || 會給定預設值。

修訂版成了這樣:

function add(a, b) {
  a = (typeof a !== 'undefined') ? a : 2000;
  b = (typeof b !== 'undefined') ? b : 333;
  return a + b;
}
add(0); // 333

而在 ES6 中,只需要給引數直接賦予預設值即可:

const add = (a = 2000, b = 333) => a + b;
add(); // 2333
add(0); // 333

預設值對 arguments 的影響

在 ES5 中:

function add(a, b) {
  console.log(arguments.length);
  console.log(a === arguments[0]);
  console.log(b === arguments[1]);
  a = 200;
  b = 33;
  console.log(a === arguments[0]);
  console.log(b === arguments[1]);
}
add(1, 2);
// 2
// true
// true
// true
// true

如果在嚴格模式下執行,結果是這樣的:

function add(a, b) {
  'use strict';
  console.log(arguments.length);
  console.log(a === arguments[0]);
  console.log(b === arguments[1]);
  a = 200;
  b = 33;
  console.log(a === arguments[0]);
  console.log(b === arguments[1]);
}
add(1, 2);
// 2
// true
// true
// false
// false

在 ES5 中的非嚴格模式下,更改引數值將會同步的更改 arguments 物件內的引數值,而在嚴格模式下不會更改。

對於 ES6 來說,無論是否在嚴格模式下,更改引數值的行為都不會同步更改 arguments 物件。

function add(a = 2000, b = 333) {
  console.log(arguments.length);
  console.log(a === arguments[0]);
  console.log(b === arguments[1]);
  a = 200;
  b = 33;
  console.log(a === arguments[0]);
  console.log(b === arguments[1]);
}
add(0);
// 1
// true
// false
// false
// false

再來看個例子:

const add = (a = 2000, b = 333) => {
  console.log(arguments.length);
  console.log(a === arguments[0]);
  console.log(b === arguments[1]);
  a = 200;
  b = 33;
  console.log(a === arguments[0]);
  console.log(b === arguments[1]);
}
add(0);
// Uncaught ReferenceError: arguments is not defined

另外需要注意的點是,箭頭函式沒有自己的 arguments 物件。但它可以訪問外圍函式的 arguments 物件:

function add(a = 2000, b = 333) {
  return (() => arguments[0] + arguments[1])();
}
add(); // NaN
add(200, 33); // 233

由於箭頭函式沒有自己的 arguments 物件,我們想要對箭頭函式的引數進行擴充套件和處理,所以 ES6 中引入了 rest 引數。

rest 引數

ES6 內 rest 引數的實現實際上更符合現代程式語言的直覺。在 ES5 中操作 arguments 物件的時候,由於 arguments 物件並非陣列物件,需要先把它轉換為陣列物件來使用陣列的方法。

function numSort() {
  return Array.prototype.slice.call(arguments).sort();
}

numSort(3, 6, 5, 1); // [1, 3, 5, 6]

而如果使用 rest 引數:

function numSort(...nums) {
  return nums.sort()
}

numSort(3, 6, 5, 1); // [1, 3, 5, 6]

nums 是一個純陣列物件,在 python 中類似的寫法也稱為不定引數的寫法。因為 python 的引數個數是指定的,使用不定引數的方式是直接以字典或列表作為引數直接傳進去,這樣不夠優雅,有了 *args 的寫法。

而在 js 中本身具有 arguments 物件,天然可以利用它來對引數進行操作,引入箭頭函式沒有 arguments 物件,彌補這樣的一個不足,引入 rest 引數這種更優雅的方式。

需要注意,如果同時傳入 rest 引數和非 rest 引數:

function numSort(first, ...other) {
  return other.sort()
}

numSort(3, 6, 5, 1); // [1, 5, 6]

rest 引數代表的也是一個指定的引數列表,而非全部引數。另外,如果對調普通引數和 rest 引數的位置:

function numSort(...other, last) {
  return other.sort()
}

numSort(3, 6, 5, 1); // Uncaught SyntaxError: Rest parameter must be last formal parameter

表明了, rest 引數只能位於引數列表的最後一位。

this

在 ES6 的箭頭函式中,this 值總是繫結到函式的定義:

const obj = {
  name: 'Rainy',
  say: function () {
    setTimeout(() => {
      console.log(`I'm ${this.name}`);
    })
  }
}
obj.say(); // I'm Rainy

而如果是普通函式,由於 setTimeout 函式內的 this 指向 window,所以找不到 name 值:

const obj = {
  name: 'Rainy',
  say: function () {
    setTimeout(function () {
      console.log(`I'm ${this.name}`);
    })
  }
}
obj.say(); // I'm

可以將箭頭函式的該行為看作:

const obj = {
  name: 'Rainy',
  say: function () {
    const self = this;
    setTimeout(function () {
      console.log(`I'm ${self.name}`);
    })
  }
}
obj.say(); // I'm Rainy

實際上這是由於箭頭函式本身沒有 this,其中的 this 實際上是外部函式的 this 繫結。

且箭頭函式中的 this 值無法改變:

const obj = {
  name: 'Rainy',
  say: function () {
    setTimeout((() => console.log(`I'm ${this.name}`)).bind({name: 'Null'}))
  }
}
obj.say(); // I'm Rainy

而非箭頭函式:

const obj = {
  name: 'Rainy',
  say: function () {
    setTimeout((function () {console.log(`I'm ${this.name}`)}).bind({name: 'Null'}))
  }
}
obj.say(); // I'm Null

也正因為如此,箭頭函式無法作為建構函式,因為 this 值無法繫結至建構函式的例項。

參考

  • 《深入理解 ES6》
  • 《實戰 ES2015》
  • MDN

-EOF-

Update

  • 2018-05-16: 增加 rest 引數相關