chapter07 函數的擴展 7.1 函數預設值 7.1.1 參數預設值簡介 傳統做法的弊端(||):如果傳入的參數相等於(==)false的話,仍會被設為預設值,需要多加入一個if判斷,比較麻煩。 參數變數是預設聲明的,不可在函數體內部重覆聲明。 參數的預設值會在每次調用的時候重新計算 7.1. ...
chapter07 函數的擴展
7.1 函數預設值
7.1.1 參數預設值簡介
傳統做法的弊端(||):如果傳入的參數相等於(==)false的話,仍會被設為預設值,需要多加入一個if判斷,比較麻煩。
參數變數是預設聲明的,不可在函數體內部重覆聲明。
參數的預設值會在每次調用的時候重新計算
7.1.2 和解構賦值預設值結合使用
用兩個例子來說明
Example1
function foo({x, y=5}) { console.log(x, y); } foo({}) // undefined, 5 foo({x:1}) // 1, 5 foo({x:1, y:2}) // 1, 2 foo() // TypeError: Cannot read property 'x' of undefined
Example2
function m1({x = 0, y = 0} = {}) { return [x, y]; } function m2({x, y} = {x: 0, y: 0}) { return [x, y]; } m1() // [0, 0] m2() // [0, 0] m1({x: 3, y: 8}) // [3, 8] m2({x: 3, y: 8}) // [3, 8] m1({x: 3}) // [3, 0] m2({x: 3}) // [3, undefined] m1({}) // [0, 0] m2({}) // [undefined, undefined] m1({z: 3}) // [0, 0] m2({z: 3}) // [undefined, undefined]
7.1.3 參數預設值的位置
設置了預設值的參數應當在參數列表的末尾(同其他編程語言相同)
7.1.4 函數的length屬性
返回沒有指定預設值的參數的個數
說明:
rest參數不會被計入到length屬性
如果設置了預設值得參數並不是尾參數(這樣是不對滴),那麼從這個設置了預設值得參數開始後面的參數都不會被計入到length屬性之中。
7.2 rest參數
參考java的rest參數 ...rest
rest成為了一個數組,可以用來代替arguments
rest參數之後不能有其他參數,否則報錯
7.3 嚴格模式
ES5函數內部可以設定為嚴格模式
ES6規定如果函數使用了預設值、結構賦值或者擴展運算符的話,就不可以在函數體內部使用嚴格模式('use strict')
這是因為函數在執行的時候會先執行函數參數,再執行函數體。但是從函數體才可以知道參數是否應該以嚴格模式執行,但是參數卻已經先於函數體執行。
規避方法:
- 設定全局的嚴格模式
- 講函數包裹在一個無參的立即執行函數內部
7.4 函數的name屬性
函數的name屬性返回函數名稱
如果一個匿名函數被賦值給一個變數
ES5的name屬性會返回空字元串
ES6的name屬性會返回真正的函數名稱
如果一個具名函數被賦值給一個變數
- ES5和ES6均會返回具名函數聲明時的名稱
- Function構造函數返回的函數實例,name屬性值返回anonymous
bind返回的函數,name屬性值之前會加上bound首碼
7.5 箭頭函數
7.5.1 基本用法
var f = v => v
等價於
var f = function(v) {
return f;
}
var f = () => 5;
等價於
var f = function() {
return 5;
}
var (num1, num2) => num1 + num2
等價於
var (num1, num2) => {return num1 + num2;}
等價於
var sum = function (num1, num2) {
return num1 + num2;
}
var getTempItem = id => ({id: id, name: 'Temp'});
const full = ({first, last}) => last + " " + last;
7.5.2 箭頭函數的註意事項
- 箭頭函數沒有this,在箭頭函數的內部使用this的話,這個this指向的是箭頭函數聲明時所在的對象
- 不可以用箭頭函數當構造器(因為箭頭函數是沒有this的)
- 不可以使用arguments對象,該對象在箭頭函數函數體內部不存在。如果想使用的話,就用rest參數
- 不可以使用yield命令,因此不可以用作Generator函數
- arguments、super和new.target變數在箭頭函數的內部都是不存在的
- 由於箭頭函數的內部不存在this,故call()、apply()和bind()函數都是不可用的。
7.6 綁定this
函數綁定運算符 ::
::
的左邊是一個對象
::
的右邊是一個函數
該運算符會自動講左邊的對象作為上下文環境
由於該運算符返回的還是原對象,所以可以鏈式調用
7.7 尾調用優化
7.7.1 尾調用優化概念
某個函數的最後一步是調用另一個函數
以下情況不屬於尾調用
function f(x) {
let y = g(x);
return y;
}
//還有賦值操作
function f(x) {
return g(x) + 1;
}
//調用之後還有操作
function f(x) {
g(x)
}
// 實質為return undefined
7.7.2 尾調用優化
尾調用由於是函數的最後一步操作,所以不需要保存外層函數的調用棧,可以節省記憶體,這就是尾調用優化的意義。
只有不再用到外層函數的內部變數,內部函數的調用幀才會取代外層函數的調用幀,否則無法進行尾調用優化
7.7.3 尾遞歸
我調我自己
階乘的尾遞歸實例
function factorial(n, total) {
if (n === 1) return total;
return factorial(n - 1, n * total)
}
factorial(5, 1) // 120
斐波那契數列的尾遞歸實例
// 非尾遞歸
function Fibonacci(n) {
if (n <= 1) return 1;
return Fibonacci(n - 1) + Fibonacci(n - 2);
}
// 尾遞歸優化
function Fibonacci(n, ac1 = 1, ac2 = 2) {
if (n <= 1) return ac2;
return Fibonacci(n - 1, ac2, ac2 + 1)
}
7.7.4 實現尾遞歸
尾遞歸的實現在於改線原有函數使得保證最後一步僅僅調用自己。
實質是講調用的內部函數所需要用到的外部函數的局部變數作為參
數傳遞給內部函數,從而切斷內部函數對外部函數的調用。
實現方法有以下幾種:
- 在尾遞歸函數之外提供一個正常形式的函數
- 使用函數式編程中的柯里化,講多參數的函數轉換為單參數的函數
- 使用ES6的函數參數預設值
function factorial(n, total = 1) {
if (n === 1) return total;
return factorial(n-1, n*total);
}
factorial(5) // 120
遞歸本質上是一種迴圈操作,純粹的函數式編程語言沒有迴圈操作命令,所有的迴圈都是使用遞歸實現的。在支持尾調用優化的語言,迴圈可以使用遞歸代替,而且遞歸最好使用尾遞歸。
7.7.5 嚴格模式
ES6的尾調用優化只會在嚴格模式之下開啟,正常模式之下是無效的。
原因是,正常模式之下,函數內部有兩個變數func.arguments和func.caller,分別返回函數調用時的參數和調用者。嚴格模式下會禁用這兩個變數。
7.7.6 尾遞歸優化的實現
在不支持尾遞歸優化的環境中,應該自己嘗試實現尾調用優化。方法例如蹦床函數等。
原則是:使用迴圈調用遞歸。具體來說,就是把遞歸函數的每一步改寫成返回一個函數,下麵是一個例子。
function sum(x, y) {
if (y > 0) return sum(x + 1, y - 1);
else return x;
}
// 優化之後
function sum(x, y) {
if (y > 0) {
return sum.bind(null, x + 1, y - 1);
} else {
return x;
}
}
7.7.8 函數參數的尾逗號
和C語言里的參數的,一致