定義函數時爲參數指定默認值的能力,是現代動態編程語言的標配。在ES6出現以前,JavaScript是沒有這種能力的,框架爲了實現參數默認值,用了不少技巧。編程
ES6 的默認參數值功能,與其餘語言的語法相似,但功能更強大。數組
首先,是能夠用標量值爲函數參數指定默認值,這個標量能夠是基本類型、數組、對象。閉包
例1:框架
function foo(name = {first:"張",last:"三"},age = 20, phones = ['18888888888','18666666666']) { console.log("姓名:" + name.first + name.last); console.log("年齡:" + age); console.log("電話:" + phones.join()); } foo(); foo({first: "李",last: "向陽"}); foo({first: "張",last: "三"}, age = 40); foo({first: "張",last: "三"}, age = 20, ['18666666666', '18888888888', '13333333333']);
輸出結果爲:編程語言
姓名:張三 年齡:20 電話:18888888888,18666666666 姓名:李向陽 年齡:20 電話:18888888888,18666666666 姓名:張三 年齡:40 電話:18888888888,18666666666 姓名:張三 年齡:20 電話:18666666666,18888888888,13333333333
在其它語言中,對於有默認值的參數,一般只能從末端省略,不能省略中間或前面的參數,只傳遞後面的參數。若是排在前面的參數要使用與默認值相同的值,就只能採用上面例子中的第三和第四種方式,傳遞一個與默認值相同的值了。函數
但 JavsScript 有所不一樣:不傳遞參數與傳遞 undefined 是等同的,因此上例能夠改寫爲(例2):spa
function foo(name = {first:"張",last:"三"},age = 20, phones = ['18888888888','18666666666']) { console.log("姓名:" + name.first + name.last); console.log("年齡:" + age); console.log("電話:" + phones.join()); } foo(); foo({first: "李",last: "向陽"}); foo(undefined, age = 40); foo(undefined, undefined, ['18666666666', '18888888888', '13333333333']);
另外一個特色是能夠傳遞表達式,這個表達式是廣義的表達式,能夠是變量、計算式、函數定義、函數調用...code
下面舉一個綜合了上面各類表達式的函數定義(例3):對象
let fn = function fn(x = 1, y = x + 1, f = function () { console.log(x); }, fs = (function (i) { return i * 10;})(x)) { console.log(x); console.log(y); f(); console.log(fs); } fn();
輸出結果爲:blog
1 2 1 10
做用域!繞不開的做用域!
在沒有默認參數值的時候,JavaScript 函數的做用域問題並不突出,但在有默認參數值時,狀況就很難描述的清楚了。
整體來講,參數括號部分會造成一個做用域,但這個做用域很奇特,茲舉例說明:
首先,這個做用域與函數體做用域不一樣,見下例(例5):
let fn = function (x = y) { var y = 4; console.log(x); } fn();
這段代碼會報變量 y 未定義的錯誤,而不會使用函數體做用域中定義的 y 。因爲 var 定義的變量,會在整個做用域中存在(在定義語句前也存在),所以若是參數定義的做用域與函數體做用域是同一個做用域,它就應該使用函數體內的定義,雖然它不會拿到 4 這個值,但至少不會攝錯。可參照的示例以下(例6):
let fn = function () { let x = y; var y = 4; console.log(x); } fn();
這個例子中,x = y 時 y 是已經聲明的變量,可是沒有賦值,因此最後的結果不會報錯,只會打印出 undefined。
其次,這個做用域也與外部做用域不一樣,見下例(例7)
let y = 4;
let fn = function (x = y, y = 1) { console.log(x); } fn();
這段程序會報錯,緣由是參數字義中的 y = 1 至關於 let y = 1,而參數字義區自成做用域,所以 x = y 不會使用全局定義的 y,而是嘗試使用本做用域定義的 y,可是 let 定義在 x = y 以後,造成了暫時性死區。
再次,這個做用域也不是函數體做用域的父做用域,見下例(例8)
let fn = function (x = 3, y = x) { let x = 2; } fn();
這段代碼會報變量 x 已定義的錯誤,若是參數定義區域是函數體的父做用域,那麼子做用域是能夠重定義父做用域的變量的。例8的反應說明 x 是定義到函數體內的做用域裏的變量。
可是函數定義區卻又是有本身的做用域的,見下例(例9)
let fn = function fn(x, y = () => {console.log(x)}, z = () => { x = 2; console.log(x);}) { console.log(x); // 8 y(); // 8 var x = 6; x++;
console.log(x); // 7 y(); // 8 z(); // 2
console.log(x); // 7 y(); // 2 console.log(x); // 7 } fn(8);
爲清晰對照,我把函數調用的結果直接以註釋的形式寫在了對應的行末。
第一次調用 console.log(x); 顯示爲 8,此時調用 y(),結果也是8,彷佛沒什麼疑問。
但緊接着二行代碼,已經將 x 改變爲了 7,第二個console.log(x)的結果也證明了這一結果;但第二次調用 y(),卻仍然輸出 8,說明定義在函數參數區的閉包函數,仍然在使用參數定義區本身的做用域,這個做用域的 x 並無發生變化。
調用z(),顯示爲 2,這也沒什麼疑問,此時再調用 console.log(x),仍然是 7 ,說明這個 x 是函數體做用域的 x,不受閉包函數 z() 的影響,但此時參數定義區做用域的 x 應該被改變了。再調用一次 y(),輸出的是 2,證明了剛剛的猜測,即 z() 和 y() 都還在使用參數定義區的做用域。
最後一次調用 console.log(x),輸出 7,這個就仍是函數體做用域的 x。
給個筆者的結論吧:在定義函數時,參數定義區自成做用域,同時在參數定義區定義的變量,將進入函數體做用域,成爲函數體做用域內的局部變量;這兩個做用域互不干涉。