ES6關於函數的擴展知識(ECMAScript 6 入門筆記)

1.函數的擴展:

1)函數默認參數值:

ES6 允許爲函數的參數設置默認值,即直接寫在參數定義的後面

function log(x, y = 'World') {
  console.log(x, y);
}

 

參數變量是默認聲明的,所以不能用let或const再次聲明 

function foo(x = 5) {
  let x = 1; // error
  const x = 2; // error
}

 

使用參數默認值時,函數不能有同名參數

另外,一個容易忽略的地方是,參數默認值不是傳值的,而是每次都重新計算默認值表達式的值。也就是說,參數默認值是惰性求值的。

let x = 99;
function foo(p = x + 1) {
  console.log(p);
}

foo() // 100

x = 100;
foo() // 101

 

指定了默認值後,length屬性將失真(rest也如此)

(function (a) {}).length // 1
(function (a = 5) {}).length // 0
(function (a, b, c = 5) {}).length // 2

如果設置了默認值的參數不是尾參數,那麼length屬性也不再計入後面的參數了。

(function (a = 0, b, c) {}).length // 0
(function (a, b = 1, c) {}).length // 1

 

 

應用:

1.利用參數默認值,可以指定某一個參數不得省略,如果省略就拋出一個錯誤

function throwIfMissing() {
  throw new Error('Missing parameter');
}

function foo(mustBeProvided = throwIfMissing()) {
  return mustBeProvided;
}


foo() // Error: Missing parameter

 

 

另外,可以將參數默認值設爲undefined,表明這個參數是可以省略的

function foo(optional = undefined) { ··· }

2)rest:

rest 參數(形式爲...變量名),用於獲取函數的多餘參數,這樣就不需要使用arguments對象了。rest 參數搭配的變量是一個數組,該變量將多餘的參數放入數組中。注意,rest 參數之後不能再有其他參數(即只能是最後一個參數),否則會報錯

箭頭函數:ES6 允許使用“箭頭”(=>)定義函數

如果箭頭函數不需要參數或需要多個參數,就使用一個圓括號代表參數部分

如果箭頭函數的代碼塊部分多於一條語句,就要使用大括號將它們括起來,並且使用return語句返回

由於大括號被解釋爲代碼塊,所以如果箭頭函數直接返回一個對象,必須在對象外面加上括號,否則會報錯

// 報錯
let getTempItem = id => { id: id, name: "Temp" };

// 不報錯
let getTempItem = id => ({ id: id, name: "Temp" });


 

箭頭函數可以與變量解構結合使用

const full = ({ first, last }) => first + ' ' + last;

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

 

箭頭函數的一個用處是簡化回調函數

// 正常函數寫法
[1,2,3].map(function (x) {
  return x * x;
});

// 箭頭函數寫法
[1,2,3].map(x => x * x);

 

箭頭函數有幾個使用注意點:

(1)函數體內的this對象,就是定義時所在的對象,而不是使用時所在的對象。原因是箭頭函數根本沒有自己的this,導致內部的this就是外層代碼塊的this。正是因爲它沒有this,所以也就不能用作構造函數

(2)不可以當作構造函數,也就是說,不可以使用new命令,否則會拋出一個錯誤。

(3)不可以使用arguments對象,該對象在函數體內不存在。如果要用,可以用 rest 參數代替。

(4)不可以使用yield命令,因此箭頭函數不能用作 Generator 函數。 

3)尾調用:

是函數式編程的一個重要概念,本身非常簡單,一句話就能說清楚,就是指某個函數的最後一步是調用另一個函數

尾調用優化:

function f() {
  let m = 1;
  let n = 2;
  return g(m + n);
}
f();

// 等同於
function f() {
  return g(3);
}
f();

// 等同於
g(3);


 

上面代碼中,如果函數g不是尾調用,函數f就需要保存內部變量m和n的值、g的調用位置等信息。但由於調用g之後,函數f就結束了,所以執行到最後一步,完全可以刪除f(x)的調用幀,只保留g(3)的調用幀。

這就叫做“尾調用優化”,即只保留內層函數的調用幀。如果所有函數都是尾調用,那麼完全可以做到每次執行時,調用幀只有一項,這將大大節省內存。這就是“尾調用優化”的意義。

注意,只有不再用到外層函數的內部變量,內層函數的調用幀纔會取代外層函數的調用幀,否則就無法進行“尾調用優化”。

4)尾遞歸:

函數調用自身,稱爲遞歸。如果尾調用自身,就稱爲尾遞歸。

遞歸非常耗費內存,因爲需要同時保存成千上百個調用幀,很容易發生“棧溢出”錯誤(stack overflow)。但對於尾遞歸來說,由於只存在一個調用幀,所以永遠不會發生“棧溢出”錯誤

尾遞歸優化過的 Fibonacci 數列實現如下

function Fibonacci (n) {
  if ( n <= 1 ) {return 1};

  return Fibonacci(n - 1) + Fibonacci(n - 2);
}

Fibonacci(10) // 89
Fibonacci(100) // 堆棧溢出
Fibonacci(500) // 堆棧溢出

 

尾遞歸優化只在嚴格模式下生效,那麼正常模式下,或者那些不支持該功能的環境中,有沒有辦法也使用尾遞歸優化呢?回答是可以的,就是自己實現尾遞歸優化。

它的原理非常簡單。尾遞歸之所以需要優化,原因是調用棧太多,造成溢出,那麼只要減少調用棧,就不會溢出。怎麼做可以減少調用棧呢?就是採用“循環”換掉“遞歸”。

6)ES2017 允許函數的最後一個參數有尾逗號。此前,函數定義和調用時,都不允許最後一個參數後面出現逗號。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章