JavaScript "новий масив (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
Так чи не вдається JavaScript, що неможливо перевірити, чи не визначений покажчик, чи покажчик на невизначений? Я маю на увазі (new Array(1))[0] === [undefined][0].
Тревор Норріс

Ну, масив невизначених відрізняється від масиву покажчиків до невизначених об'єктів. Масив undefines був би схожим на масив null значень, [null, null, null], тоді як масив покажчиків на undefined був би як [343423, 343424, 343425], що вказує на null та null та null. У другому рішенні є реальні вказівники, що вказують на адреси пам'яті, а перші не вказують нікуди. Якщо це невдача JS, ймовірно, питання про обговорення, але не тут;)
David 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.
кальмар314

3
Якщо говорити про "невизначені покажчики" в JavaScript, це заплутує проблему. Термін, який ви шукаєте, - це "елісії" . x = new Array(3);еквівалентно x = [,,,];, ні x = [undefined, undefined, undefined].
Метт Кантор

118

У мене було завдання, що я знав лише довжину масиву і потрібен для перетворення елементів. Я хотів зробити щось подібне:

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

Оновлення

Іншим рішенням може бути:

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)]
Моломбі

З великими масивами синтаксис розповсюдження створює проблеми, тому краще уникати

або[...Array(10).keys()]
Chungzuwalla

25

Рішення ES6:

[...Array(10)]

Однак не працює на машинопис (2.3)


6
Array(10).fill("").map( ...це те, що працювало для мене з Typescript 2.9
ibex

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(а не викидає помилку, як це відбувається при спробі посилатися на неіснуючу змінну ), що те саме, що ви отримуєте, якщо властивість раніше було експліцитно встановлено undefined.


17

На сторінці MDC для map:

[...] callbackвикликається лише для індексів масиву, яким присвоєно значення; [...]

[undefined]насправді застосовує сеттер до індексу (ив) так, що mapбуде ітератором, тоді як new Array(1)просто ініціалізує індекс (и) зі значенням за замовчуванням, 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-arrayacumulation 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}.

Реалізація map () діє лише на визначені властивості.


3

Не помилка. Ось так визначається конструктор Array.

Від MDC:

Коли ви вказуєте єдиний числовий параметр з конструктором Array, ви вказуєте початкову довжину масиву. Наступний код створює масив з п'яти елементів:

var billingMethod = new Array(5);

Поведінка конструктора масиву залежить від того, чи є єдиний параметр числом.

.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
Ось чому вам слід скористатися x = []замістьx = new Array()
Rocket Hazmat

3

Якщо ви робите це для того, щоб легко заповнити масив зі значеннями, не можете використовувати заповнення з міркувань підтримки браузера і дійсно не хочете робити цикл for-петлі, ви також можете зробити це, x = new Array(3).join(".").split(".").map(...що дасть вам масив порожнього струни.

Досить негарно сказати, але принаймні проблема та намір досить чітко повідомляються.


1

Оскільки питання полягає в тому, чому це пов'язано з тим, як було розроблено JS.

Є дві основні причини, які я можу придумати, щоб пояснити цю поведінку:

  • Продуктивність: Дано, 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]
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.