Kuidas ma saan JavaScript'i abil läbi lugeda kõik massiivi kirjed?
Ma arvasin, et see on midagi sellist:
forEach(instance in theArray)
Kus TheArray
on minu massiivi, kuid see tundub olevat vale.
TL;DR
for-in
, kui te ei kasuta seda kaitsemeetmetega või kui te vähemalt ei tea, miks see võib teid hammustada.for-of
silmus (ainult ES2015+),Array#forEach
(spec
| MDN
) (või selle sugulased some
ja muu taoline) (ainult ES5+),for
silmus,for-in
koos kaitsemeetmetega.
Aga seal'on palju rohkem uurida, loe edasi...JavaScriptil on võimas semantika massiivide ja massiivilaadsete objektide läbimiseks. Ma'olen jaganud vastuse kahte ossa: Valikud tõeliste massiividega ja valikud asjadega, mis on lihtsalt massiivilaadsed, nagu näiteks objekt arguments
, muud iteratiivsed objektid (ES2015+), DOM-kollektsioonid ja nii edasi.
Märgin kiiresti, et ES2015 valikuid saab kasutada juba, isegi ES5 mootorites, transpileerides ES2015 ES5-le. Otsige "ES2015 transpiling" / "ES6 transpiling" rohkem...
Okei, vaatame meie võimalusi:
Teil on kolm võimalust ECMAScript 5 ("ES5"), mis on hetkel kõige laialdasemalt toetatud versioon, ja veel kaks, mis on lisatud ECMAScript 2015 ("ES2015", "ES6"):
forEach
ja sellega seotud (ES5+)for
tsüklitfor-in
korrektselt.for-of
(kasutage kaudselt iteraatorit) (ES2015+)forEach
ja sellega seotudIgas ebamääraselt moodsas keskkonnas (seega mitte IE8), kus teil on juurdepääs ES5 poolt lisatud Array
funktsioonidele (otse või kasutades polyfills), saate kasutada forEach
(spec
| MDN
):
var a = ["a", "b", "c"];
a.forEach(function(entry) {
console.log(entry);
});
forEach
võtab vastu tagasilöögifunktsiooni ja valikuliselt väärtuse, mida kasutatakse this
-ina selle tagasilöögi kutsumisel (eespool ei ole kasutatud). Callback kutsutakse välja iga massiivi kirje kohta, järjekorras, jättes hõredates massiivides olematuid kirjeid vahele. Kuigi ma kasutasin eespool ainult ühte argumenti, kutsutakse callbacki kolme argumendiga: Iga kirje väärtus, selle kirje indeks ja viide massiivile, mille üle iteratsioon toimub (juhul, kui teie funktsioonil ei ole seda juba käepärast).
Kui te ei'toeta vananenud brausereid nagu IE8 (mille NetApps näitab veidi üle 4% turuosa käesoleva artikli kirjutamise ajal septembris 2016), võite forEach
i üldotstarbelises veebilehes õnnelikult kasutada ilma shimita. Kui teil on vaja toetada vananenud brausereid, on forEach
i shimming/polyfilling hõlpsasti teostatav (otsige "es5 shim" mitmete võimaluste jaoks).
forEach
i eeliseks on see, et te ei pea deklareerima indekseerimis- ja väärtuse muutujaid sisaldavas ulatuses, kuna need on esitatud argumentidena iteratsioonifunktsioonile ja seega kenasti ainult selle iteratsiooni jaoks.
Kui te muretsete iga massiivi kirje jaoks funktsioonikõne tegemise jooksuaegsete kulude pärast, siis ärge olge; details.
Lisaks on forEach
"loop through them all" funktsioon, kuid ES5 defineeris mitmeid teisi kasulikke "work your way through the array and do things" funktsioone, sealhulgas:
every
(lõpetab tsükli läbimise, kui tagasiside esimest korda tagastab false
või midagi valet).some
(lõpetab tsükli läbimise esimesel korral, kui tagasiside tagastab true
või midagi tõepärase).filter
(loob uue massiivi, mis sisaldab elemente, mille puhul filterfunktsioon tagastab true
ja jätab välja need, mille puhul tagastatakse false
)map
(loob uue massiivi tagasilöögi poolt tagastatud väärtustest)reduce
(moodustab väärtuse, kutsudes korduvalt tagasikutsumist, edastades eelnevaid väärtusi; vt üksikasju spetsifikatsioonist; kasulik massiivi sisu summeerimiseks ja paljudeks muudeks asjadeks).reduceRight
(nagu reduce
, kuid töötab pigem kahanevas kui kasvavas järjekorras)for
tsüklitMõnikord on vanad viisid parimad:
var index;
var a = ["a", "b", "c"];
for (index = 0; index < a.length; ++index) {
console.log(a[index]);
}
Kui massiivi pikkus ei muutu tsükli ajal ja see'on jõudluse suhtes tundlikus koodis (ebatõenäoline), võib veidi keerulisem versioon, mis haarab pikkuse ette, olla tiny natuke kiirem:
var index, len;
var a = ["a", "b", "c"];
for (index = 0, len = a.length; index < len; ++index) {
console.log(a[index]);
}
Ja/või tagasi lugedes:
var index;
var a = ["a", "b", "c"];
for (index = a.length - 1; index >= 0; --index) {
console.log(a[index]);
}
Kuid kaasaegsete JavaScript-mootoritega on harva vaja seda viimast mahlakust välja tõmmata.
ES2015 ja uuemates versioonides saate muuta oma indeksi ja väärtuse muutujad for
tsüklile lokaalseks:
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 kui sa seda teed, siis mitte ainult value
, vaid ka index
luuakse uuesti iga tsükli iteratsiooni jaoks, mis tähendab, et tsükli kehas loodud sulgemised hoiavad viidet index
ile (ja value
ile), mis on loodud selle konkreetse iteratsiooni jaoks:
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>
Kui teil oleks viis div-i, siis'saaksite "Index on: 0" kui te klõpsaksite esimesel ja "Index on: 4" kui te klõpsasite viimasele. See ei tööta, kui kasutate var
asemel let
.
for-in
korrektselt.Sa'saad inimesi, kes ütlevad sulle, et kasuta for-in
, kuid selleks ei ole for-in
mõeldud. for-in
läbib objekti loetavad omadused, mitte massiivi indeksid. Se järjekord ei ole garanteeritud, isegi mitte ES2015 (ES6) puhul. ES2015+ määratleb küll objekti omaduste järjekorra (läbi [[OwnPropertyKeys]]
, [[Enumerate]]
ja neid kasutavate asjade nagu Object.getOwnPropertyKeys
), kuid see ei määratle, et for-in
järgib seda järjekorda. (Üksikasjad selles teises vastuses.)
Ainukesed tõelised kasutusjuhud for-in
jaoks massiivis on järgmised:
for-in
, et külastada neid hõredaid massiivi elemente, kui kasutate asjakohaseid kaitsemeetmeid:
// `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]);
}
}
Pange tähele kolme kontrolli:
length
võib omada. (Nt. massiivi'pikkus mahub 32-bitise täisarvu sisse.) *(Props RobG-le, kes osutas kommentaaris minu blogipostituse, et minu eelmine test ei olnud päris õige.)
Loomulikult ei teeks te seda inline-koodis. Sa'd kirjutaksid kasuliku funktsiooni. Võib-olla:
// 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
(kasutage kaudselt iteraatorit) (ES2015+)ES2015 lisab JavaScriptile iteratoreid. Kõige lihtsam viis iteraatorite kasutamiseks on uus for-of
avaldis. See näeb välja nii:
const a = ["a", "b", "c"];
for (const val of a) {
console.log(val);
}
Kaante all, mis saab iterator massiivist ja loopib selle läbi, saades sealt väärtused. Sellel ei ole probleemi, mis on for-in
kasutamisel, sest see kasutab objekti (massiivi) poolt defineeritud iteraatorit ja massiivid defineerivad, et nende iteraatorid itereerivad läbi nende sisendite (mitte nende omaduste). Erinevalt for-in
-st ES5-s on kirjete külastamise järjekord nende indeksite numbriline järjekord.
Mõnikord võib soovida kasutada iteraatorit eksplitsiitselt. Ka seda saab teha, kuigi see'on palju kohmakam kui for-of
. See näeb välja nii:
const a = ["a", "b", "c"];
const it = a.values();
let entry;
while (!(entry = it.next()).done) {
console.log(entry.value);
}
Iteraator on objekt, mis vastab spetsifikatsioonis esitatud Iteraatori definitsioonile. Selle meetod next
tagastab iga kord, kui seda kutsutakse, uue tulemusobjekti. Tulemusobjektil on omadus done
, mis ütleb meile, kas ta on valmis, ja omadus value
selle iteratsiooni väärtusega. (done
on valikuline, kui see oleks false
, value
on valikuline, kui see oleks undefined
).
value
tähendus varieerub sõltuvalt iteraatorist; massiivid toetavad (vähemalt) kolme funktsiooni, mis tagastavad iteraatoreid:
values()
: See on see, mida ma eespool kasutasin. See tagastab iteraatori, kus iga value
on selle iteratsiooni massiivisisend ("a"
, "b"
ja "c"
eelnevas näites).keys()
: Tagastab iteraatori, kus iga väärtus
on selle iteratsiooni võti (nii et meie a
puhul oleks see "0"
, siis "1"
, siis "2"
).entries()
: Tagastab iteraatori, kus iga value
on massiiv kujul [key, value]
selle iteratsiooni jaoks.Lisaks tõelistele massiividele on olemas ka massiivi sarnased objektid, millel on omadus length
ja numbriliste nimedega omadused: NodeList
instantsid, arguments
objekt jne. Kuidas me nende sisu läbi loopeerime?
Vähemalt mõned ja võib-olla enamik või isegi kõik ülaltoodud massiivi lähenemisviisid kehtivad sageli võrdselt hästi ka massiivi sarnaste objektide puhul:
Kasutage forEach
ja sellega seonduvat (ES5+).
Erinevad funktsioonid Array.prototype
is on "tahtlikult üldised" ja neid saab tavaliselt kasutada massiivilaadsete objektide puhul Function#call
või Function#apply
kaudu. (Vt Caveat host'i poolt pakutavate objektide jaoks selle vastuse lõpus, kuid see'on harva esinev probleem).
Oletame, et te tahate kasutada forEach
i Node
'i childNodes
i omadust. Sa'teeksid seda:
Array.prototype.forEach.call(node.childNodes, function(child) {
// Tee midagi child
iga
});
Kui sa'kavatsed seda palju teha, siis võid soovida funktsiooniviite koopia haarata muutujasse korduvkasutamiseks, nt:
// (See kõik on arvatavasti mõnes skopeerimisfunktsioonis).
var forEach = Array.prototype.forEach;
// Siis hiljem...
forEach.call(node.childNodes, function(child) {
// Tee midagi lapse
ga
});
Kasutame lihtsat for
tsüklit
Ilmselgelt kehtib lihtne for
silmus massiivi sarnaste objektide puhul.
Kasutage for-in
tsüklit korrektselt.
for-in
samade kaitsemeetmetega nagu massiivi puhul peaks töötama ka massiivi-sarnaste objektide puhul; hostilt saadud objektide suhtes võib kehtida eespool #1 toodud hoiatus.
Kasutage for-of
(kasutage kaudselt iteraatorit) (ES2015+)
for-of
kasutab objekti poolt pakutavat iteraatorit (kui see on olemas); me'peame vaatama, kuidas see mängib erinevate massiivi-sarnaste objektidega, eriti host-varustatud objektidega. Näiteks querySelectorAll
i NodeList
i spetsifikatsioon uuendati, et toetada iteratsiooni. getElementsByTagName'i
HTMLCollection'i spetsifikatsiooni ei ole muudetud.
Kasutage iteraatorit selgesõnaliselt (ES2015+). Vt #4, me'peame vaatama, kuidas iteraatorid toimivad.
Teinekord võib olla vaja muuta massiivi sarnane objekt tõeliseks massiivi. Selle tegemine on üllatavalt lihtne:
Kasutage massiivide meetodit slice
.
Me võime kasutada massiivi slice
meetodit, mis nagu ka teised eespool mainitud meetodid on "tahtlikult üldine" ja seega saab seda kasutada massiivi-sarnaste objektidega, näiteks nii:
var trueArray = Array.prototype.slice.call(arrayLikeObject);
Nii näiteks, kui me tahame konverteerida NodeList
i tõeliseks massiivi, võiksime teha nii:
var divs = Array.prototype.slice.call(document.querySelectorAll("div"));
Vt allpool Caveat peremehe poolt pakutavate objektide jaoks. Pange eelkõige tähele, et see ebaõnnestub IE8-s ja varasemates versioonides, mis ei lase teil kasutada host-varustatud objekte this
-ina niimoodi.
Kasutage spread syntax (...
)
See'on võimalik kasutada ka ES2015's spread syntax koos JavaScript mootoritega, mis toetavad seda funktsiooni:
var trueArray = [...iterableObject];
Nii näiteks, kui me tahame konverteerida NodeList
i true array'ks, siis spread süntaksiga muutub see üsna kokkuvõtlikuks:
var divs = [...document.querySelectorAll("div")];
Kasutame Array.from
(spec) | (MDN)
Array.from
(ES2015+, kuid kergesti polüfunktsionaalne) loob massiivi massiivi sarnasest objektist, edastades kirjed valikuliselt kõigepealt kaardistamisfunktsiooni kaudu. Seega:
var divs = Array.from(document.querySelectorAll("div"));
Või kui soovite saada antud klassi elementide tagide nimede massiivi, siis'kasutaksite mapping-funktsiooni:
// Noole funktsioon (ES2015):
var divs = Array.from(document.querySelectorAll(".some-class"), element => element.tagName);
// Tavafunktsioon (kuna Array.from
saab varjutada):
var divs = Array.from(document.querySelectorAll(".some-class"), function(element) {
return element.tagName;
});
Kui kasutate Array.prototype
funktsioone host-provided massiivi sarnaste objektidega (DOM loendid ja muud asjad, mida pakub brauser, mitte JavaScript mootor), peate kindlasti testima oma sihtkeskkondades, et veenduda, et host-provided objekt käitub õigesti. Se käituvad enamasti korralikult (praegu), kuid on oluline testida. Põhjuseks on see, et enamik Array.prototype
meetodeid, mida te tõenäoliselt soovite kasutada, tuginevad sellele, et host-varustatud objekt annab ausa vastuse abstraktsele [[HasProperty]]
operatsioonile. Käesoleva artikli kirjutamise ajal teevad brauserid seda väga head tööd, kuid 5.1 spetsifikatsioon lubas võimalust, et host-varustatud objekt ei pruugi olla aus. See'on §8.6.2, mitu lõiku allpool suurt tabelit selle lõigu alguses), kus öeldakse:
Host-objektid võivad neid sisemisi meetodeid rakendada mis tahes viisil, kui ei ole teisiti määratud; näiteks üks võimalus on, et
[[Get]]
ja[[Put]]
konkreetse host-objekti puhul tõepoolest nopivad ja salvestavad omaduste väärtusi, kuid[[HasProperty]]
genereerib alati false. (Ma ei leidnud ES2015 spetsifikatsioonist vastavat sõnastust, kuid see'on kindlasti endiselt nii.) Jällegi, selle artikli kirjutamise ajal käitlevad tavalised host'i poolt pakutavad massiivilaadsed objektid kaasaegsetes brauserites [näiteksNodeList
instantsid] toimivad korrektselt[[HasProperty]]
ga, kuid see'on oluline testida).
Märkus: See vastus on lootusetult vananenud. Kaasaegsema lähenemise jaoks vaadake massiivi kohta saadaolevad meetodid. Huvipakkuvad meetodid võiksid olla:
Standardne viis JavaScript massiivide iteratsiooniks on vanilla for
-silmus:
var length = arr.length,
element = null;
for (var i = 0; i < length; i++) {
element = arr[i];
// Do something with element
}
Pange aga tähele, et see lähenemine on hea ainult siis, kui teil on tihe massiiv ja iga indeks on hõivatud elemendi poolt. Kui massiiv on hõre, siis võib selle lähenemisega tekkida jõudlusprobleeme, kuna te itereerite paljude indeksite üle, mida massiivis tõesti ei eksisteeri. Sellisel juhul võib olla parem idee for .. in
-silmus. Kaasa, peate kasutama asjakohaseid kaitsemeetmeid, et tagada, et toimiksid ainult massiivi soovitud omadused (st massiivi elemendid), kuna for..in
-silmus on ka vanades brauserites loendatav või kui lisaomadused on defineeritud loendatavatena
.
In ECMAScript 5 on massiivi prototüübil forEach meetod, kuid seda ei toeta vanad brauserid. Seega, et seda järjepidevalt kasutada, peab teil olema kas keskkond, mis seda toetab (näiteks Node.js serveripoolse JavaScripti puhul), või kasutada "Polyfill". Polyfill selle funktsionaalsuse jaoks on aga triviaalne ja kuna see muudab koodi kergemini loetavaks, on see hea polyfill, mida lisada.
Kui sa soovid teha tsüklit massiivi üle, kasuta standardset kolmeosalist for
tsüklit.
for (var i = 0; i < myArray.length; i++) {
var arrayItem = myArray[i];
}
Saate mõningaid jõudluse optimeerimisi, kui vahemällu salvestate myArray.length
või itereerite selle üle tagurpidi.