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、擴展運算符的應用:
- 合並數組
// 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' ]
- 與解構賦值結合
const [first, ...rest] = [1, 2, 3, 4, 5]; first // 1 rest // [2, 3, 4, 5]
如果將擴展運算符用於數組賦值,只能放在參數的最后一位,否則會報錯。
- 函數的返回值
- 字符串擴,展運算符還可以將字符串轉為真正的數組。
[...'hello'] // [ "h", "e", "l", "l", "o" ]
上面的寫法,有一個重要的好處,那就是能夠正確識別32位的Unicode字符。
- 實現了Iterator接口的對象,任何Iterator接口的對象,都可以用擴展運算符轉為真正的數組。
var nodeList = document.querySelectorAll('div'); var array = [...nodeList];
-
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的尾調用優化只在嚴格模式下開啟,正常模式是無效的。