Miten voin käydä läpi kaikki merkinnät array käyttäen JavaScript?
Luulin, että se oli jotakin tällaista:
forEach(instance in theArray)
Jossa theArray
on minun array, mutta tämä näyttää olevan väärin.
TL;DR
for-in
, ellet käytä sitä turvatoimien kanssa tai ole ainakin tietoinen siitä, miksi se voi purra sinua.for-of
-silmukka (vain ES2015+),Array#forEach
(spec
| MDN
) (tai sen sukulaiset some
ja vastaavat) (vain ES5+),for
-silmukka,for-in
-silmukka suojatoimien kanssa.
Mutta on paljon muuta tutkittavaa, lue lisää...JavaScriptillä on tehokas semantiikka silmukoiden ja joukkojen kaltaisten objektien läpikäymiseen. Olen jakanut vastauksen kahteen osaan: Vaihtoehdot aidoille matriiseille ja vaihtoehdot asioille, jotka ovat vain matriisin kaltaisia, kuten arguments
-objekti, muut iteroituvat objektit (ES2015+), DOM-kokoelmat ja niin edelleen.
Huomautan nopeasti, että voit käyttää ES2015:n vaihtoehtoja jopa nyt, jopa ES5-moottoreissa, transpiloimalla ES2015:n ES5:een. Etsi "ES2015 transpiling" / "ES6 transpiling" lisää...
Okei, katsotaanpa vaihtoehtoja:
Sinulla on kolme vaihtoehtoa ECMAScript 5 ("ES5"), joka on tällä hetkellä laajimmin tuettu versio, ja kaksi lisää lisätty ECMAScript 2015 ("ES2015", "ES6"):
forEach
ja siihen liittyviä (ES5+)for
-silmukkaafor-in
-silmukkaa oikeasti.for-of
-silmukkaa (käytä implisiittisesti iteraattoria) (ES2015+)forEach
ja siihen liittyviäKaikissa epämääräisen nykyaikaisissa ympäristöissä (ei siis IE8), joissa sinulla on pääsy ES5:n tuomiin Array
-ominaisuuksiin (suoraan tai polyfillien avulla), voit käyttää forEach
-ominaisuutta (spec
| MDN
):
var a = ["a", "b", "c"];
a.forEach(function(entry) {
console.log(entry);
});
forEach
hyväksyy takaisinkutsufunktion ja valinnaisesti arvon, jota käytetään this
:nä kutsuttaessa takaisinkutsua (ei käytetty edellä). Takaisinkutsua kutsutaan jokaisesta matriisin merkinnästä, järjestyksessä, ohittaen olemattomat merkinnät harvoissa matriiseissa. Vaikka käytin edellä vain yhtä argumenttia, takaisinkutsua kutsutaan kolmella argumentilla: Kunkin merkinnän arvo, kyseisen merkinnän indeksi ja viittaus matriisiin, jonka yli iteroidaan (jos funktiollasi ei ole sitä jo valmiina).
Ellet'tue vanhentuneita selaimia, kuten IE8:a (jonka markkinaosuus NetAppsissa on hieman yli 4 % tätä kirjoitettaessa syyskuussa 2016), voit huoletta käyttää forEach
:ia yleiskäyttöisessä verkkosivussa ilman shimiä. Jos sinun on kuitenkin tuettava vanhentuneita selaimia, forEach
:n shimming/polyfilling on helppo tehdä (etsi hakusanalla "es5 shim" useita vaihtoehtoja).
forEach
:n etuna on se, että sinun ei tarvitse ilmoittaa indeksointi- ja arvomuuttujia sisältävässä laajuudessa, koska ne annetaan argumentteina iteraatiofunktiolle ja ovat siten mukavasti vain kyseiseen iteraatioon sidottuja.
Jos olet huolissasi siitä, että funktiokutsun tekeminen jokaista matriisin merkintää varten aiheuttaa ajonaikaisia kustannuksia, älä ole huolissasi; details.
Lisäksi forEach
on "loop through them all" -funktio, mutta ES5:ssä on määritelty useita muita hyödyllisiä "work your way through the array and do things" -funktioita, kuten:
every
(pysäyttää silmukan, kun callback palauttaa ensimmäisen kerran false
tai jotain väärää).some
(pysäyttää silmukan käsittelyn ensimmäisen kerran, kun takaisinkutsu palauttaa true
tai jotain totuudenmukaista).filter
(luo uuden matriisin, joka sisältää elementit, joiden kohdalla suodatinfunktio palauttaa true
, ja jättää pois elementit, joiden kohdalla se palauttaa false
).map
(luo uuden matriisin callbackin palauttamista arvoista) * map
(luo uuden matriisin callbackin palauttamista arvoista)reduce
(muodostaa arvon kutsumalla toistuvasti callbackia ja antamalla edelliset arvot; katso yksityiskohdat spekseistä; hyödyllinen matriisin sisällön yhteenlaskuun ja moniin muihin asioihin).reduceRight
(kuten reduce
, mutta toimii laskevassa eikä nousevassa järjestyksessä)for
-silmukkaaJoskus vanhat tavat ovat parhaita:
var index;
var a = ["a", "b", "c"];
for (index = 0; index < a.length; ++index) {
console.log(a[index]);
}
Jos matriisin pituus ei muutu silmukan aikana ja se on suorituskykyherkässä koodissa (epätodennäköistä), hieman monimutkaisempi versio, joka nappaa pituuden etukäteen, saattaa olla tiny vähän nopeampi:
var index, len;
var a = ["a", "b", "c"];
for (index = 0, len = a.length; index < len; ++index) {
console.log(a[index]);
}
Ja/tai laskemalla taaksepäin:
var index;
var a = ["a", "b", "c"];
for (index = a.length - 1; index >= 0; --index) {
console.log(a[index]);
}
Nykyaikaisilla JavaScript-moottoreilla on kuitenkin harvinaista, että sinun täytyy ottaa viimeistä hippua irti.
ES2015:ssä ja uudemmissa versioissa voit tehdä indeksi- ja arvomuuttujista paikallisia for
-silmukassa:
let a = ["a", "b", "c"];
for (let index = 0; index < a.length; ++index) {
let value = a[index];
console.log(index, value);
}
//console.log(index); // would cause "ReferenceError: index is not defined"
//console.log(value); // would cause "ReferenceError: value is not defined"
let a = ["a", "b", "c"];
for (let index = 0; index < a.length; ++index) {
let value = a[index];
console.log(index, value);
}
try {
console.log(index);
} catch (e) {
console.error(e); // "ReferenceError: index is not defined"
}
try {
console.log(value);
} catch (e) {
console.error(e); // "ReferenceError: value is not defined"
}
Ja kun teet näin, ei vain value
vaan myös index
luodaan uudelleen jokaisella silmukan iteraatiokerralla, mikä tarkoittaa, että silmukan rungossa luodut sulkimet pitävät viittauksen index
:iin (ja value
:een), joka on luotu kyseistä iteraatiota varten:
let divs = document.querySelectorAll("div");
for (let index = 0; index < divs.length; ++index) {
divs[index].addEventListener('click', e => {
console.log("Index is: " + index);
});
}
let divs = document.querySelectorAll("div");
for (let index = 0; index < divs.length; ++index) {
divs[index].addEventListener('click', e => {
console.log("Index is: " + index);
});
}
<div>zero</div>
<div>one</div>
<div>two</div>
<div>three</div>
<div>four</div>
Jos sinulla olisi viisi diviä, saisit "Index is: 0" jos klikkaisit ensimmäistä ja "Index is: 4" jos napsautit viimeistä. Tämä ei toimi, jos käytät var
:a let
:n sijasta.
for-in
oikeasti.Ihmiset kehottavat sinua käyttämään for-in
, mutta sitä varten for-in
ei ole. for-in
käy silmukalla läpi olion lueteltavissa olevat ominaisuudet, ei matriisin indeksejä. Järjestystä ei ole taattu, ei edes ES2015:ssä (ES6). ES2015+ määrittelee kyllä järjestyksen objektien ominaisuuksille ([[OwnPropertyKeys]]
, [[Enumerate]]
ja niitä käyttävien asioiden, kuten Object.getOwnPropertyKeys
, kautta), mutta se ei määrittele, että for-in
noudattaa tätä järjestystä. (Yksityiskohdat tässä toisessa vastauksessa.)
Ainoat todelliset käyttötapaukset for-in
:lle joukossa ovat:
for-in
:tä käymään noissa harvoissa array-elementeissä, jos käytät asianmukaisia suojatoimia:
// `a` is a sparse array
var key;
var a = [];
a[0] = "a";
a[10] = "b";
a[10000] = "c";
for (key in a) {
if (a.hasOwnProperty(key) && // These checks are
/^0$|^[1-9]\d*$/.test(key) && // explained
key <= 4294967294 // below
) {
console.log(a[key]);
}
}
Huomaa kolme tarkistusta:
length
voi saada. (Esim. array'n pituus mahtuu 32-bittiseen merkitsemättömään kokonaislukuun.) *(Propsit RobG:lle siitä, että hän huomautti kommentissa blogikirjoitukseeni, että edellinen testini ei ollut aivan oikea.))
Et tietenkään tekisi sitä inline-koodissa. Kirjoittaisit apufunktion. Ehkäpä:
// Utility function for antiquated environments without `forEach`
var hasOwn = Object.prototype.hasOwnProperty;
var rexNum = /^0$|^[1-9]\d*$/;
function sparseEach(array, callback, thisArg) {
var index;
for (var key in array) {
index = +key;
if (hasOwn.call(a, key) &&
rexNum.test(key) &&
index <= 4294967294
) {
callback.call(thisArg, array[key], index, array);
}
}
}
var a = [];
a[5] = "five";
a[10] = "ten";
a[100000] = "one hundred thousand";
a.b = "bee";
sparseEach(a, function(value, index) {
console.log("Value at " + index + " is " + value);
});
for-of
(käytä iteraattoria epäsuorasti) (ES2015+)ES2015 lisää iteraattorit JavaScriptiin. Helpoin tapa käyttää iteraattoreita on uusi for-of
-lause. Se näyttää tältä:
const a = ["a", "b", "c"];
for (const val of a) {
console.log(val);
}
Peiton alla tuo saa iteratorin arraysta ja käy silmukalla sen läpi, saaden arvot siitä. Tässä ei ole samaa ongelmaa kuin for-in
:n käyttämisessä, koska se käyttää objektin (array) määrittelemää iteraattoria, ja arrayt määrittelevät, että niiden iteraattorit iteroivat niiden merkintöjen (ei niiden ominaisuuksien) läpi. Toisin kuin ES5:n for-in
:ssä, järjestys, jossa merkinnät käydään läpi, on niiden indeksien numeerinen järjestys.
Joskus saatat haluta käyttää iteraattoria eksplisiittisesti. Voit tehdä senkin, vaikka se onkin paljon kömpelömpää kuin for-of
. Se näyttää tältä:
const a = ["a", "b", "c"];
const it = a.values();
let entry;
while (!(entry = it.next()).done) {
console.log(entry.value);
}
Iteraattori on määrittelyssä olevan Iteraattorin määritelmän mukainen objekti. Sen next
-metodi palauttaa uuden tulosobjektin joka kerta, kun sitä kutsutaan. Tulosobjektilla on ominaisuus done
, joka kertoo, onko se valmis, ja ominaisuus value
, jossa on kyseisen iteraation arvo. (done
on valinnainen, jos se olisi false
, value
on valinnainen, jos se olisi undefined
).
value
:n merkitys vaihtelee iteraattorista riippuen; matriisit tukevat (ainakin) kolmea iteraattorit palauttavaa funktiota:
values()
: Tätä käytin edellä. Se palauttaa iteraattorin, jossa jokainen value
on kyseisen iteraation matriisimerkintä ("a"
, "b"
ja "c"
aiemmassa esimerkissä).keys()
: Palauttaa iteraattorin, jossa jokainen arvo
on kyseisen iteraation avain (eli yllä olevan a
:n kohdalla se olisi "0"
, sitten "1"
, sitten "2"
).entries()
: Palauttaa iteraattorin, jossa jokainen arvo
on kyseisen iteraation muodossa [avain, arvo]
oleva joukko.Todellisten matriisien lisäksi on olemassa myös matriisin kaltaisia objekteja, joilla on length
-ominaisuus ja ominaisuuksia, joilla on numeeriset nimet: NodeList
-oliot, arguments
-olio jne. Miten käymme silmukalla läpi niiden sisällön?
Ainakin joitakin, ja mahdollisesti useimpia tai jopa kaikkia edellä esitettyjä lähestymistapoja voidaan usein soveltaa yhtä hyvin myös joukkojen kaltaisiin objekteihin:
Käytä forEach
ja vastaavia (ES5+).
Array.prototype
:n eri funktiot ovat "tarkoituksella yleisiä" ja niitä voidaan yleensä käyttää array-tyyppisiin objekteihin Function#call
tai Function#apply
avulla. (Katso Caveat isännän tarjoamille objekteille tämän vastauksen lopussa, mutta se on harvinainen ongelma.)
Oletetaan, että haluat käyttää forEach
-ominaisuutta Node
'n childNodes
-ominaisuuteen. Tehdään näin:
Array.prototype.forEach.call(node.childNodes, function(child) {
// Tee jotain lapselle
});
Jos'aiot tehdä tätä usein, haluat ehkä napata kopion funktion viittauksesta muuttujaan uudelleenkäyttöä varten, esim:
// (Tämä kaikki on oletettavasti jossakin scoping-funktiossa).
var forEach = Array.prototype.forEach;
// Sitten myöhemmin...
forEach.call(node.childNodes, function(child) {
// Tee jotain lapsen
kanssa
});
Käytä yksinkertaista for
-silmukkaa
On selvää, että yksinkertainen for
-silmukka soveltuu array-tyyppisiin objekteihin.
Käytä for-in
-silmukkaa oikeasti
for-in
-silmukan, jossa on samat varotoimet kuin arrayjen kanssa, pitäisi toimia myös arrayjen kaltaisten objektien kanssa; edellä #1:ssä oleva varoitus isäntäkoneen tarjoamista objekteista saattaa päteä.
Käytä for-of
(käytä implisiittisesti iteraattoria) (ES2015+).
for-of
käyttää objektin tarjoamaa iteraattoria (jos sellainen on); meidän on nähtävä, miten tämä toimii erilaisten array-tyyppisten objektien kanssa, erityisesti isännän tarjoamien objektien kanssa. Esimerkiksi querySelectorAll
:n NodeList
:n määrittely päivitettiin tukemaan iteraatiota. getElementsByTagName
:n HTMLCollection
:n spesifikaatiota ei muutettu.
Käytä iteraattoria eksplisiittisesti (ES2015+). Katso #4, meidän on nähtävä, miten iteraattorit toimivat.
Toisinaan saatat haluta muuntaa array-tyyppisen objektin todelliseksi arrayksi. Sen tekeminen on yllättävän helppoa:
Käytä slice
-metodia arrayjen tapauksessa.
Voimme käyttää arrayjen slice
-metodia, joka muiden edellä mainittujen metodien tapaan on "tarkoituksellisesti yleinen" ja sitä voidaan siis käyttää arrayjen kaltaisten objektien kanssa, esimerkiksi näin:
var trueArray = Array.prototype.slice.call(arrayLikeObject);
Jos siis esimerkiksi haluamme muuntaa NodeList
-luettelon trueArray:ksi, voimme tehdä näin:
var divs = Array.prototype.slice.call(document.querySelectorAll("div"));
Katso Caveat for host-provided objects jäljempänä. Huomaa erityisesti, että tämä epäonnistuu IE8:ssa ja sitä aikaisemmissa IE8:ssa, jotka eivät anna sinun käyttää isännän tarjoamia objekteja this
:nä.
Käytä spread-syntaksi (...
).
On myös mahdollista käyttää ES2015'n spread-syntaksia JavaScript-moottoreissa, jotka tukevat tätä ominaisuutta:
var trueArray = [...iterableObject];
Jos siis esimerkiksi haluamme muuntaa NodeList
-luettelon trueArray:ksi, spread-syntaksilla tästä tulee varsin ytimekästä:
var divs = [...document.querySelectorAll("div")];
Käytä Array.from
(spec) | (MDN) | (MDN)
Array.from
(ES2015+, mutta helposti polyfillattavissa) luo array-muotoisesta objektista array-muotoisen array-muotoisen objektin, jolloin merkinnät ohjataan valinnaisesti ensin mapping-funktion läpi. Näin:
var divs = Array.from(document.querySelectorAll("div")));
Tai jos haluaisit saada tietyn luokan sisältävien elementtien tagien nimien joukon, käyttäisit mapping-funktiota:
// Nuolifunktio (ES2015):
var divs = Array.from(document.querySelectorAll(".some-class"), element => element.tagName);
// Standardifunktio (koska Array.from
voidaan shimmata):
var divs = Array.from(document.querySelectorAll(".some-class"), function(element) {
return element.tagName;
});
Jos käytät Array.prototype
-funktioita host-provided array-tyyppisten objektien kanssa (DOM-listat ja muut selaimen eikä JavaScript-moottorin tarjoamat asiat), sinun on testattava kohdeympäristöissäsi, jotta voit varmistua siitä, että host-provided objekti käyttäytyy oikein. Useimmat käyttäytyvät oikein (nyt), mutta on tärkeää testata. Syynä on se, että useimmat Array.prototype
-metodit, joita todennäköisesti haluat käyttää, perustuvat siihen, että isännän tarjoama objekti antaa rehellisen vastauksen abstraktiin [[HasProperty]]
-operaatioon. Tätä kirjoitettaessa selaimet tekevät tämän erittäin hyvin, mutta 5.1-määrittelyssä otettiin huomioon mahdollisuus, että isännän tarjoama objekti ei ole rehellinen. Se on kohdassa §8.6.2, useita kappaleita ison taulukon alapuolella lähellä kyseisen osion alkua), jossa sanotaan:
Isäntäobjektit voivat toteuttaa nämä sisäiset metodit millä tahansa tavalla, ellei toisin määrätä; esimerkiksi yksi mahdollisuus on, että
[[Get]]
ja[[Put]]
tietyn isäntäobjektin kohdalla tosiaan noutavat ja tallentavat ominaisuuksien arvoja, mutta[[HasProperty]]
tuottaa aina false. (En löytänyt vastaavaa sanamuotoa ES2015-spekseistä, mutta näin on varmasti edelleen.) Jälleen kerran, tätä kirjoitettaessa yleiset isännän tarjoamat, nykyaikaisissa selaimissa olevat array-tyyppiset objektit [esimerkiksiNodeList
-instanssit] käsittelevät[[HasProperty]]
:n oikein, mutta on tärkeää testata).
Huomautus: Tämä vastaus on toivottoman vanhentunut. Nykyaikaisempi lähestymistapa on Joukkoon käytettävissä olevat menetelmät. Kiinnostavia menetelmiä voivat olla:
Tavallinen tapa kerrata joukkoa JavaScriptissä on vanillainen for
-silmukka:
var length = arr.length,
element = null;
for (var i = 0; i < length; i++) {
element = arr[i];
// Do something with element
}
Huomaa kuitenkin, että tämä lähestymistapa on hyvä vain, jos sinulla on tiheä array, ja jokainen indeksi on varattu elementille. Jos matriisi on harva, voit törmätä suorituskykyongelmiin tämän lähestymistavan kanssa, koska iteroit monia indeksejä, joita ei todellisuudessa ole matriisissa. Tällöin for .. in
-silmukka voisi olla parempi idea. Mutta, sinun on käytettävä asianmukaisia suojatoimia varmistaaksesi, että vain haluttuihin array-ominaisuuksiin (eli array-elementteihin) vaikutetaan, koska for..in
-silmukkaa käytetään myös vanhoissa selaimissa, tai jos lisäominaisuudet on määritelty enumeroitaviksi
.
ECMAScript 5:ssä tulee olemaan forEach-metodi array-prototyypille, mutta sitä ei tueta vanhoissa selaimissa. Jotta voit käyttää sitä johdonmukaisesti, sinulla on oltava joko ympäristö, joka tukee sitä (esimerkiksi Node.js palvelinpuolen JavaScriptille), tai sinun on käytettävä "Polyfill" -ohjelmaa. Tämän toiminnallisuuden polyfill on kuitenkin triviaali, ja koska se tekee koodista helpommin luettavaa, se on hyvä polyfill sisällyttää.
Jos haluat tehdä silmukan joukon läpi, käytä tavallista kolmiosaista for
-silmukkaa.
for (var i = 0; i < myArray.length; i++) {
var arrayItem = myArray[i];
}
Voit optimoida suorituskykyä välimuistiin tallentamalla myArray.length
tai iteroimalla sen yli takaperin.