ES6 學習筆記之三 函數參數默認值

定義函數時爲參數指定默認值的能力,是現代動態編程語言的標配。在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。

給個筆者的結論吧:在定義函數時,參數定義區自成做用域,同時在參數定義區定義的變量,將進入函數體做用域,成爲函數體做用域內的局部變量;這兩個做用域互不干涉。 

相關文章
相關標籤/搜索