web-dev-qa-db-ja.com

JavaScript「new Array(n)」および「Array.prototype.map」の奇妙さ

Firefox-3.5.7/Firebug-1.5.3およびFirefox-3.6.16/Firebug-1.6.2でこれを確認しました

Firebugを起動すると:

_    >>> x = new Array(3)
    [undefined, undefined, undefined]
    >>> y = [undefined, undefined, undefined]
    [undefined, undefined, undefined]

    >>> x.constructor == y.constructor
    true

    >>> x.map(function(){ return 0; })
    [undefined, undefined, undefined]
    >>> y.map(function(){ return 0; })
    [0, 0, 0]
_

何が起きてる?これはバグですか、それともnew Array(3)の使用方法を誤解していますか?

183
rampion

最初の例のようです

x = new Array(3);

未定義のポインターを持つ配列を作成します。

2番目は、3つの未定義オブジェクトへのポインターを持つ配列を作成します。この場合、それら自身のポインターは未定義ではなく、それらが指すオブジェクトのみです。

y = [undefined, undefined, undefined]
// The following is not equivalent to the above, it's the same as new Array(3)
y = [,,,];

マップは配列内のオブジェクトのコンテキストで実行されるため、最初のマップは関数の実行にまったく失敗し、2番目のマップは実行に失敗すると考えています。

112

配列の長さしかわからず、アイテムを変換するために必要なタスクがありました。私はこのようなことをしたかった:

_let arr = new Array(10).map((val,idx) => idx);
_

このような配列をすばやく作成するには:

_[0,1,2,3,4,5,6,7,8,9]
_

しかし、それはうまくいきませんでした。ジョナサン・ロノウスキーの答えをご覧ください。

解決策は、 Array.prototype.fill() を使用して、配列項目を任意の値(未定義であっても)で埋めることです。

_let arr = new Array(10).fill(undefined).map((val,idx) => idx);
_
console.log(new Array(10).fill(undefined).map((val, idx) => idx));

更新

別の解決策は次のとおりです。

_let arr = Array.apply(null, Array(10)).map((val, idx) => idx);
_
console.log(Array.apply(null, Array(10)).map((val, idx) => idx));
96
cstuncsik

ES6では、[...Array(10)].map((a, b) => a)をすばやく簡単に実行できます。

74
Manuel Beaudru

ES6ソリューション:

[...Array(10)]

ただし、TypeScript(2.3)では機能しません。

21
Serge Intern

配列は異なります。違いは、new Array(3)は長さ3のプロパティを持たない配列を作成するのに対し、[undefined, undefined, undefined]は、それぞれがundefinedの値を持つ、「0」、「1」、「2」と呼ばれる3つのプロパティと3つのプロパティを持つ配列を作成します。 in演算子を使用して違いを確認できます。

"0" in new Array(3); // false
"0" in [undefined, undefined, undefined]; // true

これは、JavaScriptでネイティブオブジェクトの存在しないプロパティの値を取得しようとすると、undefinedを返します(参照しようとしたときにエラーがスローされるのではなく)これは、プロパティが以前に明示的にundefinedに設定されている場合に取得するものと同じです。

17
Tim Down

map のMDCページから:

[...] callbackは、値が割り当てられた配列のインデックスに対してのみ呼び出されます。 [...]

_[undefined]_は実際にインデックスにセッターを適用し、mapが反復するのに対して、new Array(1)はデフォルト値のundefinedでインデックスを初期化します。 mapはスキップします。

これはすべての 繰り返し方法 で同じだと思います。

14

これを説明する最良の方法は、Chromeがそれを処理する方法を調べることです。

>>> x = new Array(3)
[]
>>> x.length
3

実際に起こっているのは、new Array()が長さ3の空の配列を返しているが、値は返していないということです。したがって、技術的に空の配列でx.mapを実行する場合、設定するものはありません。

Firefoxは、値がなくても、空のスロットにundefinedを「埋める」だけです。

これは明らかにバグではないと思います。何が起こっているかを表現する方法としては不十分です。配列には実際には何も存在しないことが示されているため、Chromeの「より正しい」と思います。

7
helloandre

ECMAScript第6版仕様。

new Array(3)はプロパティlengthのみを定義し、_{length: 3}_などのインデックスプロパティを定義しません。 https://www.ecma-international.org/ecma-262/6.0/index.html#sec-array-len ステップ9。

_[undefined, undefined, undefined]_は、_{0: undefined, 1: undefined, 2: undefined, length: 3}_のようなインデックスプロパティと長さプロパティを定義します。 https://www.ecma-international.org/ecma-262/6.0/index.html#sec-runtime-semantics-arrayaccumulationElementListステップ5。

メソッドmapeverysomeforEachslicereducereduceRight、配列のfilterHasProperty内部メソッドによってインデックスプロパティをチェックするため、new Array(3).map(v => 1)はコールバックを呼び出しません。

詳細については、 https://www.ecma-international.org/ecma-262/6.0/index.html#sec-array.prototype.map を参照してください

直し方?

_let a = new Array(3);
a.join('.').split('.').map(v => 1);

let a = new Array(3);
a.fill(1);

let a = new Array(3);
a.fill(undefined).map(v => 1);

let a = new Array(3);
[...a].map(v => 1);
_
6
wenshin

ただこれに遭遇しました。 Array(n).mapを使用できると便利です。

Array(3)はおよそ{length: 3}

[undefined, undefined, undefined]は、番号付きプロパティを作成します。
{0: undefined, 1: undefined, 2: undefined, length: 3}

Map()実装は、定義されたプロパティに対してのみ機能します。

4
Vezquex

バグではありません。これが、Arrayコンストラクターが機能するように定義されている方法です。

MDCから:

Arrayコンストラクターで単一の数値パラメーターを指定する場合、配列の初期長を指定します。次のコードは、5つの要素の配列を作成します。

_var billingMethod = new Array(5);
_

Arrayコンストラクターの動作は、単一のパラメーターが数値かどうかによって異なります。

.map()メソッドには、値が明示的に割り当てられている配列の反復要素のみが含まれます。 undefinedを明示的に割り当てても、値は反復に含めるのに適格と見なされます。それは奇妙に思えますが、本質的にオブジェクトの明示的なundefinedプロパティと欠落しているプロパティの違いです。

_var x = { }, y = { z: undefined };
if (x.z === y.z) // true
_

オブジェクトxには「z」というプロパティがありません。オブジェクトyにはあります。ただし、どちらの場合も、プロパティの「値」はundefinedであるように見えます。配列では、状況は似ています:lengthの値は、ゼロから_length - 1_までのすべての要素に値の割り当てを暗黙的に実行します。したがって、.map()関数は、Arrayコンストラクターと数値引数を使用して新しく構築された配列で呼び出された場合、何もしません(コールバックを呼び出しません)。

3
Pointy

配列に値を簡単に入力するためにこれを実行している場合、ブラウザのサポート上の理由で fill を使用できず、実際にforループを実行したくない場合は、x = new Array(3).join(".").split(".").map(...これは空の文字列の配列を提供します。

かなりいですが、少なくとも問題と意図ははっきりと伝えられています。

3
Alex

回避策として、簡単なユーティリティメソッドを次に示します。

単純なmapFor

function mapFor(toExclusive, callback) {
    callback = callback || function(){};
    var arr = [];
    for (var i = 0; i < toExclusive; i++) {
        arr.Push(callback(i));
    }
    return arr;
};

var arr = mapFor(3, function(i){ return i; });
console.log(arr); // [0, 1, 2]
arr = mapFor(3);
console.log(arr); // [undefined, undefined, undefined]

完全な例

オプションの開始インデックスを指定できる、より完全な例(健全性チェック付き)を次に示します。

function mapFor() {
var from, toExclusive, callback;
if (arguments.length == 3) {
    from = arguments[0];
    toExclusive = arguments[1];
    callback = arguments[2];
} else if (arguments.length == 2) {
    if (typeof arguments[1] === 'function') {
        from = 0;
        toExclusive = arguments[0];
        callback = arguments[1];
    } else {
        from = arguments[0];
        toExclusive = arguments[1];
    }
} else if (arguments.length == 1) {
    from = 0;
    toExclusive = arguments[0];
}

callback = callback || function () {};

var arr = [];
for (; from < toExclusive; from++) {
    arr.Push(callback(from));
}
return arr;
}

var arr = mapFor(1, 3, function (i) { return i; });
console.log(arr); // [1, 2]
arr = mapFor(1, 3);
console.log(arr); // [undefined, undefined]
arr = mapFor(3);
console.log(arr); // [undefined, undefined, undefined]

カウントダウン

コールバックに渡されるインデックスを操作すると、逆方向にカウントできます。

var count = 3;
var arr = arrayUtil.mapFor(count, function (i) {
    return count - 1 - i;
});
// arr = [2, 1, 0]
0
DJDaveMark