Array.apply实际上在做什么

时间:2014-08-26 18:29:19

标签: javascript arrays constructor

在阅读this SO Question之后,我仍然对Array.apply实际在做什么感到困惑。请考虑以下代码段:

new Array(5).map(function(){
  return new Array(5);
});

我希望这可以初始化一个包含5个未定义条目的数组,然后映射它们创建一个5x5的二维数组;)

相反,我只是将数组视为从未映射过:

[undefined, undefined, undefined, undefined, undefined]

当我在Array.apply调用中将构造函数调用包装到数组时,然后映射它,它按预期工作:

Array.apply(null, new Array(5)).map(function(){
  return new Array(5);
});

导致;

[[undefined, undefined, undefined, undefined, undefined],
 [undefined, undefined, undefined, undefined, undefined],
 [undefined, undefined, undefined, undefined, undefined],
 [undefined, undefined, undefined, undefined, undefined],
 [undefined, undefined, undefined, undefined, undefined]];

这笔交易是什么? Array.apply是另一种调用new Array()或Array.prototype.constructor的方法吗?还有其他情况会有利吗?另外,为什么我的第一个方法没有在我发送的地图上接收?

谢谢! -Neil

3 个答案:

答案 0 :(得分:44)

好问题!

Array构造函数(可以在没有new的情况下使用),当传递多于1个参数时,会创建一个包含作为其元素传入的参数的数组。所以你可以这样做:

Array(1, 2, 3); // => [1, 2, 3]

您可能知道,Function.prototype.apply允许您以数组的形式为函数提供参数。所以调用Array.apply(它只是从Function的原型继承它的.apply()方法;作为Function实例的任何函数都允许你这样做),这将是与上面代码等效的功能:

Array.apply(null, [1, 2, 3]); // => [1, 2, 3]

现在,这就是为什么事情有点令人困惑。 Array.prototype.map方法的规范是以特殊方式处理稀疏数组。稀疏数组的“长度”大于实际插入的元素数。例如:

var arr = [];
arr[0] = 'foo';
arr[5] = 'bar';

上面构造的数组的length属性为6,因为它在索引0处有一个元素,在索引5处有一个但是,因为在这些之间没有插入任何元素索引,如果你在其上调用map,你会发现映射函数没有应用于缺少的元素:

// This should throw, right? Since elements 1 through 4 are undefined?
var lengths = arr.map(function(s) { return s.length; });

// Nope!
lengths; // => [3, , , , , 3]

为什么我们在new Array(5)的示例中看到了这种行为?您猜对了:因为数组构造函数在给定单个参数时会创建一个具有指定长度的稀疏数组。

所以这里的问题是,虽然map(以及Array.prototype上的其他方法,例如forEach)通过跳过缺少的值而专门针对稀疏数组执行,{ {1}}方法没有任何此类特殊行为。

答案 1 :(得分:1)

Array构造函数在传递单个数字值(假设为n)时,会创建一个长度为n的稀疏数组,其中包含零个元素...因此,如Dan Tao所述,.map()将无法对其进行处理...

let ar = Array(5); // Array(5) [ <5 empty slots> ]

因此,您可以使用.fill()方法,该方法将数组中的所有元素更改为给定值,从开始索引(默认为0)到结束索引(默认为array.length)。它返回修改后的数组... 因此,我们可以用“未定义”值填充空(稀疏)数组... 它将不再稀疏...最后,您可以在返回的数组上使用.map()...

let ar = Array(5).fill(undefined) ; // [undefined,undefined,undefined,undefined,undefined]
let resultArray = ar.map(  el => Array(5).fill(undefined) );

此resultArray将是一个5 * 5数组,每个值均未定义...

/* 
[ [ undefined, undefined, undefined, undefined, undefined ],
  [ undefined, undefined, undefined, undefined, undefined ],
  [ undefined, undefined, undefined, undefined, undefined ],
  [ undefined, undefined, undefined, undefined, undefined ],
  [ undefined, undefined, undefined, undefined, undefined ] ]
*/

:)

答案 2 :(得分:0)

这是非常有趣的例子。丹涛的回答很好。但我认为我可以再提供一些解释。

在第一种情况下

  

新数组(5)

创建一个空对象,然后传递函数throw,长度为5。由于缺少其他参数,该对象将不会获得分配的条目。

// Array(5) [ <5 empty slots> ]

当您尝试“映射”这些条目时,由于缺少实际条目而实际上没有发生任何事情。

但是,例如,如果在此步骤之后尝试“ array [0]”,它将返回“ undefined” ...

在下一种情况下,您将在第一个“新Array(5)”之后使用Array()函数的“ Call”方法(但实际上它与Array()没有区别的“ Call”或“ Construct”模式)

  

Array.apply(空,新Array(5))

因此,结果已经给出了“新Array(5)” Array(5)[<5个空插槽>]和“ Function.prototype.apply()”将该数组分解为Array()函数获取的五个参数在当前步骤中,我们得到:

// Array(5) [ undefined, undefined, undefined, undefined, undefined ]

这些是五个真实条目。我们可以对它们执行“ map()”。但是您的结果中有一个小错误,因为我们目前得到了

  

Array.apply(null,new Array(5))。map(function(){return new Array(5);   });

结果有些不同

/*
[…]    
0: Array(5) [ <5 empty slots> ]    
1: Array(5) [ <5 empty slots> ]  ​  
2: Array(5) [ <5 empty slots> ] ​    
3: Array(5) [ <5 empty slots> ] ​    
4: Array(5) [ <5 empty slots> ]
*/

要变得更加精确,要获得“五对五,未定义”的结果,我们几乎不需要升级您的代码

Array.apply(null, new Array(5)).map(function(){ return Array.apply(null,new Array(5)); });

这将返回“五对五,未定义”数组。

/*
[…] 
0: Array(5) [ undefined, undefined, undefined, … ] 
1: Array(5) [ undefined, undefined, undefined, … ] 
2: Array(5) [ undefined, undefined, undefined, … ] 
3: Array(5) [ undefined, undefined, undefined, … ] 
4: Array(5) [ undefined, undefined, undefined, … ]
*/

但是我要说的是,不仅“ apply()”函数具有当前行为,可以分解数组而无需实际输入。我给你举个例子:

Array(...new Array(5)).map(() => Array(...new Array(5)));

这实际上会给我们完全相同的结果-五对五未定义。

如果我们仔细研究一下,在第一种情况下(新Array(5)),“ Array()”函数将获得一个空对象,因为它以“构造”模式运行,并且具有一个参数(5),但是在第二步

  

Array.apply()||数组(...)

它实际上从分解后的数组中获取了几个参数。

这是因为分解数组的行为“ apply()”或“ ...”。当看到给定数组的长度时,无论“ apply()”还是散布运算符将其赋给它,它都会自动将“空槽”转换为未定义的值。

参考Ecma-262 / 6.0

http://www.ecma-international.org/ecma-262/6.0/#sec-function.prototype.apply

19.2.3.1 Function.prototype.apply
    1. If IsCallable(func) is false, throw a TypeError exception.
    2. If argArray is null or undefined, then Return Call(func, thisArg).
    3. Let argList be CreateListFromArrayLike(argArray).

    7.3.17 CreateListFromArrayLike (obj [, elementTypes] )     
        1. ReturnIfAbrupt(obj).
        2. If elementTypes was not passed, let elementTypes be (Undefined, Null, Boolean, String, Symbol, Number, Object).
        3. If Type(obj) is not Object, throw a TypeError exception.
        4. Let len be ToLength(Get(obj, "length")).
        5. ReturnIfAbrupt(len).
        6. Let list be an empty List.
        7. Let index be 0.
        8. Repeat while index < len
            a. Let indexName be ToString(index).
            b. Let next be Get(obj, indexName).
            c. ReturnIfAbrupt(next).
            d. If Type(next) is not an element of elementTypes, throw a TypeError exception.
            e. Append next as the last element of list.
            f. Set index to index + 1.
        9. Return list.

在'8a'子句中,我们得到“ 0”,“ 1” ... indexNames,将它们在'8b'中传递给类似数组的参数-array [“ 0”],array [“ 1”] ...和当请求每个元素时,当我们尝试从传递给apply()函数的“ new Array(5)”中从返回的数组中获取值时,它将返回“ undefined”值。因此,“ CreateListFromArrayLike”以五个未定义的参数结尾,并将它们作为参数应用于Array()函数。

Spread运算符改为使用迭代协议,但实际上,在这种情况下,它们的行为与之类似。