JavaScript“ new 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 นี่อาจจะถูกกำหนดในภาษาแม้ว่ามันจะไม่เหมาะสมดังนั้นฉันหวังว่ามันจะไม่เป็นจริง
Hashbrown

คำตอบ:


125

ปรากฏว่าตัวอย่างแรก

x = new Array(3);

สร้างอาร์เรย์ด้วยตัวชี้ที่ไม่ได้กำหนด

และอันที่สองสร้างอาร์เรย์ที่มีพอยน์เตอร์ไปยังวัตถุที่ไม่ได้กำหนด 3 ตัวในกรณีนี้พอยน์เตอร์ที่ตัวเองไม่ได้ถูกกำหนดจะมีเฉพาะวัตถุที่พวกมันชี้ไป

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

ในขณะที่แผนที่ทำงานในบริบทของวัตถุในอาเรย์ฉันเชื่อว่าแผนที่แรกไม่สามารถเรียกใช้ฟังก์ชั่นได้เลยในขณะที่แผนที่ที่สองทำงานได้


86
จากMDC (การเน้นของฉัน): " mapเรียกใช้ฟังก์ชันการเรียกกลับที่จัดเตรียมไว้หนึ่งครั้งสำหรับแต่ละองค์ประกอบในอาร์เรย์ตามลำดับและสร้างอาร์เรย์ใหม่จากผลลัพธ์callbackจะถูกเรียกใช้เฉพาะกับดัชนีของอาร์เรย์ที่มีค่าที่กำหนดไว้เท่านั้น สำหรับดัชนีที่ถูกลบหรือที่ไม่เคยมีการกำหนดค่าไว้ " ในกรณีนี้x's ค่ายังไม่ได้กำหนดอย่างชัดเจนค่าในขณะที่y' s undefinedได้รับมอบหมายถึงแม้ว่ามันจะเป็นค่า
Martijn

2
ดังนั้นมันจึงเป็นจาวาสคริปต์ที่ล้มเหลวที่เป็นไปไม่ได้ที่จะตรวจสอบว่ามันเป็นตัวชี้ที่ไม่ได้กำหนดหรือตัวชี้ไปที่ไม่ได้กำหนด? (new Array(1))[0] === [undefined][0]ผมหมายถึง
Trevor Norris

อาร์เรย์ที่ไม่ได้กำหนดนั้นแตกต่างจากอาร์เรย์ของตัวชี้ไปยังวัตถุที่ไม่ได้กำหนด อาร์เรย์ของ undefines จะเหมือนกับอาร์เรย์ของค่า null, [null, null, null] ในขณะที่อาร์เรย์ของตัวชี้ไปยังไม่ได้กำหนดจะเป็น [343423, 343424, 343425] poining เป็น null และ null และ null โซลูชันที่สองมีพอยน์เตอร์จริงที่ชี้ไปยังที่อยู่หน่วยความจำในขณะที่วิธีแรกไม่ได้ชี้ไปที่ใด ถ้าเกิดว่าเป็นความล้มเหลวของ JS อาจจะเป็นเรื่อง o การอภิปราย แต่ไม่ได้ที่นี่;)
เดวิดMårtensson

4
@TrevNorris คุณสามารถทดสอบว่าด้วยการhasOwnPropertyยกเว้นในกรณีที่hasOwnPropertyตัวเองมีข้อผิดพลาด: และ(new Array(1)).hasOwnProperty(0) === false [undefined].hasOwnProperty(0) === trueในความเป็นจริงคุณสามารถทำเช่นเดียวกันกับที่แน่นอนin: และ0 in [undefined] === true 0 in new Array(0) === false
squid314

3
การพูดถึง "พอยน์เตอร์ที่ไม่ได้กำหนด" ใน JavaScript ทำให้เกิดความสับสน คำที่คุณกำลังมองหาคือ"elisions" x = new Array(3);คือการเทียบเท่าไม่x = [,,,]; x = [undefined, undefined, undefined]
Matt Kantor

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);
Yann Eves

ในทำนองเดียวกันคุณสามารถใช้Array.from(Array(10))

84

ด้วย ES6 คุณสามารถทำได้[...Array(10)].map((a, b) => a)ง่ายและรวดเร็ว!


9
Pre-ES6 ที่คุณสามารถnew Array(10).fill()ใช้ได้ ผลเช่นเดียวกับ[...Array(10)]
Molomby

ด้วยอาร์เรย์ขนาดใหญ่ไวยากรณ์การแพร่กระจายจะสร้างปัญหาดังนั้นจึงควรหลีกเลี่ยง

หรือ[...Array(10).keys()]
Chungzuwalla


19

อาร์เรย์มีความแตกต่าง แตกต่างก็คือnew Array(3)สร้างอาร์เรย์ที่มีความยาวสาม แต่คุณสมบัติไม่ในขณะที่[undefined, undefined, undefined]สร้างอาร์เรย์ที่มีความยาวสามและสามคุณสมบัติที่เรียกว่า "0", "1" และ "2" undefinedแต่ละคนมีค่าของ คุณสามารถเห็นความแตกต่างโดยใช้inโอเปอเรเตอร์:

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

สิ่งนี้เกิดจากความจริงที่สับสนเล็กน้อยว่าหากคุณพยายามรับค่าคุณสมบัติที่ไม่มีอยู่ของวัตถุดั้งเดิมใน JavaScript มันจะส่งคืนundefined(แทนที่จะโยนข้อผิดพลาดเกิดขึ้นเมื่อคุณพยายามอ้างถึงตัวแปรที่ไม่มีอยู่จริง ) ซึ่งเป็นเช่นเดียวกับสิ่งที่คุณจะได้รับถ้าคุณสมบัติได้รับก่อนหน้านี้ตั้ง explictly undefinedไป


17

จากหน้า MDC สำหรับmap:

[... ] callbackถูกเรียกใช้เฉพาะสำหรับดัชนีของอาร์เรย์ซึ่งมีค่าที่กำหนดไว้เท่านั้น [ ... ]

[undefined]ใช้ setter กับดัชนี (es) จริง ๆ เพื่อที่mapจะวนซ้ำในขณะที่new Array(1)เพิ่งเริ่มต้นดัชนี (es) ด้วยค่าเริ่มต้นundefinedดังนั้นmapข้ามมัน

ผมเชื่อว่านี่เป็นเหมือนกันสำหรับทุกวิธีการทำซ้ำ


8

ในข้อมูลจำเพาะรุ่นที่ 6 ของ ECMAScript

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

วิธีการmap, every, some, forEach, slice, reduce, reduceRight, filterของอาร์เรย์จะตรวจสอบคุณสมบัติดัชนีโดย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

ดังนั้นสิ่งที่เกิดขึ้นจริงก็คือ 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}สร้างคุณสมบัติหมายเลข:

แผนที่ () การใช้งานจะทำหน้าที่เฉพาะในคุณสมบัติที่กำหนดไว้


3

ไม่ใช่ข้อบกพร่อง นั่นคือวิธีที่ตัวสร้าง Array ถูกกำหนดให้ทำงาน

จาก MDC:

เมื่อคุณระบุพารามิเตอร์ตัวเลขเดียวกับตัวสร้าง Array คุณจะระบุความยาวเริ่มต้นของอาร์เรย์ รหัสต่อไปนี้สร้างอาร์เรย์ของห้าองค์ประกอบ:

var billingMethod = new Array(5);

พฤติกรรมของ Constructor Array ขึ้นอยู่กับว่าพารามิเตอร์เดียวเป็นตัวเลขหรือไม่

.map()วิธีเดียวที่รวมในการย้ำองค์ประกอบของอาร์เรย์ที่มีอย่างชัดเจนได้ค่าที่ได้รับมอบหมาย แม้การมอบหมายอย่างชัดเจนundefinedจะทำให้ค่าที่จะถือว่ามีสิทธิ์ได้รับการรวมอยู่ในการทำซ้ำ ดูเหมือนแปลก แต่โดยหลักแล้วความแตกต่างระหว่างundefinedคุณสมบัติชัดเจนบนวัตถุและคุณสมบัติที่ขาดหายไป:

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

วัตถุxไม่มีคุณสมบัติที่เรียกว่า "z" และวัตถุyนั้น อย่างไรก็ตามในทั้งสองกรณีก็ปรากฏว่า "คุณค่า" undefinedของทรัพย์สินที่เป็น ในอาร์เรย์สถานการณ์ที่คล้ายกัน: ค่าของไม่ปริยายดำเนินการกำหนดค่าให้กับทุกองค์ประกอบจากศูนย์ผ่านlength ฟังก์ชั่นจึงจะไม่ทำอะไร (จะไม่โทรโทรกลับ) เมื่อเรียกอาร์เรย์ที่สร้างขึ้นใหม่ด้วยตัวสร้างอาร์เรย์และอาร์กิวเมนต์ที่เป็นตัวเลขlength - 1.map()


มันถูกกำหนดให้แตก? มันถูกออกแบบมาเพื่อสร้างองค์ประกอบสามอย่างที่จะคงอยู่undefinedตลอดไป?
การแข่งขัน Lightness ใน Orbit

ใช่ถูกต้องยกเว้นส่วน "ถาวร" คุณสามารถกำหนดค่าให้กับองค์ประกอบในภายหลัง
Pointy

3
นั่นเป็นเหตุผลที่คุณควรใช้x = []แทนx = new Array()
Rocket Hazmat

3

หากคุณกำลังทำสิ่งนี้เพื่อเติมค่าที่มีอาร์เรย์ได้อย่างง่ายดายไม่สามารถใช้การเติมสำหรับเหตุผลในการสนับสนุนเบราว์เซอร์และไม่ต้องการทำการวนซ้ำคุณสามารถทำได้x = new Array(3).join(".").split(".").map(...ซึ่งจะทำให้คุณมีอาร์เรย์ว่าง เงื่อนไข

ค่อนข้างน่าเกลียดฉันต้องพูด แต่อย่างน้อยปัญหาและความตั้งใจค่อนข้างชัดเจนสื่อสาร


1

เนื่องจากคำถามคือสาเหตุสิ่งนี้เกี่ยวข้องกับวิธีการออกแบบ JS

มี 2 ​​เหตุผลหลักที่ฉันสามารถอธิบายพฤติกรรมนี้ได้:

  • ประสิทธิภาพการทำงาน: กำหนดx = 10000แล้วและnew 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]และa[9] = 9, console.log(a)จะกลับมา(10) [0, empty x 8, 9]เติมเต็มช่องว่างโดยอัตโนมัติโดยการกลับความแตกต่างระหว่างสองค่าประกาศอย่างชัดเจน


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

นี่คือวิธีการใช้งานที่ง่าย ๆ

แผนที่ง่าย ๆ

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.