JavaScript mới Mảng mới (n) và sự lạ lùng của Array Array.prototype.map


209

Tôi đã quan sát điều này trong Firefox-3.5.7 / Fireorms-1.5.3 và Firefox-3.6.16 / Fireorms-1.6.2

Khi tôi bắn Firebird:

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]

Những gì đang xảy ra ở đây? Đây có phải là một lỗi, hoặc tôi hiểu lầm làm thế nào để sử dụng new Array(3)?


Tôi không nhận được kết quả giống như bạn thấy từ ký hiệu mảng. Tôi vẫn không xác định được thay vì 0. Tôi chỉ nhận được kết quả 0 nếu tôi đặt một cái gì đó giống như var y = x.map(function(){return 0; });và tôi nhận được điều này cho cả phương thức Array () mới và mảng bằng chữ. Tôi đã thử nghiệm trên Firefox 4 và Chrome.
RussellUresti

cũng bị vỡ trong Chrome, điều này có thể được định nghĩa bằng ngôn ngữ, mặc dù nó không có ý nghĩa gì nên tôi thực sự hy vọng nó không phải là
Hashbrown

Câu trả lời:


125

Có vẻ như ví dụ đầu tiên

x = new Array(3);

Tạo một mảng với con trỏ không xác định.

Và cái thứ hai tạo ra một mảng với các con trỏ tới 3 đối tượng không xác định, trong trường hợp này, các con trỏ chúng tự KHÔNG được xác định, chỉ các đối tượng mà chúng trỏ tới.

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

Vì bản đồ được chạy trong ngữ cảnh của các đối tượng trong mảng, tôi tin rằng bản đồ thứ nhất không chạy được chức năng trong khi bản đồ thứ hai quản lý để chạy.


86
Từ MDC (nhấn mạnh của tôi): " mapgọi hàm gọi lại được cung cấp một lần cho mỗi phần tử trong một mảng, theo thứ tự và xây dựng một mảng mới từ kết quả. Chỉ callbackđược gọi cho các chỉ mục của mảng đã gán các giá trị ; nó không được gọi cho các chỉ mục đã bị xóa hoặc chưa bao giờ được gán giá trị. " Trong trường hợp này, xcác giá trị của các giá trị không được gán rõ ràng, trong khi các giá trị yđược gán, ngay cả khi đó là giá trị undefined.
Martijn

2
Vì vậy, có phải JavaScript không thành công mà không thể kiểm tra xem đó là con trỏ không xác định hay con trỏ không xác định? Ý tôi là (new Array(1))[0] === [undefined][0].
Trevor Norris

Chà, một mảng không xác định khác với một mảng các con trỏ đến các đối tượng không xác định. Một mảng các giá trị không xác định sẽ giống như một mảng các giá trị null, [null, null, null] trong khi một mảng các con trỏ không xác định sẽ giống như [343423, 343424, 343425] sắp thành null và null và null. Các giải pháp thứ hai có con trỏ thực trỏ đến địa chỉ bộ nhớ trong khi giải pháp đầu tiên không trỏ đến bất kỳ đâu. Nếu đó là một thất bại của JS có lẽ là vấn đề thảo luận, nhưng không phải ở đây;)
David Mårtensson

4
@TrevNorris, bạn có thể dễ dàng kiểm tra điều đó hasOwnPropertytrừ khi hasOwnPropertybản thân nó có lỗi: (new Array(1)).hasOwnProperty(0) === false[undefined].hasOwnProperty(0) === true. Trong thực tế, bạn có thể làm chính xác với in: 0 in [undefined] === true0 in new Array(0) === false.
mực314

3
Nói về "con trỏ không xác định" trong JavaScript gây nhầm lẫn vấn đề. Thuật ngữ bạn đang tìm kiếm là "cuộc bầu cử" . x = new Array(3);tương đương với x = [,,,];, không x = [undefined, undefined, undefined].
Matt Kantor

118

Tôi có một nhiệm vụ mà tôi chỉ biết chiều dài của mảng và cần thiết để biến đổi các mục. Tôi muốn làm một cái gì đó như thế này:

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

Để nhanh chóng tạo một mảng như thế này:

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

Nhưng nó không hoạt động vì: (xem câu trả lời của Jonathan Lonowski một vài câu trả lời ở trên)

Giải pháp có thể là lấp đầy các mục mảng với bất kỳ giá trị nào (ngay cả khi không xác định) bằng Array.prototype.fill ()

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

Cập nhật

Một giải pháp khác có thể là:

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

console.log(Array.apply(null, Array(10)).map((val, idx) => idx));


28
đáng lưu ý bạn không cần nêu undefinedtrong .fill()phương pháp, đơn giản hóa mã rất nhẹ thànhlet arr = new Array(10).fill().map((val,idx) => idx);
Yann Eves

Tương tự bạn có thể sử dụngArray.from(Array(10))

84

Với ES6, bạn có thể làm [...Array(10)].map((a, b) => a), nhanh chóng và dễ dàng!


9
Pre-ES6 bạn có thể sử dụng new Array(10).fill(). Kết quả tương tự như[...Array(10)]
Molomby

Với các mảng lớn, cú pháp lây lan tạo ra các vấn đề nên tránh tốt hơn

hoặc[...Array(10).keys()]
Chungzuwalla

25

Giải pháp ES6:

[...Array(10)]

Không hoạt động trên bản thảo (2.3), mặc dù


6
Array(10).fill("").map( ...là những gì làm việc cho tôi với Bản in 2.9
ibex

18

Các mảng là khác nhau. Sự khác biệt là new Array(3)tạo ra một mảng có độ dài ba nhưng không có thuộc tính, trong khi [undefined, undefined, undefined]tạo ra một mảng có độ dài ba và ba thuộc tính được gọi là "0", "1" và "2", mỗi thuộc tính có giá trị là undefined. Bạn có thể thấy sự khác biệt bằng cách sử dụng intoán tử:

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

Điều này xuất phát từ một thực tế hơi khó hiểu rằng nếu bạn cố gắng lấy giá trị của một thuộc tính không tồn tại của bất kỳ đối tượng gốc nào trong JavaScript, nó sẽ trả về undefined(thay vì ném một lỗi, như xảy ra khi bạn cố gắng tham chiếu đến một biến không tồn tại ), giống như những gì bạn nhận được nếu tài sản trước đó đã được đặt thành undefined.


16

Từ trang MDC cho map:

[...] Chỉ callbackđược gọi cho các chỉ mục của mảng đã gán giá trị; [...]

[undefined]thực sự áp dụng các setter trên chỉ mục (es) để mapsẽ lặp, trong khi new Array(1)chỉ khởi tạo các chỉ mục (es) với một giá trị mặc định của undefinednên mapbỏ qua nó.

Tôi tin rằng điều này là giống nhau cho tất cả các phương pháp lặp .


8

Trong đặc tả phiên bản thứ 6 của ECMAScript.

new Array(3)chỉ xác định thuộc tính lengthvà không xác định thuộc tính chỉ mục như thế nào {length: 3}. xem https://www.ecma-i Intl.org/ecma-262/6.0/index.html#sec-array-len Bước 9.

[undefined, undefined, undefined]sẽ xác định thuộc tính chỉ mục và thuộc tính chiều dài như thế nào {0: undefined, 1: undefined, 2: undefined, length: 3}. xem https://www.ecma-i Intl.org/ecma-262/6.0/index.html#sec-racer-semantics-arrayaccumulation ElementList Bước 5.

phương pháp map, every, some, forEach, slice, reduce, reduceRight, filtercác mảng sẽ kiểm tra tài sản chỉ mục bằng HasPropertyphương pháp nội bộ, vì vậy new Array(3).map(v => 1)sẽ không gọi callback.

để biết thêm chi tiết, hãy xem https: //www.ecma-i quốc.org / ecma-626/6 / index.html # sec-array.prototype.map

Làm thế nào để khắc phục?

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

Giải thích rất hay.
Dheeraj Rao

7

Tôi nghĩ cách tốt nhất để giải thích điều này là nhìn vào cách Chrome xử lý nó.

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

Vì vậy, những gì đang thực sự xảy ra là Array mới () đang trả về một mảng trống có độ dài 3, nhưng không có giá trị. Do đó, khi bạn chạy x.maptrên một mảng trống về mặt kỹ thuật , không có gì được đặt.

Firefox chỉ 'lấp đầy' những chỗ trống undefinedđó mặc dù nó không có giá trị.

Tôi không nghĩ rằng đây rõ ràng là một lỗi, chỉ là một cách kém để thể hiện những gì đang diễn ra. Tôi cho rằng Chrome là "chính xác hơn" bởi vì nó cho thấy thực sự không có gì trong mảng.


4

Chỉ cần chạy vào đây. Nó chắc chắn sẽ thuận tiện để có thể sử dụng Array(n).map.

Array(3) năng suất khoảng {length: 3}

[undefined, undefined, undefined]tạo các thuộc tính được đánh số :
{0: undefined, 1: undefined, 2: undefined, length: 3}.

Việc thực hiện map () chỉ hoạt động trên các thuộc tính được xác định.


3

Không phải là một lỗi. Đó là cách trình xây dựng Array được xác định để hoạt động.

Từ MDC:

Khi bạn chỉ định một tham số số duy nhất với hàm tạo Array, bạn chỉ định độ dài ban đầu của mảng. Đoạn mã sau tạo một mảng gồm năm phần tử:

var billingMethod = new Array(5);

Hành vi của hàm tạo Array phụ thuộc vào việc tham số đơn có phải là số không.

Các .map()phương pháp duy nhất bao gồm trong lặp yếu tố của mảng đó đã rõ ràng giá trị giao. Ngay cả một sự phân công rõ ràng undefinedsẽ khiến một giá trị được coi là đủ điều kiện để đưa vào phép lặp. Điều đó có vẻ kỳ lạ, nhưng về cơ bản, đó là sự khác biệt giữa một thuộc tính rõ ràng undefinedtrên một đối tượng và một thuộc tính bị thiếu:

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

Đối tượng xkhông có thuộc tính được gọi là "z" và đối tượng ythì có. Tuy nhiên, trong cả hai trường hợp, dường như "giá trị" của tài sản là undefined. Trong một mảng, tình huống tương tự: giá trị lengththực hiện ngầm định việc gán giá trị cho tất cả các phần tử từ 0 đến length - 1. Các .map()chức năng do đó sẽ không làm bất cứ điều gì (sẽ không gọi gọi lại) khi kêu gọi một mảng mới được xây dựng với các nhà xây dựng Array và một cuộc tranh luận số.


Nó được định nghĩa là bị phá vỡ? Nó được thiết kế để tạo ra một mảng gồm ba yếu tố sẽ luôn tồn undefinedtại mãi mãi?
Các cuộc đua nhẹ nhàng trong quỹ đạo

Vâng, điều đó đúng, ngoại trừ phần "mãi mãi". Sau đó, bạn có thể gán giá trị cho các yếu tố.
Pointy

3
Đó là lý do tại sao bạn nên sử dụng x = []thay vìx = new Array()
Rocket Hazmat

3

Nếu bạn đang làm điều này để dễ dàng điền vào một mảng với các giá trị, không thể sử dụng điền vì lý do hỗ trợ trình duyệt và thực sự không muốn thực hiện một vòng lặp for, bạn cũng có thể làm điều x = new Array(3).join(".").split(".").map(...đó sẽ cung cấp cho bạn một mảng trống dây.

Tôi phải nói là khá xấu xí, nhưng ít nhất vấn đề và ý định được truyền đạt khá rõ ràng.


1

Vì câu hỏi là tại sao, điều này có liên quan đến cách thiết kế của JS.

Có hai lý do chính tôi có thể nghĩ ra để giải thích hành vi này:

  • Hiệu suất: Được cung cấp x = 10000new Array(x)thật khôn ngoan cho nhà xây dựng để tránh lặp từ 0 đến 10000 để lấp đầy các undefinedgiá trị.

  • Ngầm "không xác định": Hãy cho a = [undefined, undefined]b = new Array(2), a[1]b[1]cả hai sẽ trở lại undefined, nhưng a[8]b[8]cũng sẽ trở lại undefinedngay cả khi họ ra khỏi phạm vi.

Cuối cùng, ký hiệu empty x 3là một phím tắt để tránh thiết lập và hiển thị một danh sách dài các undefinedgiá trị undefineddù sao đi nữa vì chúng không được khai báo rõ ràng.

Lưu ý: Cho mảng a = [0]a[9] = 9, console.log(a)sẽ trả về (10) [0, empty x 8, 9], tự động điền vào khoảng trống bằng cách trả về chênh lệch giữa hai giá trị được khai báo rõ ràng.


1

Vì lý do được giải thích kỹ lưỡng trong các câu trả lời khác, Array(n).mapkhông hoạt động. Tuy nhiên, trong ES2015 Array.fromchấp nhận chức năng bản đồ:

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

Đây là một phương pháp tiện ích đơn giản như một cách giải quyết:

Bản đồ đơn giản

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]

Ví dụ hoàn chỉnh

Đây là một ví dụ đầy đủ hơn (với kiểm tra độ tỉnh táo) cũng cho phép chỉ định một chỉ mục bắt đầu tùy chọn:

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]

Đếm ngược

Thao tác chỉ mục được chuyển đến cuộc gọi lại cho phép đếm ngược:

var count = 3;
var arr = arrayUtil.mapFor(count, function (i) {
    return count - 1 - i;
});
// arr = [2, 1, 0]
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.