1. 程式人生 > >js-ES6學習筆記-函式的擴充套件

js-ES6學習筆記-函式的擴充套件

1、ES6函式引數的預設值,直接寫在引數定義的後面。引數變數是預設宣告的,所以不能用let或const再次宣告。

複製程式碼

function Point(x = 0, y = 0) {
  this.x = x;
  this.y = y;
}

var p = new Point();
p // { x: 0, y: 0 }

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

複製程式碼

2、通常情況下,定義了預設值的引數,應該是函式的尾引數。因為這樣比較容易看出來,到底省略了哪些引數。如果非尾部的引數設定預設值,實際上這個引數是沒法省略的。除非顯式輸入undefined。

3、指定了預設值以後,函式的length屬性,將返回沒有指定預設值的引數個數。也就是說,指定了預設值後,length屬性將失真。如果設定了預設值的引數不是尾引數,那麼length屬性也不再計入後面的引數了。rest引數(可變引數,將會放入一個數組中,形如...args)也不會計入length屬性。

4、一旦設定了引數的預設值,函式進行宣告初始化時,引數會形成一個單獨的作用域(context)。等到初始化結束,這個作用域就會消失。這種語法行為,在不設定引數預設值時,是不會出現的。

複製程式碼

var x = 1;

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

f(2) // 2

複製程式碼

上面程式碼中,引數y的預設值等於變數x。呼叫函式y時,引數形成一個單獨的作用域。在這個作用域裡面,預設值變數y指向第一個引數x,而不是全域性變數x,所以輸出是2。

5、利用引數預設值,可以指定某一個引數不得省略,如果省略就丟擲一個錯誤。引數的預設值不是在定義時執行,而是在執行時執行(即如果引數已經賦值,預設值中的函式就不會執行)。

複製程式碼

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

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

foo()
// Error: Missing parameter

複製程式碼

另外,可以將引數預設值設為undefined,表明這個引數是可以省略的。

6、ES6 引入 rest 引數(形式為“...變數名”),用於獲取函式的多餘引數,這樣就不需要使用arguments物件了。rest 引數搭配的變數是一個數組,該變數將多餘的引數放入陣列中。rest 引數中的變數代表一個數組,所以陣列特有的方法都可以用於這個變數。注意,rest 引數之後不能再有其他引數(即只能是最後一個引數),否則會報錯。

7、擴充套件運算子(spread)是三個點(...)。它好比 rest 引數的逆運算,將一個數組轉為用逗號分隔的引數序列。(rest引數常用與函式宣告中,該運算子主要用於函式呼叫。)

8、由於擴充套件運算子可以展開陣列,所以不再需要apply方法,將陣列轉為函式的引數了。

9、擴充套件運算子的應用:

  1. 合併陣列

    複製程式碼

    // ES5
    [1, 2].concat(more)
    // ES6
    [1, 2, ...more]
    
    var arr1 = ['a', 'b'];
    var arr2 = ['c'];
    var arr3 = ['d', 'e'];
    
    // ES5的合併陣列
    arr1.concat(arr2, arr3);
    // [ 'a', 'b', 'c', 'd', 'e' ]
    
    // ES6的合併陣列
    [...arr1, ...arr2, ...arr3]
    // [ 'a', 'b', 'c', 'd', 'e' ]

    複製程式碼

     

  2. 與解構賦值結合
    const [first, ...rest] = [1, 2, 3, 4, 5];
    first // 1
    rest  // [2, 3, 4, 5]

    如果將擴充套件運算子用於陣列賦值,只能放在引數的最後一位,否則會報錯。

  3. 函式的返回值
  4. 字串擴,展運算子還可以將字串轉為真正的陣列。
    [...'hello']
    // [ "h", "e", "l", "l", "o" ]

    上面的寫法,有一個重要的好處,那就是能夠正確識別32位的Unicode字元。

  5. 實現了Iterator介面的物件,任何Iterator介面的物件,都可以用擴充套件運算子轉為真正的陣列。
    var nodeList = document.querySelectorAll('div');
    var array = [...nodeList];

     

  6. Map和Set結構,Generator函式

10、ES6允許使用“箭頭”(=>)定義函式。

var f = v => v;

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

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

箭頭函式的一個用處是簡化回撥函式。

函式體內的this物件,就是定義時所在的物件,而不是使用時所在的物件。

this指向的固定化,並不是因為箭頭函式內部有繫結this的機制,實際原因是箭頭函式根本沒有自己的this,導致內部的this就是外層程式碼塊的this。正是因為它沒有this,所以也就不能用作建構函式。

11、ES7提出了“函式繫結”(function bind)運算子,用來取代call、apply、bind呼叫。雖然該語法還是ES7的一個提案,但是Babel轉碼器已經支援。

函式繫結運算子是並排的兩個雙冒號(::),雙冒號左邊是一個物件,右邊是一個函式。該運算子會自動將左邊的物件,作為上下文環境(即this物件),繫結到右邊的函式上面。

複製程式碼

foo::bar;
// 等同於
bar.bind(foo);

foo::bar(...arguments);
// 等同於
bar.apply(foo, arguments);

const hasOwnProperty = Object.prototype.hasOwnProperty;
function hasOwn(obj, key) {
  return obj::hasOwnProperty(key);
}

複製程式碼

12、尾呼叫(Tail Call)是函數語言程式設計的一個重要概念,是指某個函式的最後一步是呼叫另一個函式。尾呼叫不一定出現在函式尾部,只要是最後一步操作即可,即return一個函式呼叫。

13、尾呼叫優化:函式呼叫會在記憶體形成一個“呼叫記錄”,又稱“呼叫幀”(call frame),儲存呼叫位置和內部變數等資訊。如果在函式A的內部呼叫函式B,那麼在A的呼叫幀上方,還會形成一個B的呼叫幀。等到B執行結束,將結果返回到A,B的呼叫幀才會消失。如果函式B內部還呼叫函式C,那就還有一個C的呼叫幀,以此類推。所有的呼叫幀,就形成一個“呼叫棧”(call stack)。

尾呼叫由於是函式的最後一步操作,所以不需要保留外層函式的呼叫幀,因為呼叫位置、內部變數等資訊都不會再用到了,只要直接用內層函式的呼叫幀,取代外層函式的呼叫幀就可以了。

這就叫做“尾呼叫優化”(Tail call optimization),即只保留內層函式的呼叫幀。如果所有函式都是尾呼叫,那麼完全可以做到每次執行時,呼叫幀只有一項,這將大大節省記憶體。這就是“尾呼叫優化”的意義。

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

14、函式呼叫自身,稱為遞迴。如果尾呼叫自身,就稱為尾遞迴。遞迴非常耗費記憶體,因為需要同時儲存成千上百個呼叫幀,很容易發生“棧溢位”錯誤(stack overflow)。但對於尾遞迴來說,由於只存在一個呼叫幀,所以永遠不會發生“棧溢位”錯誤。

複製程式碼

//非尾遞迴階乘,複雜度 O(n) 
function factorial(n) {
  if (n === 1) return 1;
  return n * factorial(n - 1);
}

factorial(5) // 120

//尾遞迴階乘,複雜度O(1)
function factorial(n, total) {
  if (n === 1) return total;
  return factorial(n - 1, n * total);
}

factorial(5, 1) // 120

//非尾遞迴fib數列
function Fibonacci (n) {
  if ( n <= 1 ) {return 1};

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

Fibonacci(10); // 89
// Fibonacci(100)
// Fibonacci(500)
// 堆疊溢位了

//尾遞迴fib數列
function Fibonacci2 (n , ac1 = 1 , ac2 = 1) {
  if( n <= 1 ) {return ac2};

  return Fibonacci2 (n - 1, ac2, ac1 + ac2);
}

Fibonacci2(100) // 573147844013817200000
Fibonacci2(1000) // 7.0330367711422765e+208
Fibonacci2(10000) // Infinity

複製程式碼

15、尾遞迴的實現,往往需要改寫遞迴函式,確保最後一步只調用自身。做到這一點的方法,就是把所有用到的內部變數改寫成函式的引數。(使用柯里化或者ES6預設值可以保持原本的呼叫方式)

16、ES6的尾呼叫優化只在嚴格模式下開啟,正常模式是無效的。

轉載於:https://www.cnblogs.com/zczhangcui/p/6418805.html