JavaScript „new Array (n)” i „Array.prototype.map” dziwne

209

Zauważyłem to w Firefox-3.5.7 / Firebug-1.5.3 i Firefox-3.6.16 / Firebug-1.6.2

Kiedy odpalam 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]

Co tu się dzieje? Czy to błąd, czy nie rozumiem, jak go używać new Array(3)?

szaleństwo
źródło
Nie otrzymuję takich samych wyników, jakie widzisz z literalnej notacji tablicowej. Nadal jestem niezdefiniowany zamiast 0. Otrzymuję wynik 0 tylko, jeśli ustawię coś podobnego var y = x.map(function(){return 0; });, i otrzymuję to zarówno dla nowej metody Array (), jak i literału tablicowego. Testowałem w Firefox 4 i Chrome.
RussellUresti
również odpadł w Chrome, może to być zdefiniowane w języku, chociaż nie ma to sensu, więc mam nadzieję, że tak nie jest
Hashbrown,

Odpowiedzi:

125

Wygląda na to, że pierwszy przykład

x = new Array(3);

Tworzy tablicę z niezdefiniowanymi wskaźnikami.

A drugi tworzy tablicę ze wskaźnikami do 3 niezdefiniowanych obiektów, w tym przypadku wskaźniki NIE są niezdefiniowane, tylko obiekty, na które wskazują.

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

Ponieważ mapa jest uruchamiana w kontekście obiektów w tablicy, uważam, że pierwsza mapa w ogóle nie uruchamia funkcji, podczas gdy druga zarządza uruchomieniem.

David Mårtensson
źródło
86
Z MDC (wyróżnienie moje): „ mapwywołuje podaną funkcję wywołania zwrotnego raz dla każdego elementu w tablicy, w kolejności, i konstruuje nową tablicę z wyników. callbackJest wywoływany tylko dla indeksów tablicy, którym przypisano wartości ; nie jest wywoływany dla indeksów, które zostały usunięte lub którym nigdy nie przypisano wartości. ” W tym przypadku xwartościom nie przypisano wyraźnie wartości, natomiast wartościom yprzypisano, nawet jeśli była to wartość undefined.
Martijn
2
Czy to błąd JavaScript, że nie można sprawdzić, czy jest niezdefiniowanym wskaźnikiem, czy wskaźnikiem niezdefiniowanym? Mam na myśli (new Array(1))[0] === [undefined][0].
Trevor Norris,
Cóż, tablica niezdefiniowanych różni się od tablicy wskaźników do niezdefiniowanych obiektów. Tablica niezdefiniowanych byłaby jak tablica wartości zerowych, [null, null, null], podczas gdy tablica wskaźników do niezdefiniowanych byłaby jak [343423, 343424, 343425] ustawiająca się na null i null i null. Drugie rozwiązania mają prawdziwe wskaźniki wskazujące adresy pamięci, podczas gdy pierwsze nie wskazują nigdzie. Jeśli to jest porażka JS, jest to prawdopodobnie kwestia dyskusji, ale nie tutaj;)
David Mårtensson
4
@ TrevNorris, możesz łatwo to przetestować, hasOwnPropertychyba że hasOwnPropertysam ma błąd: (new Array(1)).hasOwnProperty(0) === falsei [undefined].hasOwnProperty(0) === true. W rzeczywistości możesz zrobić dokładnie to samo z in: 0 in [undefined] === truei 0 in new Array(0) === false.
squid314,
3
Mówienie o „niezdefiniowanych wskaźnikach” w JavaScript dezorientuje problem. Termin, którego szukasz, to „wybory” . x = new Array(3);jest równoważne x = [,,,];, nie x = [undefined, undefined, undefined].
Matt Kantor,
118

Miałem zadanie, które znałem tylko długość tablicy i potrzebowałem przekształcić przedmioty. Chciałem zrobić coś takiego:

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

Aby szybko utworzyć taką tablicę:

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

Ale to nie zadziałało, ponieważ: (patrz odpowiedź Jonathana Lonowskiego kilka odpowiedzi powyżej)

Rozwiązaniem może być wypełnienie elementów tablicy dowolną wartością (nawet niezdefiniowaną) za pomocą Array.prototype.fill ()

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

Aktualizacja

Innym rozwiązaniem może być:

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

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

cstuncsik
źródło
28
warto zauważyć, że nie trzeba podawać undefinedw .fill()metodzie, bardzo upraszczając kod dolet arr = new Array(10).fill().map((val,idx) => idx);
Yann Eves
Podobnie możesz użyćArray.from(Array(10))
84

Z ES6 możesz to zrobić [...Array(10)].map((a, b) => a)szybko i łatwo!

Manuel Beaudru
źródło
9
Przed ES6 możesz użyć new Array(10).fill(). Taki sam wynik jak[...Array(10)]
Molomby
Z dużymi tablicami składnia spread stwarza problemy, dlatego lepiej unikać
lub[...Array(10).keys()]
Chungzuwalla
25

Rozwiązanie ES6:

[...Array(10)]

Jednak nie działa na maszynopisie (2.3)

Serge Intern
źródło
6
Array(10).fill("").map( ...jest to, co zadziałało dla mnie z Typescript 2.9
ibex
19

Tablice są różne. Różnica polega na tym, że new Array(3)tworzy tablicę o długości trzech, ale bez właściwości, a [undefined, undefined, undefined]tworzy tablicę o długości trzech i trzech właściwości zwanych „0”, „1” i „2”, z których każda ma wartość undefined. Różnicę możesz zobaczyć za pomocą inoperatora:

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

Wynika to z nieco mylącego faktu, że jeśli spróbujesz uzyskać wartość nieistniejącej właściwości dowolnego obiektu macierzystego w JavaScript, zwraca undefined(zamiast zgłaszania błędu, jak to się dzieje, gdy próbujesz odwołać się do nieistniejącej zmiennej ), co jest tym samym, co otrzymasz, jeśli właściwość została wcześniej jawnie ustawiona na undefined.

Tim Down
źródło
17

Ze strony MDC dla map:

[...] callbackjest wywoływane tylko dla indeksów tablicy, którym przypisano wartość; [...]

[undefined]faktycznie stosuje seter do indeksu (ów) tak, mapaby iterował, podczas gdy new Array(1)po prostu inicjuje indeksy z wartością domyślną, undefinedwięc mappomija je.

Uważam, że to samo dotyczy wszystkich metod iteracji .

Jonathan Lonowski
źródło
8

W specyfikacji ECMAScript 6. edycja.

new Array(3)definiuj tylko właściwości lengthi nie definiuj właściwości indeksu, takich jak {length: 3}. patrz https://www.ecma-international.org/ecma-262/6.0/index.html#sec-array-len Krok 9.

[undefined, undefined, undefined]zdefiniuje właściwości indeksu i właściwości długości, takie jak {0: undefined, 1: undefined, 2: undefined, length: 3}. patrz https://www.ecma-international.org/ecma-262/6.0/index.html#sec-runtime-semantics-arrayaccumulation ElementList Krok 5.

Metody map, every, some, forEach, slice, reduce, reduceRight, filterArray będzie sprawdzić właściwość Indeks według HasPropertymetody wewnętrznych, więc new Array(3).map(v => 1)nie będzie powoływać się na zwrotnego.

Aby uzyskać więcej informacji, zobacz https://www.ecma-international.org/ecma-262/6.0/index.html#sec-array.prototype.map

Jak naprawić?

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);
wenshin
źródło
Bardzo miłe wytłumaczenie.
Dheeraj Rao
7

Myślę, że najlepszym sposobem na wyjaśnienie tego jest spojrzenie na sposób, w jaki Chrome to obsługuje.

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

Tak naprawdę dzieje się tak, że nowa funkcja Array () zwraca pustą tablicę o długości 3, ale bez wartości. Dlatego po uruchomieniu x.mapna technicznie pustą tablicę, nie ma nic do ustawienia.

Firefox po prostu „wypełnia” te puste miejsca, undefinednawet jeśli nie ma żadnych wartości.

Nie sądzę, że jest to wyraźny błąd, tylko zły sposób na przedstawienie tego, co się dzieje. Podejrzewam, że Chrome jest „bardziej poprawny”, ponieważ pokazuje, że tak naprawdę nic nie ma w tablicy.

cześć
źródło
4

Właśnie na to wpadłem. Byłoby z pewnością wygodne w użyciu Array(n).map.

Array(3) daje z grubsza {length: 3}

[undefined, undefined, undefined]Tworzy numerowane właściwości:
{0: undefined, 1: undefined, 2: undefined, length: 3}.

Implementacja map () działa tylko na zdefiniowanych właściwościach.

Vezquex
źródło
3

To nie błąd. Tak definiuje się konstruktor Array do działania.

Z MDC:

Określając pojedynczy parametr numeryczny za pomocą konstruktora Array, określasz początkową długość tablicy. Poniższy kod tworzy tablicę pięciu elementów:

var billingMethod = new Array(5);

Zachowanie konstruktora Array zależy od tego, czy pojedynczy parametr jest liczbą.

.map()Sposób obejmuje tylko iteracji elementy tablicy, które zostały wyraźnie miały wartości przypisane. Nawet wyraźne przypisanie undefinedspowoduje, że wartość zostanie uznana za kwalifikującą się do włączenia do iteracji. To wydaje się dziwne, ale zasadniczo jest to różnica między jawną undefinedwłaściwością obiektu a brakującą właściwością:

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

Obiekt xnie ma właściwości o nazwie „z”, a obiekt yma. Jednak w obu przypadkach wydaje się, że „wartością” właściwości jest undefined. W tablicy sytuacja jest podobna: wartość lengthniejawnie wykonuje przypisanie wartości do wszystkich elementów od zera do length - 1. Dlatego .map()funkcja nic nie zrobi (nie wywoła wywołania zwrotnego), gdy zostanie wywołana na nowo zbudowanej tablicy za pomocą konstruktora Array i argumentu numerycznego.

Pointy
źródło
Jest zdefiniowane jako zepsute? Został zaprojektowany, aby stworzyć tablicę trzech elementów, które zawsze pozostaną undefinedna zawsze?
Wyścigi lekkości na orbicie
Tak, to prawda, z wyjątkiem części „na zawsze”. Następnie możesz przypisać wartości do elementów.
Pointy
3
Dlatego powinieneś użyć x = []zamiastx = new Array()
Rocket Hazmat
3

Jeśli robisz to w celu łatwego wypełnienia tablicy wartościami, nie możesz użyć wypełnienia ze względu na obsługę przeglądarki i naprawdę nie chcesz robić pętli for, możesz również zrobić to, x = new Array(3).join(".").split(".").map(...co da ci tablicę pustych smyczki.

Muszę powiedzieć, że jest to dość brzydkie, ale przynajmniej problem i intencje są dość jasno przedstawione.

Alex
źródło
1

Ponieważ pytanie brzmi dlaczego, ma to związek z tym, jak zaprojektowano JS.

Istnieją dwa główne powody, dla których mogę wyjaśnić to zachowanie:

  • Wydajność: Biorąc pod uwagę x = 10000i new Array(x)rozsądne jest, aby konstruktor unikał zapętlania od 0 do 10000 w celu wypełnienia tablicy undefinedwartościami.

  • Domniemane „niezdefiniowane”: Podaj a = [undefined, undefined]i b = new Array(2), a[1]i b[1]oba powrócą undefined, ale a[8]i b[8]powrócą, undefinednawet jeśli będą poza zasięgiem.

Ostatecznie notacja empty x 3jest skrótem, aby uniknąć ustawiania i wyświetlania długiej listy undefinedwartości, które i undefinedtak są, ponieważ nie są one jawnie zadeklarowane.

Uwaga: Ze względu tablica a = [0]i a[9] = 9, console.log(a)powróci (10) [0, empty x 8, 9], wypełniając lukę automatycznie poprzez zwrot różnicy między dwiema wartościami zadeklarowane jawnie.

BPS Julien
źródło
1

Z powodów dokładnie wyjaśnionych w innych odpowiedziach Array(n).mapnie działa. Jednak w ES2015 Array.fromakceptuje funkcję mapy:

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

Eden Landau
źródło
0

Oto prosta metoda narzędzia jako obejście:

Prosta mapa

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]

Kompletny przykład

Oto bardziej kompletny przykład (z kontrolą poprawności), który pozwala również określić opcjonalny indeks początkowy:

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]

Odliczać

Manipulowanie indeksem przekazywanym do wywołania zwrotnego pozwala na liczenie wstecz:

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