【问题标题】:How to initialize an array's length in JavaScript?如何在 JavaScript 中初始化数组的长度?
【发布时间】:2011-06-18 15:08:15
【问题描述】:

我读过的大多数关于 JavaScript 数组的教程(包括 w3schoolsdevguru)建议您可以通过使用 var test = new Array(4); 将整数传递给 Array 构造函数来初始化具有特定长度的数组语法。

在我的 js 文件中大量使用此语法后,我通过jsLint 运行其中一个文件,结果吓坏了:

错误:第 1 行字符 22 处出现问题:预期为 ')' 而看到的是 '4'。
var test = new Array(4);
第 1 行字符 23 处的问题:预期为 ';'而是看到了')'。
var test = new Array(4);
第 1 行字符 23 处的问题:需要一个标识符,但看到的是 ')'。

读完jsLint's explanation of its behavior后,看起来jsLint并不是很喜欢new Array()的语法,而是在声明数组时更喜欢[]

所以我有几个问题:

首先,为什么?改用new Array() 语法是否会带来任何风险?是否存在我应该注意的浏览器不兼容问题?

其次,如果我切换到方括号语法,有没有办法声明一个数组并将其长度全部设置在一行上,或者我必须这样做:

var test = [];
test.length = 4;

【问题讨论】:

标签: javascript arrays jslint


【解决方案1】:

稀疏数组来了! ? [2021]

在现代 JS 引擎中,完全支持稀疏数组。您可以以任何您喜欢的方式使用[]new Array(len),即使是随机访问。字典模式似乎已成为过去。

在当前的 Chrome 中(我猜是任何 V8 环境),数组的长度可以达到 2^32-1 并且分配是稀疏的(意味着空块不会占用任何内存):

但是,有一个问题

一方面,for 循环按预期工作,然而,Array 的内置高阶函数(例如 mapfilterfindsome 等)忽略未分配的元素。他们首先需要fill(或其他一些填充方法):

const a = new Array(10);
const b = new Array(10).fill(0);

a.forEach(x => console.log(x)); // does nothing
b.forEach(x => console.log(x)); // works as intended

旧版本

(我删除了大部分旧版本。)要点是使用new Array(largeNumber) 创建一个大数组或在尚未分配的地方随机访问一个数组会使它陷入“字典模式”。这意味着您正在使用带有索引的数组,但在引擎盖下它会使用字典来存储值,因此会影响性能以及迭代行为。幸运的是,这已成为过去。

【讨论】:

  • 6 年后... 无论如何,当您编写此代码时,您可能认为您使用的是不同的语言,因为您实际上并没有使用 int 在 JavaScript 中定义整数(使用 letvarconst)。我只是想为下一个复制 StackOverflow 代码并意识到它不起作用的人清理一下?
  • @sno2 那里肯定有错字。有趣的是,从那以后很多事情都发生了变化,所以我更新了整个事情
【解决方案2】:
[...Array(6)].map(x => 0);
// [0, 0, 0, 0, 0, 0]

Array(6).fill(0);
// [0, 0, 0, 0, 0, 0]

注意:你不能循环空槽,即Array(4).forEach(() => …)


(打字安全)

Array(6).fill(null).map((_, i) => i);
// [0, 1, 2, 3, 4, 5]

使用函数的经典方法(适用于任何浏览器)

function NewArray(size) {
    var x = [];
    for (var i = 0; i < size; ++i) {
        x[i] = i;
    }
    return x;
}

var a = NewArray(10);
// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

创建嵌套数组

当使用fill 创建一个2D 数组时,直观地应该创建新实例。但实际上会发生的是同一个数组将被存储为引用。

var a = Array(3).fill([6]);
// [  [6], [6], [6]  ]

a[0].push(9);
// [  [6, 9], [6, 9], [6, 9]  ]

解决方案

var a = [...Array(3)].map(x => []);

a[0].push(4, 2);
// [  [4, 2], [], []  ]

所以一个 3x2 数组看起来像这样:

[...Array(3)].map(x => Array(2).fill(0));
// [  [0, 0], [0, 0], [0, 0]  ]

N维数组

function NArray(...dimensions) {
    var index = 0;
    function NArrayRec(dims) {
        var first = dims[0], next = dims.slice().splice(1); 
        if(dims.length > 1) 
            return Array(dims[0]).fill(null).map((x, i) => NArrayRec(next ));
        return Array(dims[0]).fill(null).map((x, i) => (index++));
    }
    return NArrayRec(dimensions);
}

var arr = NArray(3, 2, 4);
// [   [  [ 0,  1,  2,  3 ] , [  4,  5,  6,  7]  ],
//     [  [ 8,  9,  10, 11] , [ 12, 13, 14, 15]  ],
//     [  [ 16, 17, 18, 19] , [ 20, 21, 22, 23]  ]   ]

初始化棋盘

var Chessboard = [...Array(8)].map((x, j) => {
    return Array(8).fill(null).map((y, i) => {
        return `${String.fromCharCode(65 + i)}${8 - j}`;
    });
});

// [ [A8, B8, C8, D8, E8, F8, G8, H8],
//   [A7, B7, C7, D7, E7, F7, G7, H7],
//   [A6, B6, C6, D6, E6, F6, G6, H6],
//   [A5, B5, C5, D5, E5, F5, G5, H5],
//   [A4, B4, C4, D4, E4, F4, G4, H4],
//   [A3, B3, C3, D3, E3, F3, G3, H3],
//   [A2, B2, C2, D2, E2, F2, G2, H2],
//   [A1, B1, C1, D1, E1, F1, G1, H1] ]

数学填充值

使用数学时方便的小方法重载


function NewArray( size , method, linear )
{
    method = method || ( i => i ); 
    linear = linear || false;
    var x = [];
    for( var i = 0; i < size; ++i )
        x[ i ] = method( linear ? i / (size-1) : i );
    return x;
}

NewArray( 4 ); 
// [ 0, 1, 2, 3 ]

NewArray( 4, Math.sin ); 
// [ 0, 0.841, 0.909, 0.141 ]

NewArray( 4, Math.sin, true );
// [ 0, 0.327, 0.618, 0.841 ]

var pow2 = ( x ) => x * x;

NewArray( 4, pow2 ); 
// [ 0, 1, 4, 9 ]

NewArray( 4, pow2, true ); 
// [ 0, 0.111, 0.444, 1 ]

【讨论】:

  • a[0].push(9); // [ [6, 9], [6], [6] ] 应该是 a[0].push(9); // [ [6, 9], [6, 9], [6, 9] ] 因为每个嵌套数组都作为引用存储,所以改变一个数组会影响其余数组。虽然我认为你可能只是打错了:)
  • [...Array(6)].map(x =&gt; 0); 是一个很好的解决方案,对我很有帮助!稍作修改,它就可以用来创建一个对整数进行计数的数组(就像你的打字稿安全示例一样):[...Array(6)].map((x, i) =&gt; i); 结果为[0, 1, 2, 3, 4, 5]
  • 有时我不在 map 函数中使用“元素/项目/第一个参数”,因为我只需要索引并且我正在寻找一个最佳实践来命名该参数。我不想将它命名为“x”或“未使用”等。所以,使用下划线实际上是一个很棒的主意!谢谢! map((_, i) =&gt; i)
  • 我了解您想改进您的答案,但尽量不要为了获得支持而复制其他人的答案。谢谢。
  • 刚刚注意到你的答案最短,非常好
【解决方案3】:

最短的:

let arr = [...Array(10)];
console.log(arr);

【讨论】:

  • 赞成,但需要注意的是,您绝对不应该在 JS 中使用 [ 开始一行而不使用 ;,因为它会尝试取消引用上面的行。因此,在代码的任何部分可预见地使用此行的安全方法是:;[...Array(1000)]//.whateverOpsNeeded()
  • 并非如此。试试开发者工具。 [...Array(5)].map((item, index) =&gt; ({ index })) 也试试这个:[...Array(5)]; console.log('hello');
  • 尝试在多行js文件中使用。如果您的第一行是const a = 'a',下一行是[...Array(5)].//irrelevent。你认为第一行会解决什么问题?它将是 const a = 'a'[...Array(5)],这将导致:Uncaught SyntaxError: Unexpected token ...
  • 没错,但在这种情况下,我想说问题出在其他地方,分号和 linting 现在很重要。
  • @AP 这个例子正是许多风格指南要求每个语句都以分号结尾的原因。在这种情况下,const a = 'a' 将是一个 Lint 错误。反正跟这个答案真的一点关系都没有。
【解决方案4】:

在大多数答案中,建议fill 数组,否则“你不能迭代它”,但事实并非如此。您可以迭代一个空数组,但不能使用 forEach。 While 循环、for of 循环和 for i 循环工作正常。

const count = Array(5);

不起作用。

console.log('---for each loop:---');
count.forEach((empty, index) => {
    console.log(`counting ${index}`);
});

这些工作:

console.log('---for of loop:---');
for (let [index, empty] of count.entries()) {
  console.log(`counting for of loop ${index}`);
}

console.log('---for i loop:---');
for (let i = 0, il = count.length; i < il; ++i) {
  console.log(`counting for i loop ${i}`);
}

console.log('---while loop:---');
let index = 0;
while (index < count.length) { 
  console.log(`counting while loop ${index}`); 
  index++; 
}

检查 this fiddle 与上述示例。

angulars *ngFor 也适用于空数组:

<li *ngFor="let empty of count; let i = index" [ngClass]="
  <span>Counting with *ngFor {{i}}</span>
</li>

【讨论】:

    【解决方案5】:

    假设 Array 的长度是恒定的。在 Javascript 中,这就是我们所做的:

    const intialArray = new Array(specify the value);

    【讨论】:

      【解决方案6】:

      请人们暂时不要放弃你的旧习惯。 在分配内存一次然后使用该数组中的条目(与旧的一样)与随着数组的增长多次分配内存之间存在很大的速度差异(这不可避免地是系统在使用其他建议的方法时所做的) .

      当然,这些都不重要,除非你想用更大的数组做一些很酷的事情。然后就可以了。

      鉴于目前 JS 中似乎仍然没有设置数组初始容量的选项,所以我使用以下...

      var newArrayWithSize = function(size) {
        this.standard = this.standard||[];
        for (var add = size-this.standard.length; add>0; add--) {
         this.standard.push(undefined);// or whatever
        }
        return this.standard.slice(0,size);
      }
      

      需要权衡取舍:

      • 此方法第一次调用函数所需的时间与其他方法一样长,但后续调用的时间非常短(除非要求更大的数组)。
      • standard 数组会永久保留与您请求的最大数组一样多的空间。

      但是,如果它与您正在做的事情相吻合,就会有回报。 非正式的时间安排

      for (var n=10000;n>0;n--) {var b = newArrayWithSize(10000);b[0]=0;}
      

      非常快(考虑到 n=1000000 大约需要 5 秒,10000 大约需要 50 毫秒),并且

      for (var n=10000;n>0;n--) {
        var b = [];for (var add=10000;add>0;add--) {
          b.push(undefined);
        }
      }
      

      一分钟多一点(10000 在同一个 chrome 控制台上大约需要 90 秒,或者慢大约 2000 倍)。 这不仅仅是分配,还有 10000 次推送、for 循环等。

      【讨论】:

      • 如果每次到达数组末尾时都复制它,插入 n 个值的复杂度仍然是 O(n),因此复杂度没有差异(但速度较慢)。
      • 很好地抓住了@TomerWolberg,我改变了我的措辞。推送方法是否比按切片初始化/复制单个成员的复杂性更高,这是一个问题。 AFAIK,它们都是恒定的时间,至少它们可能是这样,所以我改用了“速度”这个词。
      【解决方案7】:

      使用ES2015 .fill(),您现在可以简单地执行以下操作:

      // `n` is the size you want to initialize your array
      // `0` is what the array will be filled with (can be any other value)
      Array(n).fill(0)
      

      Array.apply(0, new Array(n)).map(i =&gt; value)简洁多了

      可以将0 放到.fill() 中并在不带参数的情况下运行,这将用undefined 填充数组。 (然而,这在 Typescript 中会失败

      【讨论】:

      • @AralRoca 你总是可以使用MDN页面上提供的Polyfill,或者考虑使用Modernizr
      • 是的,请在评论前尝试一下
      • @GarretWilson 和 @Christian 这是in the specWhen Array is called as a function rather than as a constructor, it also creates and initializes a new Array object. Thus the function call Array(…) is equivalent to the object creation expression new Array(…) with the same arguments
      • @AP.,零是多余的。做Array(n).fill()
      • @Pacerier 我不认为 0 是多余的。根据es6类型定义,这是函数签名:fill(value: T, start?: number, end?: number): this;因此填充值似乎不是可选的。 Typescript 编译将失败,因为上面写了函数调用。
      【解决方案8】:

      ES6 引入了Array.from,它允许您从任何“array-like”iterables 对象创建Array

      Array.from({length: 10}, (x, i) => i);
      // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
      

      在这种情况下,{length: 10} 表示 “类数组” 对象的最小定义:一个只定义了 length 属性的空对象。

      Array.from 允许第二个参数映射到结果数组。

      【讨论】:

      【解决方案9】:
      • Array(5) 为您提供长度为 5 但没有值的数组,因此您无法对其进行迭代。

      • Array.apply(null, Array(5)).map(function () {}) 为您提供一个长度为 5 且未定义为值的数组,现在可以对其进行迭代。

      • Array.apply(null, Array(5)).map(function (x, i) { return i; }) 为您提供长度为 5 且值为 0、1、2、3、4 的数组。

      • Array(5).forEach(alert) 什么都不做,Array.apply(null, Array(5)).forEach(alert) 给你 5 个警报

      • ES6 给了我们Array.from 所以现在你也可以使用Array.from(Array(5)).forEach(alert)

      • 如果你想用某个值进行初始化,这些很好知道...
        Array.from('abcde'), Array.from('x'.repeat(5))
        Array.from({length: 5}, (v, i) =&gt; i) // gives [0, 1, 2, 3, 4]

      【讨论】:

      • 这就是我要找的。我想在逻辑序列上应用地图;这应该这样做。谢谢!
      • 为什么 Array.apply() 给它 undefined 作为值?
      • @wdanxna 当Array 被赋予多个参数时,它会遍历arguments 对象并将每个值显式应用于新数组。当您使用数组或具有长度属性的对象调用Array.apply 时,Array 将使用长度显式设置新数组的每个值。这就是为什么Array(5) 给出了一个包含 5 个省略号的数组,而Array.apply(null, Array(5)) 给出了一个包含 5 个未定义的数组。有关详细信息,请参阅this 答案。
      • Array(5) 和 new Array(5) 有区别吗?
      • Array.apply(null, Array(5)) 也可以写成Array.apply(null, {length: 5})。确实没有太大区别,但是后者很明确,目的是创建一个length 5的数组,而不是一个包含5的数组
      【解决方案10】:

      您可以使用array.length = youValue设置数组长度

      原来如此

      var myArray = [];
      myArray.length = yourValue;
      

      【讨论】:

        【解决方案11】:

        这是另一个解决方案

        var arr = Array.apply( null, { length: 4 } );
        arr;  // [undefined, undefined, undefined, undefined] (in Chrome)
        arr.length; // 4
        

        apply()的第一个参数是一个this对象绑定,这里我们不关心,所以设置为null

        Array.apply(..) 正在调用Array(..) 函数并将{ length: 3 } 对象值作为其参数展开。

        【讨论】:

        • 我不明白为什么这个答案被否决了。这实际上是一个不错的 hack。这意味着您不能使用new Array(n) 来初始化像new Array(n).map(function(notUsed,index){...}) 这样的数组,但是使用这种方法,正如@zangw 所提到的,您可以做到。来自我的 +1。
        • 这太棒了,这正是我 5 年后一直在寻找的东西!向你致敬,我的朋友。如果您想知道,我正在使用它来根据作为道具传入的 int 值来渲染一定数量的孩子。 Array.apply(null, { length: childrenLength }).map((notUsed, i) =&gt; (&lt;div key={i} /&gt;)
        【解决方案12】:
        1. 为什么要初始化长度?理论上没有必要这样做。它甚至会导致令人困惑的行为,因为所有使用length 来确定数组是否为空的测试都会报告该数组不为空。
          Sometests 表明设置初始大型数组的长度可以如果之后填充数组会更有效,但性能增益(如果有的话)似乎因浏览器而异。

        2. jsLint 不喜欢new Array(),因为构造函数不明确。

          new Array(4);
          

          创建一个长度为4的空数组。但是

          new Array('4');
          

          创建一个数组包含值 '4'

        关于您的评论:在 JS 中您不需要初始化数组的长度。它动态增长。您可以将长度存储在某个变量中,例如

        var data = [];
        var length = 5; // user defined length
        
        for(var i = 0; i < length; i++) {
            data.push(createSomeObject());
        }
        

        【讨论】:

        • 数组中对象的数量是用户定义的,所以我让用户选择一个数字,然后用那么多槽初始化数组。然后我运行一个for 循环,该循环遍历数组的长度并填充它。我想在 JavaScript 中还有其他方法可以做到这一点,所以真正回答“我为什么要这样做?”是“因为用其他语言编程时形成的旧习惯。” :)
        • @mlms 只需让 for 循环遍历用户定义的数字,而不必设置数组长度然后遍历它。这对你来说不是更有意义吗?
        • 为什么要初始化长度?理论上没有必要这样做。并且所有使用长度来确定数组是否为空的测试都会失败。
          嗯,性能 可能吗?设置数组的预先存在的元素比动态添加要快。
        • @codehead:回应这句话:“设置数组的预先存在的元素比动态添加它更快。”:注意new Array(10) 不会创建一个包含 10 个未定义元素的数组。它只是创建一个长度为 10 的空数组。请参阅此答案以了解令人讨厌的事实:stackoverflow.com/questions/18947892/…
        • “你为什么要……”在 SO 上被问了这么多次。 FWIW 我认为当有人发布问题时,可以合理地假设他们心中有一个合法的用例并从那里开始。
        【解决方案13】:
        var arr=[];
        arr[5]=0;
        alert("length="+arr.length); // gives 6
        

        【讨论】:

        • 是的,但是 console.log(arr) 给出 [5: 0] 这是一个稀疏数组,可能不是我们想要的。
        • 解释为什么您可能不想要稀疏数组,正如@dreftymac 所说:稀疏数组(包括空单元格)可能会出现意外行为。例如,如果我运行以下代码:js const foo = []; foo[10] = undefined; foo 的值不是[undefined, undefined undefined, ...],而是[empty * 9, undefined]。如果您尝试运行任何顺序数组方法(forEachmapreduce),那么您会意识到它实际上并没有遍历空项。它们只是死空间。
        【解决方案14】:

        令我惊讶的是,还没有建议的功能性解决方案允许您在一行中设置长度。以下基于UnderscoreJS:

        var test = _.map(_.range(4), function () { return undefined; });
        console.log(test.length);
        

        由于上述原因,除非我想将数组初始化为特定值,否则我会避免这样做。有趣的是,还有其他实现 range 的库,包括 Lo-dash 和 Lazy,它们可能具有不同的性能特征。

        【讨论】:

        • 这里真的不需要下划线黑魔法。依赖就像糖,一开始看起来很甜,但不知不觉就得了糖尿病。
        【解决方案15】:

        (这可能是一个更好的评论,但太长了)

        所以,读完这篇文章后,我很好奇预分配是否真的更快,因为理论上它应该是。然而,这篇博客给出了一些反对它的建议http://www.html5rocks.com/en/tutorials/speed/v8/

        所以仍然不确定,我对其进行了测试。事实证明,它实际上似乎更慢。

        var time = Date.now();
        var temp = [];
        for(var i=0;i<100000;i++){
            temp[i]=i;
        }
        console.log(Date.now()-time);
        
        
        var time = Date.now();
        var temp2 = new Array(100000);
        for(var i=0;i<100000;i++){
            temp2[i] = i;
        }
        console.log(Date.now()-time); 
        

        此代码在几次随意运行后会产生以下结果:

        $ node main.js 
        9
        16
        $ node main.js 
        8
        14
        $ node main.js 
        7
        20
        $ node main.js 
        9
        14
        $ node main.js 
        9
        19
        

        【讨论】:

        • 有趣,这似乎出乎意料,所以我创建了a JSPerf test。 Chrome 结果与您的 Node 测试相匹配,但是当您为数组预先分配空间时,Firefox 和 IE 会稍微快一些。
        • @MichaelMartin-Smucker 等等...这是否意味着 V8 实际上并没有完全优化和完美?!?!? :P
        • @MichaelMartin-Smucker - 我刚刚在 Chrome 版本 42.0.2311.90 中运行了你的 jsperf(具体来说 - 在 Windows Server 2008 R2 / 7 64 位上的 Chrome 42.0.2311.90 32 位中进行测试)和动态大小的数组慢了 69%。
        • 同上。 (我写了原始节点测试)在 chrome 42.0.2311.90 上,windows 的动态大小慢了 81% :)。但我们最初的测试是一年多前的事了。时间一直在溜走,溜走......
        • 令人着迷...从 jsperf 上的结果来看,预分配在 Chrome 38 中得到了巨大的提升。未来!
        【解决方案16】:

        数组构造函数有一个ambiguous syntax,JSLint毕竟只是伤了你的感情。

        此外,您的示例代码已损坏,第二个 var 语句将引发 SyntaxError。您正在设置数组test 的属性length,因此不需要另一个var

        就您的选择而言,array.length 是唯一“干净”的选项。问题是,为什么首先需要设置大小?尝试重构您的代码以摆脱这种依赖关系。

        【讨论】:

        • 糟糕,看第二个var test。那是我草率的复制和粘贴。
        • @IvoWetzel,您想设置大小以提高性能。 JS 数组是dynamic arrays。如果你不设置大小(或者更确切地说,它的容量),JS 将分配一个默认大小的数组。如果您随后添加的元素超出了容量范围,它将不得不增大数组,这意味着它将在内部分配一个新数组,然后复制所有元素。
        【解决方案17】:

        这会将长度属性初始化为 4:

        var x = [,,,,];
        

        【讨论】:

        • 这很聪明。它没有构造函数的灵活性,因为您不能使用变量来设置长度,但它确实回答了我最初所说的问题,所以 +1。
        • 想象一下,当性能真的很重要时,为 300 个项目做这件事!
        • 在预分配如此小尺寸的数组时,性能提升可能不大。当创建更大的数组时,性能差异会更好。在这些情况下,用逗号初始化它们会有点不知所措。
        • 我认为这会在不同的浏览器中产生不同的结果,因为最后的逗号,有时是 4,有时是 5,见stackoverflow.com/questions/7246618/…
        • var size = 42; var myArray = eval("["+",".repeat(size)+"]"); 怎么样? (没那么严重;)
        【解决方案18】:

        您不应该使用new Array 的原因如下代码所示:

        var Array = function () {};
        
        var x = new Array(4);
        
        alert(x.length);  // undefined...
        

        其他一些代码可能与 Array 变量混淆。我知道有人会写这样的代码有点牵强,但仍然......

        另外,正如 Felix King 所说,界面有点不一致,可能会导致一些非常难以追踪的错误。

        如果你想要一个长度 = x 的数组,填充未定义(就像new Array(x) 会做的那样),你可以这样做:

        var x = 4;
        var myArray = [];
        myArray[x - 1] = undefined;
        
        alert(myArray.length); // 4
        

        【讨论】:

        • 根据这个推理,您不应该使用alertundefined,因为其他一些代码可能会混淆它们。第二个示例的可读性不如new Array(4),并且不会给您相同的结果:jsfiddle.net/73fKd
        • 这个答案很奇怪
        • 你无法避免人们在 JavaScript 中搞乱全局对象;根本不这样做或使用人们这样做的框架。
        • 当前在 chrome repl 中:j = new Array(4); j.长度; // 结果为 4
        猜你喜欢
        • 2019-01-25
        • 2012-04-24
        • 2010-12-12
        • 2013-07-05
        • 1970-01-01
        • 2013-06-24
        • 1970-01-01
        • 2021-08-23
        • 1970-01-01
        相关资源
        最近更新 更多