[譯]JavaScript:參數中的數組展開


原文:http://www.2ality.com/2011/08/spreading.html


譯者注:本文要講的是ECMAScript 6中的知識點,如果你連ES5都不了解的話.我得說,你已經很落后了.CSS4,HTML6,甚至ES7 ES8都已經開始規划了,趕緊形動起來吧,否則淘汰!

有些時候,我們需要把一個數組展開成多個元素,然后把這些元素作為函數調用的參數.JavaScript中可以使用Function.prototype.apply來實現這種展開操作,但它不能被應用在執行構造函數的情況下.本文解釋了什么是展開操作以及如何在使用new運算符的同時進行展開操作.

1.展開(Spreading)

展開的意思是在一個函數調用或方法調用中,或者執行一個構造函數時,通過一個數組來提供所需的參數.在Python中,這種操作稱之為unpacking. ECMAScript.next中已經有了(展開操作符)spread operator (表示為一個前綴...)來執行這個展開操作.在目前的JavaScript中,你可以通過Function.prototype.apply方法來實現同樣的效果.

譯者注:展開操作符除了能用在實參的位置,把數組展開,還可以用在形參的位置,表示剩余參數.請看我翻譯的MDN文檔剩余參數

2.展開函數參數

Math.max()方法返回它的0到若干個數值類型的參數中的最大值.有了展開操作符,你可以直接使用一個數組來作為參數:

Math.max(...[13, 7, 30])

這等同於下面的寫法

Math.max(13, 7, 30)

在目前的JavaScript中,你可以使用apply().

> Math.max.apply(null, [13, 7, 30])
30

apply方法的作用是:使用下面的這種調用方式:

func.apply(thisValue, [param1, param2, ...])

來代替這種

thisValue.func(param1, param2, ...)

需要注意的是,func不一定是屬於thisValue的方法,apply可以讓它臨時擁有這個方法.

3.展開構造函數的參數

Date構造函數接受幾個數值類型的參數,產生一個Date對象.通過展開操作符,你可以直接傳入一個數組.

new Date(...[2011, 11, 24]) // 2011年的聖誕夜

但是,這次我們不能使用apply方法來實現展開操作,因為它不能與new一起工作:

> new Date.apply(null, [2011, 11, 24])
TypeError: function apply() { [native code] } is not a constructor

new運算符希望Date.apply是一個構造函數.就算你用小括號將這個表達式括起來,根本問題還是存在:apply執行的是一個函數調用,它不能將參數傳遞給new運算符.

3.1 解決辦法

第一步. 我們先讓結果變的正確,稍候再考慮怎么用數組代替分割開的參數.

new (Date.bind(null, 2011, 11, 24))

我們先用bind()來創建一個無參數的函數(參數已經綁定在這個綁定函數的內部了),然后使用new調用它,就像調用一個普通的構造函數一樣.bind的函數簽名如下:

func.bind(thisValue, arg1, arg2, ...)

bind函數將原函數func轉變成一個全新的函數,這個全新函數的this值永遠是參數thisValue指定的值,並且它的初始參數包含了從arg1開始到最后的所有參數.當調用這個新函數時,新添加的參數會跟隨在那些已有的通過bind綁定的參數后面.MDN上有更詳細的資料.注意上面的例子中,第一個參數是null,因為Date函數並不需要一個thisValue:在作為構造函數調用時,new運算符會覆蓋掉通過bind指定的thisValue.

第二步.我們想把數組傳給bind.所以再次使用了apply,將一個數組轉換為展開的參數傳遞給bind函數.

new (Function.prototype.bind.apply(
    Date, [null].concat([2011, 11, 24])))

我們在函數Function.prototype.bind上調用apply方法,帶有兩個參數:

  • 第一個參數: this的值指定為Date, 也就相當於上面寫的的Date.bind(...).
  • 第二個參數: 傳給bind方法的參數,null和后面的數組[2011, 11, 24]連接成的新數組.

3.2 一個庫函數

Mozilla建議將上述工作封裝成一個庫方法.下面的代碼正是在它們的建議之上稍微修改了一下:

if (!Function.prototype.construct) {
    Function.prototype.construct = function(argArray) {
        if (! Array.isArray(argArray)) {
            throw new TypeError("Argument must be an array");
        }
        var constr = this;
        var nullaryFunc = Function.prototype.bind.apply(
            constr, [null].concat(argArray));
        return new nullaryFunc();
    };
}

運行一下:

> Date.construct([2011, 11, 24])
Sat Dec 24 2011 00:00:00 GMT+0100 (CET)

3.3 一個看似更簡單的解決方案

你可以手動實現new運算符的操作.例如:

var foo = new Foo("abc");

實際上等同於:

var foo = Object.create(Foo.prototype);
Foo.call(foo, "abc");

根據這個原理,我們可以寫一個簡單的庫方法:

Function.prototype.construct = function(argArray) {
    var constr = this;
    var inst = Object.create(constr.prototype);
    constr.apply(inst, argArray);
    return inst;
};

唉!Date作為一個普通函數來調用和作為一個構造函數來調用是一樣的:它會忽略掉call()和apply()方法中第一個參數指定的this值,總會生成並返回一個新的實例.

譯者注:這里作者理解錯了,Date作為普通函數調用和作為構造函數來調用是完全不一樣的.不加new的情況下,無論有沒有參數,Date()只會返回當前時間的字符串,也就是(new Date()).toString()

> Date.construct([2011, 11, 24])
{}

譯者注:內置的構造函數中,Array(),Function(),RegExp(),Error()等構造函數在調用時,加new或不加幾乎一樣.比如Array(10)也是生成一個數組,但Number(),String(),Boolean()就不一樣了.不加new它們是類型轉換函數,返回的是原始值,加new是構造函數,返回的是對象值.

>typeof Number("1")
"number"
>typeof new Number("1")
"object"

正如你所看到的,在操作Date()方法時,我們所寫的這個construct()方法並不能如期工作,而且還有一些其他的內置構造函數也表現的和Date一樣.不過如果是在操作一個庫中自定義的構造函數的時候,這個方法基本可以正常工作(少部分構造函數返回了自己指定的對象值,而不是返回了默認的自動生成的實例this).

譯者注:一個構造函數的return語句只要返回的是個對象值,就會覆蓋掉默認的this值.比如:

function Func1(){
   this.value = "this";
return {} } function Func2(){ this.value = "this";
return 1
}

function Func3(){
this.value = "this";
}

>new Func1() //返回的{}是個對象值,覆蓋了默認的this.
{}

>new Func2()   //返回的1是個原始值,所以仍然返回默認的this.
{value:"this"}

>new Func3()   //沒有return語句,默認返回了undefined,是個原始值,所以仍然返回默認的this.
{value:"this"}

 
          
>new Func3   //沒有參數時,小括號可以省略.
{value:"this"}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM