ChatGPT解决这个技术问题 Extra ChatGPT

如何在 JavaScript 中初始化数组的长度?

我读过的关于 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;
standard js also advise 反对一般使用 new Array(),但可以指定大小。我认为这一切都归结为整个上下文的代码一致性。
对于那些希望预分配更严格的数组结构的人,有 Typed Arrays。请注意,performance benefits may vary
请查看以下提供不同解决方案的基准:measurethat.net/Benchmarks/Show/9721/0/…

Q
Qwerty

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) => i) // 给出 [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 的数组
F
Felix Kling

为什么要初始化长度?理论上没有必要这样做。它甚至会导致令人困惑的行为,因为所有使用长度来确定数组是否为空的测试都会报告该数组不为空。一些测试表明,如果稍后填充数组,则设置大型数组的初始长度可能会更有效,但性能增益(如果有的话)似乎因浏览器而异。 jsLint 不喜欢 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 中还有其他方法可以做到这一点,所以真正回答“我为什么要这样做?”是“因为用其他语言编程时形成的旧习惯。” :)
为什么要初始化长度?理论上没有必要这样做。并且所有使用长度来确定数组是否为空的测试都将失败。
嗯,也许是性能?设置数组的预先存在的元素比动态添加它要快。
@codehead:回应这句话:“设置数组的预先存在的元素比动态添加它更快。”:请注意 new Array(10) 不会创建具有 10 个未定义元素的数组。它只是创建一个长度为 10 的空数组。请参阅此答案以了解令人讨厌的事实:stackoverflow.com/questions/18947892/…
“你为什么要……”在 SO 上被问了很多次。 FWIW 我认为当有人发布问题时,可以合理地假设他们有一个合法的用例并从那里开始。
这是另一个与海报争论的“答案”,从未真正回答过这个问题。如果您不想初始化数组的长度,请不要在代码中这样做。否则,不要告诉别人他们能做什么,不能做什么。如果你不能回答这个问题,那就不要。
A
AP.

使用 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 => 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 编译将失败。
V
Vlad
[...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 => 0); 是一个很好的解决方案,对我很有帮助!稍作修改,它就可以用来创建一个对整数进行计数的数组(就像你的打字稿安全示例一样):[...Array(6)].map((x, i) => i); 结果为 [0, 1, 2, 3, 4, 5]
有时我不在 map 函数中使用“元素/项目/第一个参数”,因为我只需要索引并且我正在寻找一个最佳实践来命名该参数。我不想将它命名为“x”或“未使用”等。所以,使用下划线实际上是一个很棒的主意!谢谢! map((_, i) => i)
刚刚注意到你的答案最短,非常好
Array(Infinity).fill(0) 创造你的宇宙! Array(Infinity).fill(Infinity) 你是上帝!
T
Teocci

最短的:

让 arr = [...Array(10)];控制台.log(arr);


赞成,但需要注意的是,您绝对不应该在 JS 中使用 [ 开始一行而不使用 ;,因为它会尝试取消引用上面的行。因此,在代码的任何部分可预见地使用此行的安全方法是:;[...Array(1000)]//.whateverOpsNeeded()
并不真地。试试开发者工具。 [...Array(5)].map((item, index) => ({ 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 错误。无论如何,这与这个答案无关。
A
AaronDancer

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 允许第二个参数映射到结果数组。


@dain 我认为 [...Array(10)] 更好stackoverflow.com/a/47929322/4137472
为什么更好?不鼓励使用 Array 构造函数,一些 linter 也可能会抱怨
D
Domi

稀疏数组来了! 🥳 [2021]

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

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

https://i.stack.imgur.com/aEmtE.png

https://i.stack.imgur.com/db75k.png

但是,有一个问题

一方面,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 那里肯定有错字。有趣的是,从那以后很多事情都发生了变化,所以我更新了整个事情
您也可以只调用 .fill 而不带参数,然后您可以遍历数组:new Array(10).fill().forEach(i => console.log(i))
Š
Šime Vidas

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

var x = [,,,,];

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

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

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

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


这里真的不需要下划线黑魔法。依赖就像糖,一开始看起来很甜,但在不知不觉中,你就会患上糖尿病。
z
zangw

这是另一个解决方案

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) => (<div key={i} />)
w
wils

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

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

鉴于目前 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);
}

有一些权衡:

该方法第一次调用该函数所需的时间与其他方法一样长,但以后调用的时间很少(除非要求更大的数组)。

标准阵列会永久保留与您要求的最大阵列一样多的空间。

但是,如果它与您正在做的事情相符,则可能会有所回报。非正式定时看跌期权

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,它们都是恒定的时间,至少它们可能是这样,所以我使用了“速度”这个词。
j
j03m

(这可能作为评论更好,但太长了)

所以,读完这篇文章后,我很好奇预分配是否真的更快,因为理论上它应该是。但是,此博客提供了一些反对它的提示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 中得到了巨大的提升。未来!
u
user1067305
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),那么您会意识到它实际上并没有遍历空项。它们只是死角。
m
mohan babu

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

const intialArray = new Array(specify the value);


D
Domi

数组构造函数有一个 ambiguous syntax,毕竟 JSLint 只会伤害你的感受。

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

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


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

除了其他人的答案,另一个聪明的方法是使用Float32Array创建一个数组并对其进行迭代。

为此,从 Float32Array 创建一个具有所需长度的实例,如下所示:

new Float32Array(5)

此代码返回一个类似数组,您可以使用 Array.from() 将其转换为数组:

Array.from(new Float32Array(5)) // [0, 0, 0, 0, 0]

您还可以使用 fill() 更改项目的值:

Array.from(new Float32Array(5).fill(2)) // [2, 2, 2, 2, 2]

当然,您可以对其进行迭代:

Array.from(new Float32Array(5)).map(item => /* ... */ )

W
Wilt

在大多数答案中,建议 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>

a
ajoposor

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

所以它会是

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

n
nickf

以下代码说明了您不应使用 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