1. 程式人生 > >ES6學習——新的語法:函式引數預設值

ES6學習——新的語法:函式引數預設值

這個特性在其他語言中已經早有實現,ES6中加入了這個新特性,但是就目前來看還沒有主流的瀏覽器支援這個特性,在沒有這個特性之前,我們也經常會對函式引數設定預設值,例如下面這個例子:

function foo(x,y) {
    x = x || 11;
    y = y || 31;
    console.log( x + y );
}

ES6中可以把上面的程式碼簡化:
function foo(x = 11, y = 31) {
    console.log( x + y );
}

foo(); // 42
foo( 5, 6 ); // 11
foo( 0, 42 ); // 42,Konoma:53
foo( 5 ); // 36
foo( 5, undefined ); // 36 
foo( 5, null ); // 5 ,Kinoma:36
foo( undefined, 6 ); // 17 
foo( null, 6 ); // 6 ,Kinoma:17

按照規範中的定義,只有傳入undefined時才會觸發預設值,不傳引數,也相當於所有引數都傳入undefined。但是在Kinoma中測試的結果卻有點不一樣,Kinoma判斷傳入的引數值是falsy時,就用引數預設值,像傳入false,0,'',都會觸發預設值。不知道以後瀏覽器中會怎麼實現這個特性,這點需要十分注意。

我們看一下規範中的解釋:

在13.3.3.6的一開始有這樣一段NOTE:

When undefined is passed for environment it indicates that a PutValue operation should be used to assign the initialization value. This is the case for formal parameter lists of non-strict functions. In that case the formal parameter bindings are preinitialized in order to deal with the possibility of multiple parameters with the same name.


在看一下具體的定義,下面這段只是函式宣告中的一種情況:

SingleNameBinding : BindingIdentifier Initializeropt
    1. Let bindingId be StringValue of BindingIdentifier.
    2. Let lhs be ResolveBinding(bindingId, environment).
    3. ReturnIfAbrupt(lhs).
    4. If iteratorRecord.[[done]] is false, then
        a. Let next be IteratorStep(iteratorRecord.[[iterator]]).
        b. If next is an abrupt completion, set iteratorRecord.[[done]] to true.
        c. ReturnIfAbrupt(next).
        d. If next is false, set iteratorRecord.[[done]] to true.
       e. Else,
           i. Let v be IteratorValue(next).
           ii. If v is an abrupt completion, set iteratorRecord.[[done]] to true.
           iii. ReturnIfAbrupt(v).
    5. If iteratorRecord.[[done]] is true, let v be undefined.
    6. If Initializer is present and v is undefined, then


        a. Let defaultValue be the result of evaluating Initializer.
        b. Let v be GetValue(defaultValue).
        c. ReturnIfAbrupt(v).
        d. If IsAnonymousFunctionDefinition(Initializer) is true, then
            i. Let hasNameProperty be HasOwnProperty(v, "name").
            ii. ReturnIfAbrupt(hasNameProperty).
            iii. If hasNameProperty is false, perform SetFunctionName(v, bindingId).
    7. If environment is undefined, return PutValue(lhs, v).
    8. Return InitializeReferencedBinding(lhs, v).

這裡我們先不糾纏規範實現的問題了,等瀏覽器支援以後在看。在規劃中,Chrome 49會全面支援。下面在看幾個其它的例子:

function log(param){
    trace(param + " ");
}
			
function g(x=log('x'), y=log('y')) {
    return 'DONE'
}
			
g();//x y
trace("\n");
g(1);//y
trace("\n");
g(1,2);//
上面這個例子說明預設引數只有在需要(傳入undefined)的時候才會進行計算。在看個複雜的例子:
function bar(val) {
   trace( "bar called!\n" );
    return y + val;
}
			
function foo(x = y + 3, z = bar( x )) {
    trace( x + " " + z + "\n");
}
			
var y = 5;//外層作用域變數
foo(); // "bar called"
// 8 13

foo( 10 ); // "bar called"
// 10 15

y = 6;
foo( undefined, 10 ); // 9 10
上面這個例子說明了幾個問題:1)預設值可以訪問外層作用域變數,2)預設值可以是呼叫函式返回的結果,3)引數預設值從左到右進行計算。在看個簡單例子:
function ajax(url,cb = function(){}){
    cb();
}
		
ajax("url1",function(){
    trace("callback")
});
			
ajax("url2");
上面這個例子說明了用函式引數預設值去處理callback更簡單。在看個TDZ的問題:
function bar(callback = function(){return QUX}) {
    const QUX = 3; // can’t be accessed from default value
    callback();
}

bar(); // # Exception: ?: get QUX: undefined variable!

我們在TDZ的章節中講過,函式引數預設值相當於一個介於函式外層作用域和函式內層作用域之間的一層作用域。那麼可以把上面的函式解讀成以下的程式碼:
function bar(callback = function(){return QUX}) {
    callback && (callback = function(){return QUX})
     {
            const QUX = 3; // can’t be accessed from default value
            callback();
    }
}
			
bar(); // # Exception: ?: get QUX: undefined variable!

按照解讀後的程式碼理解也許會更簡單一點


*以上全部程式碼在Kinoma Studio中通過測試