JavaScriptの「新しいArray(n)」と「Array.prototype.map」の奇妙さ


209

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

Firebugを起動すると:

var x = new Array(3)
console.log(x) 
// [undefined, undefined, undefined]

var y = [undefined, undefined, undefined]
console.log(y) 
// [undefined, undefined, undefined]

console.log( x.constructor == y.constructor) // true

console.log( 
  x.map(function() { return 0; })
)
// [undefined, undefined, undefined]

console.log(
  y.map(function() { return 0; })
)
// [0, 0, 0]

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


配列リテラル表記から見たのと同じ結果は得られません。私はまだ0の代わりに未定義を取得します。のようなものを設定した場合にのみ0の結果var y = x.map(function(){return 0; });を取得し、新しいArray()メソッドと配列リテラルの両方に対してこれを取得します。Firefox 4とChromeでテストしました。
RussellUresti

これはChromeでも
無効化

回答:


125

最初の例は

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番目のマップは実行できたと思います。


86
MDC(強調鉱山)」mapために、アレイ内の各要素に対して一度ずつ設けられたコールバック関数を呼び出し、その結果から新しい配列を構築する。callbackのみの値を割り当てた配列のインデックスに対して呼び出され、それが呼び出されていません削除された、または値が割り当てられていないインデックスの場合。」この場合、xの値は明示的に割り当てられた値ではありませんyが、の値であっても割り当てられましたundefined
Martijn、2011年

2
それで、JavaScriptが失敗して、それが未定義のポインタであるか、未定義へのポインタであるかどうかを確認できないのでしょうか?つまり(new Array(1))[0] === [undefined][0]
Trevor Norris

まあ、未定義の配列は、未定義のオブジェクトへのポインタの配列とは異なります。undefinesの配列はnull値の配列[null、null、null]のようになり、undefinedへのポインターの配列は[343423、343424、343425]のようになり、nullとnullとnullを指すようになります。2番目のソリューションにはメモリアドレスを指す実際のポインタがあり、最初のソリューションはどこも指しません。それがJSの失敗である場合はおそらく議論の問題ですが、ここではそうではありません;)
DavidMårtensson2012

4
@TrevNorris、あなたはそれで簡単にテストすることができhasOwnPropertyない限り、hasOwnProperty自身がバグを持っています(new Array(1)).hasOwnProperty(0) === false[undefined].hasOwnProperty(0) === true。実際、in0 in [undefined] === trueとでもまったく同じことができます0 in new Array(0) === false
squid314 2015年

3
JavaScriptで「未定義のポインター」について話すと、問題が混乱します。あなたが探している言葉は「エリジョン」です。x = new Array(3);はと同じですがx = [,,,];、ではありませんx = [undefined, undefined, undefined]
Matt Kantor、2015

118

配列の長さを知っているだけで、項目を変換する必要があるというタスクがありました。私はこのようなことをしたかった:

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

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

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

しかし、それはうまくいきませんでした:(上記のJonathan Lonowskiの回答をいくつか参照してください)

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

let arr = 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));


28
あなたの注目に値する状態にする必要はありませんundefined.fill()に非常にわずかにコードを簡素化し、方法let arr = new Array(10).fill().map((val,idx) => idx);
ヤン・イブ

同様に使用できますArray.from(Array(10))

84

ES6を使用すると[...Array(10)].map((a, b) => a)、すばやく簡単に行うことができます。


9
ES6より前のバージョンで使用できますnew Array(10).fill()。同じ結果[...Array(10)]
Molomby 2017

大規模な配列では、spread構文によって問題が発生するため、回避することをお

または[...Array(10).keys()]
Chungzuwalla

25

ES6ソリューション:

[...Array(10)]

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


6
Array(10).fill("").map( ...Typescript 2.9で私のために働いたものです
ibex

19

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

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

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


17

MDCページからmap

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

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

これはすべての反復法で同じだと思います


8

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-arrayaccumulation ElementListステップ5を参照してください

メソッド mapeverysomeforEachslicereducereduceRightfilter配列のことでインデックスプロパティをチェックしHasProperty、内部の方法ので、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);

とてもいい説明。
Dheeraj Rao

7

これを説明する最良の方法は、Chromeの処理方法を確認することです。

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

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

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

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


4

ちょうどこれに遭遇しました。確かに便利に使えますArray(n).map

Array(3) おおよその収量 {length: 3}

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

map()実装は、定義されたプロパティにのみ作用します。


3

バグではありません。これが、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コンストラクターと数値引数で新しく構築された配列で呼び出されても、何も実行しません(コールバックを呼び出しません)。


壊れると定義されていますか?いつundefinedまでも続く3つの要素の配列を生成するように設計されていますか?
オービットでの軽さのレース

はい、「永久に」の部分を除いて、それは正しいです。その後、要素に値を割り当てることができます。
とがった

3
そのため、x = []代わりに使用する必要がありますx = new Array()
Rocket Hazmat 2011年

3

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

かなり醜いですが、少なくとも問題と意図は非常に明確に伝えられています。


1

問題はなぜかというと、これはJSの設計方法に関係しています。

この動作を説明するために考えられる主な理由は2つあります。

  • パフォーマンス:考えるx = 10000new Array(x)コンストラクタが持つ配列を埋めるために0から10000までのループを回避することが賢明であるundefined値。

  • 暗黙的に「未定義」:与えるa = [undefined, undefined]b = new Array(2)a[1]そしてb[1]両方のリターンはなりますundefinedが、a[8]b[8]にも戻るでしょうundefined、彼らが範囲外の方にも。

結局のところ、この表記empty x 3は、明示的に宣言されていないためにとにかく長いundefined値のリストを設定および表示しないようにするためのショートカットundefinedです。

注:与えられた配列a = [0]and a[9] = 9console.log(a)を返し(10) [0, empty x 8, 9]、明示的に宣言された2つの値の差を返すことでギャップを自動的に埋めます。


1

他の回答で完全に説明されている理由により、Array(n).map動作しません。ただし、ES2015 Array.fromではマップ関数を受け入れます。

let array1 = Array.from(Array(5), (_, i) => i + 1)
console.log('array1', JSON.stringify(array1)) // 1,2,3,4,5

let array2 = Array.from({length: 5}, (_, i) => (i + 1) * 2)
console.log('array2', JSON.stringify(array2)) // 2,4,6,8,10


0

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

単純な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]
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.