Ako môžem prechádzať všetky položky v poli pomocou jazyka JavaScript?
Myslel som, že je to niečo také:
forEach(instance in theArray)
Kde theArray
je moje pole, ale zdá sa, že to nie je správne.
TL;DR
for-in
, pokiaľ ho nepoužívate s ochrannými opatreniami alebo si aspoň nie ste vedomí, prečo by vás to mohlo štípať.for-of
(len ES2015+),Array#forEach
(spec
| MDN
) (alebo jeho príbuzné some
a podobne) (iba ES5+),for
,for-in
s ochrannými prvkami.
Ale je toho oveľa viac, čo môžete preskúmať, čítajte ďalej...JavaScript má výkonnú sémantiku pre cykly cez polia a objekty podobné poliam. Odpoveď som'rozdelil na dve časti: Možnosti pre skutočné polia a možnosti pre veci, ktoré sú len poliampodobné, ako napríklad objekt argumenty
, iné iterovateľné objekty (ES2015+), kolekcie DOM atď.
Rýchlo poznamenám, že možnosti ES2015 môžete používať už teraz, dokonca aj na motoroch ES5, a to transpiláciou* ES2015 do ES5. Vyhľadajte "ES2015 transpiling" / "ES6 transpiling" pre viac informácií...
Dobre, pozrime sa na naše možnosti:
Máte tri možnosti v ECMAScript 5 ("ES5"), v súčasnosti najširšie podporovanej verzii, a ďalšie dve pridané v ECMAScript 2015 ("ES2015", "ES6"):
forEach
a súvisiacich (ES5+)for
for-in
správnefor-of
(implicitne použite iterátor) (ES2015+)forEach
a súvisiaceV každom nejasne modernom prostredí (teda nie v IE8), kde máte prístup k funkciám Array
pridaným v ES5 (priamo alebo pomocou polyfillov), môžete použiť forEach
(spec
| MDN
):
-- begin snippet: js hide: false console: true babel: false -->
var a = ["a", "b", "c"];
a.forEach(function(entry) {
console.log(entry);
});
forEach
akceptuje funkciu spätného volania a voliteľne hodnotu, ktorá sa použije ako this
pri volaní tohto spätného volania (vyššie nepoužité). Spätné volanie sa zavolá pre každú položku v poli v poradí, pričom sa preskočia neexistujúce položky v riedkych poliach. Hoci som vyššie použil len jeden argument, spätné volanie sa volá s tromi: Hodnota každej položky, index tejto položky a odkaz na pole, ktoré iterujete (v prípade, že ho vaša funkcia ešte nemá po ruke).
Pokiaľ'nepodporujete zastarané prehliadače, ako je IE8 (ktorého trhový podiel podľa údajov spoločnosti NetApps v čase písania tohto článku v septembri 2016 bol len niečo vyše 4 %), môžete forEach
spokojne používať na univerzálnej webovej stránke bez shimu. Ak potrebujete podporovať zastarané prehliadače, shimming/polyfilling forEach
sa dá ľahko vykonať (vyhľadajte "es5 shim" pre niekoľko možností).
forEach
má tú výhodu, že nemusíte deklarovať indexovacie a hodnotové premenné v obsahujúcom obore, pretože sú dodávané ako argumenty iteračnej funkcie, a tak sú pekne zakreslené len na túto iteráciu.
Ak sa obávate nákladov na vykonávanie funkcie pre každú položku poľa, nemusíte; podrobnosti.
Okrem toho je forEach
funkcia "loop through them all", ale ES5 definovala niekoľko ďalších užitočných "work your way through the array and do things" funkcií, vrátane:
every
(zastaví cyklus pri prvom vrátení false
alebo niečoho chybného)some
(zastaví cyklus, keď spätné volanie vráti prvýkrát true
alebo niečo pravdivé)filter
(vytvorí nové pole obsahujúce prvky, pri ktorých funkcia filter vráti pravdivé
a vynechá tie, pri ktorých vráti nepravdivé
)map
(vytvorí nové pole z hodnôt vrátených spätným volaním)reduce
(vytvorí hodnotu opakovaným volaním spätného volania, pričom odovzdá predchádzajúce hodnoty; podrobnosti nájdete v špecifikácii; užitočné na sčítanie obsahu poľa a mnoho ďalších vecí)reduceRight
(ako reduce
, ale pracuje v zostupnom, nie vzostupnom poradí)for
Niekedy sú staré spôsoby najlepšie:
var index;
var a = ["a", "b", "c"];
for (index = 0; index < a.length; ++index) {
console.log(a[index]);
}
Ak sa dĺžka poľa nebude počas cyklu meniť a je to v kóde citlivom na výkon (nepravdepodobné), trochu komplikovanejšia verzia, ktorá chytá dĺžku vopred, by mohla byť tiny trochu rýchlejšia:
var index, len;
var a = ["a", "b", "c"];
for (index = 0, len = a.length; index < len; ++index) {
console.log(a[index]);
}
A/alebo spätné počítanie:
var index;
var a = ["a", "b", "c"];
for (index = a.length - 1; index >= 0; --index) {
console.log(a[index]);
}
V moderných JavaScriptových enginoch však len zriedkakedy potrebujete vyžmýkať posledný kúsok šťavy.
V ES2015 a vyšších verziách môžete premenné index a hodnota urobiť lokálnymi v cykle for
:
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"
}
A keď to urobíte, nielen value
, ale aj index
sa znovu vytvorí pre každú iteráciu cyklu, čo znamená, že uzávery vytvorené v tele cyklu si zachovajú odkaz na index
(a value
) vytvorený pre danú iteráciu:
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>
Ak by ste mali päť divov, dostali by ste "Index is: 0" ak by ste klikli na prvý a "Index je: 4" ak by ste klikli na posledný. Toto nefunguje, ak použijete var
namiesto let
.
for-in
správneĽudia vám budú hovoriť, aby ste používali for-in
, ale na to for-in
neslúži. for-in
prechádza cez vypočítateľné vlastnosti objektu, nie cez indexy poľa. Poradie nie je zaručené, dokonca ani v ES2015 (ES6). ES2015+ síce definuje poradie vlastností objektu (prostredníctvom [[OwnPropertyKeys]]
, [[Enumerate]]
a vecí, ktoré ich používajú, ako napríklad Object.getOwnPropertyKeys
), ale nedefinuje, že for-in
bude toto poradie dodržiavať. (Podrobnosti v tejto inej odpovedi.)
Jediné skutočné prípady použitia for-in
na poli sú:
for-in
na navštívenie týchto voľných prvkov poľa:
// `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]);
}
}
Všimnite si tri kontroly:
dĺžka
poľa. (Napr. dĺžka poľa'sa zmestí do 32-bitového celočíselného čísla bez znamienka.) (Vďaka RobG za to, že v komentári k môjmu príspevku na blogu upozornil, že môj predchádzajúci test nebol celkom správny.)
Samozrejme, že v inline kóde by ste to neurobili. Napísali by ste užitočnú funkciu. Možno:
// 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
(implicitné použitie iterátora) (ES2015+)ES2015 pridáva do JavaScriptu iterátory. Najjednoduchším spôsobom, ako používať iterátory, je nový príkaz for-of
. Vyzerá takto:
const a = ["a", "b", "c"];
for (const val of a) {
console.log(val);
}
Pod pokrievkou, ktorá získa iterátor z poľa a prechádza ním v cykle, pričom z neho získava hodnoty. Toto nemá problém, ktorý má použitie for-in
, pretože používa iterátor definovaný objektom (poľom) a polia definujú, že ich iterátory iterujú cez svoje prvky (nie ich vlastnosti). Na rozdiel od for-in
v ES5 je poradie, v ktorom sú položky navštevované, číselné poradie ich indexov.
Niekedy môžete chcieť použiť iterátor explicitne. Aj to môžete urobiť, hoci je to'oveľa ťažkopádnejšie ako for-of
. Vyzerá to takto:
const a = ["a", "b", "c"];
const it = a.values();
let entry;
while (!(entry = it.next()).done) {
console.log(entry.value);
}
Iterátor je objekt zodpovedajúci definícii Iterator v špecifikácii. Jeho metóda next
vracia nový výsledkový objekt pri každom volaní. Objekt result má vlastnosť done
, ktorá nám hovorí, či je hotový, a vlastnosť value
s hodnotou pre danú iteráciu. (done
je nepovinné, ak by bolo false
, value
je nepovinné, ak by bolo undefined
.)
Význam value
sa líši v závislosti od iterátora; polia podporujú (aspoň) tri funkcie, ktoré vracajú iterátory:
values()
: Túto funkciu som použil vyššie. Vracia iterátor, kde každá hodnota
je položka poľa pre danú iteráciu ("a"
, "b"
a "c"
v predchádzajúcom príklade).keys()
: Vráti iterátor, kde každá hodnota
je kľúčom pre danú iteráciu (takže pre naše a
vyššie by to bolo "0"
, potom "1"
, potom "2"
).entries()
: Vracia iterátor, kde každá hodnota
je pole v tvare [kľúč, hodnota]
pre danú iteráciu.Okrem pravých polí existujú aj objekty podobné poliam, ktoré majú vlastnosť dĺžka
a vlastnosti s číselnými názvami: inštancie NodeList
, objekt argumenty
atď. Ako prechádzame ich obsah v cykle?
Prinajmenšom niektoré a možno väčšina alebo dokonca všetky vyššie uvedené prístupy k poliam sa často rovnako dobre uplatňujú aj na objekty podobné poliam:
Používajte forEach
a príbuzné (ES5+)
Rôzne funkcie na Array.prototype
sú "zámerne všeobecné" a zvyčajne sa dajú použiť na objekty podobné poliam prostredníctvom Function#call
alebo Function#apply
. (Pozri Caveat for host-provided objects na konci tejto odpovede, ale je to'zriedkavý problém.)
Predpokladajme, že chcete použiť forEach
na vlastnosť Node
's childNodes
. Urobili by ste toto:
Array.prototype.forEach.call(node.childNodes, function(child) {
// Urobiť niečo s child
});
Ak'to budete robiť často, možno budete chcieť uchopiť kópiu odkazu na funkciu do premennej na opakované použitie, napr:
// (Toto všetko je pravdepodobne v nejakej funkcii s rozsahom)
var forEach = Array.prototype.forEach;
// Potom neskôr...
forEach.call(node.childNodes, function(child) {
// Urobte niečo s child
});
Použite jednoduchý cyklus for
Je zrejmé, že jednoduchý cyklus for
sa vzťahuje na objekty typu pole.
Používajte for-in
správne
for-in
s rovnakými ochrannými opatreniami ako pri poli by mal fungovať aj pri objektoch podobných poľu; môže platiť výhrada pre objekty poskytované hostiteľom v bode č. 1 vyššie.
Používajte for-of
(implicitne použite iterátor) (ES2015+)
for-of
použije iterátor poskytnutý objektom (ak nejaký existuje); budeme musieť zistiť, ako to funguje s rôznymi objektmi podobnými poliam, najmä s objektmi poskytovanými hostiteľom. Napríklad špecifikácia pre NodeList
z querySelectorAll
bola aktualizovaná tak, aby podporovala iteráciu. Špecifikácia pre HTMLCollection
z getElementsByTagName
nebola doplnená.
Používajte iterátor explicitne (ES2015+) Pozri #4, budeme'musieť zistiť, ako sa iterátory prejavia.
V iných prípadoch môžete chcieť previesť objekt podobný poľu na pravé pole. Urobiť to je prekvapivo jednoduché:
Použite metódu slice
polí
Môžeme použiť metódu slice
polí, ktorá je podobne ako ostatné vyššie uvedené metódy "zámerne všeobecná", a tak ju môžeme použiť s objektmi podobnými poliam, ako je to napríklad v tomto prípade:
trueArray = Array.prototype.slice.call(arrayLikeObject);
Takže ak chceme napríklad previesť NodeList
na true array, môžeme to urobiť takto:
var divs = Array.prototype.slice.call(document.querySelectorAll("div"));
Pozrite si Caveat for host-provided objects nižšie. Všimnite si, že to zlyhá najmä v IE8 a starších verziách, ktoré neumožňujú používať objekty poskytované hostiteľom ako this
.
Použite syntax šírenia (...
)
Je'tiež možné použiť ES2015's spread syntax s JavaScriptovými motormi, ktoré túto funkciu podporujú:
trueArray = [...iterableObject];
Ak teda napríklad chceme previesť NodeList
na true array, pomocou syntaxe spread je to celkom stručné:
var divs = [...document.querySelectorAll("div")];
Použite Array.from
(spec) | (MDN)
Funkcia Array.from
(ES2015+, ale ľahko polyfiltrovateľná) vytvára pole z objektu podobného poľu, pričom voliteľne najprv prechádza položky cez mapovaciu funkciu. Takže:
var divs = Array.from(document.querySelectorAll("div"));
Alebo ak by ste chceli získať pole názvov značiek prvkov s danou triedou, použili by ste funkciu mapovania:
// (ES2015):
divs = Array.from(document.querySelectorAll(".some-class"), element => element.tagName);
// Štandardná funkcia (keďže Array.from
možno skrátiť):
var divs = Array.from(document.querySelectorAll(".some-class"), function(element) {
return element.tagName;
});
Ak používate funkcie Array.prototype
s objektmi podobnými poliam poskytnutým hostiteľom (zoznamy DOM a iné veci poskytované prehliadačom, a nie jadrom JavaScriptu), musíte sa uistiť, že ste v cieľovom prostredí otestovali, či sa objekt poskytnutý hostiteľom správa správne. Väčšina sa správa správne (teraz), ale je dôležité to otestovať. Dôvodom je, že väčšina metód Array.prototype
, ktoré budete chcieť používať, sa spolieha na to, že objekt poskytnutý hostiteľom poskytne pravdivú odpoveď na abstraktnú operáciu [[HasProperty]]
. V čase písania tohto článku to prehliadače robia veľmi dobre, ale špecifikácia 5.1 pripúšťa možnosť, že objekt poskytnutý hostiteľom nemusí byť úprimný. Je to v §8.6.2, niekoľko odsekov pod veľkou tabuľkou blízko začiatku tejto časti), kde sa píše:
Hostiteľské objekty môžu implementovať tieto interné metódy ľubovoľným spôsobom, ak nie je uvedené inak; napríklad jednou z možností je, že
[[Get]]
a[[Put]]
pre konkrétny hostiteľský objekt skutočne načítajú a ukladajú hodnoty vlastností, ale[[HasProperty]]
vždy generuje false. (V špecifikácii ES2015 som nenašiel ekvivalentné slovné spojenie, ale určite to tak stále je.) Opäť platí, že v čase písania tohto článku bežné objekty podobné poliam poskytované hostiteľom v moderných prehliadačoch [napríklad inštancieNodeList
] **spracúvajú[[HasProperty]]
správne, ale je dôležité to otestovať).
Poznámka: Táto odpoveď je beznádejne zastaraná. Modernejší prístup nájdete na stránke metódy dostupné na poli. Zaujímavé metódy by mohli byť:
Štandardný spôsob iterácie poľa v JavaScripte je jednoduchá slučka for
:
var length = arr.length,
element = null;
for (var i = 0; i < length; i++) {
element = arr[i];
// Do something with element
}
Všimnite si však, že tento prístup je dobrý len vtedy, ak máte husté pole a každý index je obsadený prvkom. Ak je pole riedke, potom môžete pri tomto prístupe naraziť na výkonnostné problémy, pretože budete iterovať cez množstvo indexov, ktoré v poli skutočne neexistujú. V takom prípade môže byť lepším nápadom slučka for .. in
. **Musíte však použiť vhodné ochranné opatrenia, aby ste zabezpečili, že sa bude konať len s požadovanými vlastnosťami poľa (t. j. s prvkami poľa), pretože slučka for ...in
bude v starších prehliadačoch aj enumerovaná, alebo ak sú dodatočné vlastnosti definované ako enumerovateľné
.
V ECMAScripte 5 bude na prototype poľa metóda forEach, ktorá však nie je podporovaná v starších prehliadačoch. Aby ste ju teda mohli dôsledne používať, musíte mať buď prostredie, ktoré ju podporuje (napríklad Node.js pre JavaScript na strane servera), alebo použiť "Polyfill". Polyfill pre túto funkcionalitu je však triviálny a keďže uľahčuje čítanie kódu, je dobré ho zahrnúť.
Ak chcete vytvoriť cyklus nad poľom, použite štandardný trojdielny cyklus for
.
for (var i = 0; i < myArray.length; i++) {
var arrayItem = myArray[i];
}
Niektoré optimalizácie výkonu môžete dosiahnuť ukladaním myArray.length
do vyrovnávacej pamäte alebo spätným iterovaním.