Kā, izmantojot JavaScript, veikt cilpu caur visiem masīva ierakstiem?
Es domāju, ka tas ir apmēram šādi:
forEach(instance in theArray)
kur theArray
ir mans masīvs, bet šķiet, ka tas ir nepareizi.
TL;DR
for-in
, ja vien neizmantojat to ar drošības pasākumiem vai vismaz nezināt, kādēļ tas var jums kaitēt.for-of
cilpa (tikai ES2015+),Array#forEach
(spec
| MDN
) (vai tā radinieki some
un tamlīdzīgi) (tikai ES5+),for
cilpa,for-in
ar drošības pasākumiem.
Bet ir vēl daudz ko izpētīt, lasiet tālāk...JavaScript ir spēcīga semantika, lai veiktu cilpu caur masīviem un masīviem līdzīgiem objektiem. Es šo atbildi esmu sadalījis divās daļās: Iespējas īstiem masīviem un iespējas lietām, kas ir tikai masīviemlīdzīgas, piemēram, argumentu
objektam, citiem iterējamiem objektiem (ES2015+), DOM kolekcijām utt.
Ātri atzīmēšu, ka ES2015 opcijas var izmantot jau tagad, pat ES5 dzinējos, transpilējot ES2015 uz ES5. Lai uzzinātu vairāk, meklējiet "ES2015 transpiling" / "ES6 transpiling"...
Labi, aplūkosim mūsu iespējas:
Jums ir trīs iespējas ECMAScript 5 ("ES5"), kas pašlaik ir visplašāk atbalstītā versija, un vēl divas iespējas, kas pievienotas ECMAScript 2015 ("ES2015", "ES6"):
forEach
un saistītos (ES5+)for
cilpufor-in
for-of
(netieši izmantot iteratoru) (ES2015+)forEach
un saistītosJebkurā neskaidri modernā vidē (tātad ne IE8), kur jums ir piekļuve ES5 pievienotajām Array
funkcijām (tieši vai izmantojot polifillus), varat izmantot forEach
(spec
| MDN
):
var a = ["a", "b", "c"];
a.forEach(function(entry) {
console.log(entry);
});
forEach
pieņem atpakaļsaukuma funkciju un pēc izvēles vērtību, kas jāizmanto kā this
, izsaucot šo atpakaļsaukumu (iepriekš neizmantots). Atgriezeniskais zvans tiek izsaukts katram masīva ierakstam secīgi, izlaižot neeksistējošus ierakstus retākos masīvos. Lai gan iepriekš es izmantoju tikai vienu argumentu, atpakaļsaukums tiek izsaukts ar trim argumentiem: Katra ieraksta vērtība, šī ieraksta indekss un atsauce uz masīvu, kuru iterējat (gadījumā, ja jūsu funkcijai tas vēl nav pa rokai).
Ja vien jūs neatbalstāt tādas novecojušas pārlūkprogrammas kā IE8 (kuras tirgus daļa, pēc NetApps datiem, šī raksta tapšanas brīdī, 2016. gada septembrī bija nedaudz vairāk par 4 %), jūs varat droši izmantot forEach
vispārējas nozīmes tīmekļa lapā bez apakšsadaļas. Ja jums ir jāatbalsta novecojušas pārlūkprogrammas, forEach
ir viegli izpildāms (meklējiet "es5 shim", lai atrastu vairākas iespējas).
forEach
priekšrocība ir tā, ka jums nav jādeklarē indeksēšanas un vērtības mainīgie ietverošajā sfērā, jo tie tiek sniegti kā iterācijas funkcijas argumenti, un tādējādi tie ir skaisti attiecināti tikai uz šo iterāciju.
Ja jūs uztrauc, ka katram masīva ierakstam būs jāveic funkcijas izsaukums, tad nevajag; informācija.
Turklāt forEach
ir "cilpa caur tiem visiem" funkcija, bet ES5 ir definētas vairākas citas noderīgas "iziet cauri masīvam un darīt lietas" funkcijas, tostarp:
every
(pārtrauc cilpas darbību, kad pirmo reizi atgriezeniskais zvans atgriež false
vai kaut ko nepareizu).some
(pārtrauc cilpas darbību, kad atgriezeniskais zvans pirmo reizi atgriež true
vai kaut ko patiesu)filter
(izveido jaunu masīvu, iekļaujot tajā elementus, kuros filtra funkcija atgriež true
, un izlaižot tos, kuros tā atgriež false
)map
(izveido jaunu masīvu no atpakaļsaukuma atgriezeniskās funkcijas atdotajām vērtībām)reduce
(izveido vērtību, atkārtoti izsaucot atgriezenisko zvanu, nododot iepriekšējās vērtības; sīkāku informāciju skatiet specifikācijā; noder masīva satura summēšanai un daudzām citām lietām).reduceRight
(tāpat kā reduce
, bet darbojas dilstošā, nevis augošā secībā)for
cilpuDažreiz vecie veidi ir labākie:
var index;
var a = ["a", "b", "c"];
for (index = 0; index < a.length; ++index) {
console.log(a[index]);
}
Ja masīva garums cikla laikā nemainīsies un tas ir kods, kam ir svarīga veiktspēja (maz ticams), nedaudz sarežģītāka versija, kas garumu paņem priekšā, varētu būt mazliet ātrāka:
var index, len;
var a = ["a", "b", "c"];
for (index = 0, len = a.length; index < len; ++index) {
console.log(a[index]);
}
Un/vai skaitot atpakaļ:
var index;
var a = ["a", "b", "c"];
for (index = a.length - 1; index >= 0; --index) {
console.log(a[index]);
}
Bet ar mūsdienu JavaScript dzinējiem reti kad ir nepieciešams izspiest šo pēdējo sulas devu.
ES2015 un jaunākajās versijās var padarīt indeksa un vērtības mainīgos lokālus for
cilpai:
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"
}
Un, kad jūs to darāt, ne tikai vērtība
, bet arī indekss
tiek izveidots no jauna katrai cikla iterācijai, tas nozīmē, ka cikla ķermenī izveidotie slēgumi saglabā atsauci uz konkrētajai iterācijai izveidoto indeksu
(un vērtību
):
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>
Ja jums būtu piecas divs, jūs saņemtu "Index is: 0", ja noklikšķinātu uz pirmā un "Index is: 4", ja noklikšķināt uz pēdējā. Tas nedarbojas, ja let
vietā izmantojat var
.
for-in
korektiJums cilvēki teiks izmantot for-in
, bet for-in
nav domāts tieši šim nolūkam11. for-in
veido cilpu caur objekta skaitāmajām īpašībām, nevis masīva indeksiem. Kārtas secība nav garantēta pat ES2015 (ES6). ES2015+ nosaka objektu īpašību secību (izmantojot [[[OwnPropertyKeys]]
]](https://tc39.github.io/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-ownpropertykeys), [[[Enumerate]]
]](https://tc39.github.io/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-enumerate) un lietas, kas tās izmanto, piemēram, Object.getOwnPropertyKeys
), bet tā nenosaka, ka for-in
ievēros šo secību. (Sīkāka informācija šajā citā atbildē.)
Vienīgie reālie for-in
izmantošanas gadījumi masīvam ir šādi:
for-in
, lai apmeklētu šos tukšos masīva elementus, ja izmantojat atbilstošus drošības pasākumus:
// `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]);
}
}
Ņemiet vērā trīs pārbaudes:
garumam
. (Piemēram, masīva garums iekļaujas 32 bitu neparādītajā veselā skaitlī.) (Atzinība RobG par to, ka viņš komentārā manā bloga ierakstā norādīja, ka mans iepriekšējais tests nebija gluži pareizs.) (*)
Protams, jūs to nedarītu inline kodā. Jūs rakstītu palīgfunkciju. Iespējams:
// 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
(netieši izmantot iteratoru) (ES2015+)ES2015 papildina JavaScript ar iteratoriem. Vieglākais veids, kā izmantot iteratorus, ir jaunais for-of
paziņojums. Tas izskatās šādi:
const a = ["a", "b", "c"];
for (const val of a) {
console.log(val);
}
Zem aizsega tas iegūst iteratoru no masīva un veido cilpu, iegūstot no tā vērtības. Šādā gadījumā nav problēmas, kas rodas, izmantojot for-in
, jo tiek izmantots objekta (masīva) definēts iterators, un masīvi nosaka, ka to iteratori iterē caur to ierakstiem (nevis īpašībām). Atšķirībā no for-in
ES5, ierakstu apmeklēšanas secība ir to indeksu skaitliskā secība.
Dažreiz jūs varētu vēlēties izmantot iteratoru eksplicīti. Arī to var izdarīt, lai gan tas ir daudz neveiklāk nekā for-of
. Tas izskatās šādi:
const a = ["a", "b", "c"];
const it = a.values();
let entry;
while (!(entry = it.next()).done) {
console.log(entry.value);
}
Iterators ir objekts, kas atbilst specifikācijā sniegtajai Iteratora definīcijai. Tā next
metode atgriež jaunu rezultāta objektu katru reizi, kad to izsaucat. Rezultāta objektam ir īpašība done
, kas norāda, vai tas ir pabeigts, un īpašība value
ar attiecīgās iterācijas vērtību. (done
nav obligāts, ja tas būtu false
, value
nav obligāts, ja tas būtu undefined
.)
vērtība
nozīme atšķiras atkarībā no iteratora; masīvi atbalsta (vismaz) trīs funkcijas, kas atdod iteratorus:
vērtība()
: Šo funkciju es izmantoju iepriekš. Tā atgriež iteratoru, kurā katra vērtība
ir attiecīgās iterācijas masīva ieraksts ("a"
, "b"
un "c"
iepriekšējā piemērā).atslēgas()
: Atgriež iteratoru, kurā katra vērtība
ir šīs iterācijas atslēga (tātad mūsu iepriekš minētajam a
tas būtu "0"
, tad "1"
, tad "2"
).entries()
: Atgriež iteratoru, kurā katra vērtība
ir masīvs formā [atslēga, vērtība]
attiecīgajai iterācijai.Papildus īstajiem masīviem ir arī matu masīviem līdzīgi objekti, kuriem ir garuma
īpašība un īpašības ar skaitliskajiem nosaukumiem: NodeList
gadījumi, argumentu
objekts utt. Kā veikt cilpu caur to saturu?
Vismaz dažas un, iespējams, lielākā daļa vai pat visas iepriekš minētās masīvu pieejas bieži vien ir vienlīdz labi piemērojamas arī masīviem līdzīgiem objektiem:
Izmantojiet forEach
un saistītos (ES5+).
Dažādās Array.prototype
funkcijas ir "apzināti vispārīgas" un parasti tās var izmantot masīviem līdzīgiem objektiem, izmantojot Function#call
vai Function#apply
. (Skat. Caveat for host-provided objects šīs atbildes beigās, bet tā ir reta problēma.)
Pieņemsim, ka jūs vēlaties izmantot forEach
uz mezgla
īpašību childNodes
. Jūs to izdarītu šādi:
Array.prototype.forEach.call(node.childNodes, function(child) {
// Dariet kaut ko ar child
});
Ja jūs to darīsiet bieži, iespējams, vēlēsieties ievietot funkcijas atsauces kopiju mainīgajā, lai to varētu izmantot atkārtoti, piem.,:
// (Tas viss, iespējams, ir kādā mēroga noteikšanas funkcijā).
var forEach = Array.prototype.forEach;
// Tad vēlāk...
forEach.call(node.childNodes, function(child) {
// Dariet kaut ko ar child
});
Izmantojiet vienkāršu for
cilpu
Acīmredzot vienkārša for
cilpa attiecas uz masīviem līdzīgiem objektiem.
Izmantojiet for-in
korekti.
for-in
ar tādiem pašiem drošības pasākumiem kā ar masīviem būtu jādarbojas arī ar masīviem līdzīgiem objektiem; var būt piemērojams iepriekš 1. punktā minētais brīdinājums par resursdatora nodrošinātajiem objektiem.
Izmantojiet for-of
(netieši izmantojiet iteratoru) (ES2015+).
for-of
izmantos objekta sniegto iteratoru (ja tāds ir); mums būs jāskatās, kā tas darbojas ar dažādiem masīviem līdzīgiem objektiem, jo īpaši ar saimnieka nodrošinātajiem objektiem. Piemēram, NodeList
specifikācija no querySelectorAll
tika atjaunināta, lai atbalstītu iterāciju. HTMLCollection
no getElementsByTagName
specifikācija netika mainīta.
Iteratoru lietošana nepārprotami (ES2015+) Skat. 4. punktu, mums būs jāskatās, kā iteratori darbosies.
Citos gadījumos jūs, iespējams, vēlēsieties pārveidot masīvam līdzīgu objektu par īstu masīvu. To izdarīt ir pārsteidzoši vienkārši:
Izmantojiet masīvu slice
metodi.
Mēs varam izmantot masīvu slice
metodi, kas, tāpat kā citas iepriekš minētās metodes, ir "apzināti vispārīga" un tāpēc to var izmantot ar masīviem līdzīgiem objektiem, piemēram, šādi:
trueArray = Array.prototype.slice.call(arrayLikeObject);
Tātad, piemēram, ja mēs vēlamies pārvērst NodeList
par true array, mēs varam rīkoties šādi:
var divs = Array.prototype.slice.call(document.querySelectorAll("div"));
Sk. tālāk Caveat for host-provided objects. Īpaši ņemiet vērā, ka tas neizdosies IE8 un agrākajos modeļos, kas neļauj izmantot host-provided objektus kā this
.
Izmantojiet izplatīšanas sintakse (...
)
Ir iespējams izmantot arī ES2015's spread syntax ar JavaScript dzinējiem, kas atbalsta šo funkciju:
trueArray = [...iterableObject];
Piemēram, ja mēs vēlamies pārvērst NodeList
par true array, ar izkliedes sintakses palīdzību tas kļūst pavisam kodolīgi:
Divs = [...document.querySelectorAll("div")];
Izmantojiet Array.from
(spec) | (MDN)
Array.from
(ES2015+, bet viegli aizpildāms) izveido masīvu no masīvam līdzīga objekta, pēc izvēles vispirms nododot ierakstus caur kartēšanas funkciju. Tātad:
var divs = Array.from(document.querySelectorAll("div"));
Vai arī, ja vēlaties iegūt elementu ar noteiktu klasi tagu nosaukumu masīvu, izmantojiet kartēšanas funkciju:
// (ES2015):
Divs = Array.from(document.querySelectorAll(".some-class"), element => element.tagName);
// Standarta funkcija (jo Array.from
var tikt sašaurināts):
(2): var divs = Array.from(document.querySelectorAll(".some-class"), function(element) {
return element.tagName;
});
Ja izmantojat Array.prototype
funkcijas ar host-provided masīviem līdzīgiem objektiem (DOM sarakstiem un citām lietām, ko nodrošina pārlūkprogramma, nevis JavaScript dzinējs), jums jāpārliecinās, ka mērķa vidē ir jāveic testi, lai pārliecinātos, ka host-provided objekts uzvedas pareizi. Vairums no tiem uzvedas pareizi (tagad), taču ir svarīgi pārbaudīt. Iemesls ir tāds, ka lielākā daļa Array.prototype
metožu, kuras jūs, visticamāk, vēlēsieties izmantot, ir atkarīgas no tā, vai objekts, ko nodrošina resursdators, sniedz godīgu atbildi uz abstrakto [[HasProperty]]
operāciju. Līdz šim pārlūkprogrammas to dara ļoti labi, taču 5.1 specifikācijā tika pieļauta iespēja, ka resursdatora nodrošinātais objekts var nebūt godīgs. Tas ir §8.6.2, vairākas rindkopas zem lielās tabulas pie šīs sadaļas sākuma), kur teikts:
Piemēram, viena no iespējām ir tāda, ka [[Get]]
un [[Put]]
konkrētam saimnieka objektam tiešām iegūst un saglabā īpašību vērtības, bet [[HasProperty]]
vienmēr ģenerē false.
(ES2015 specifikācijā es nevarēju atrast līdzvērtīgu formulējumu, bet tā noteikti joprojām ir.) Atkārtoti, no šī raksta rakstīšanas brīža mūsdienu pārlūkprogrammās izplatītie masīviem līdzīgie objekti [piemēram, NodeList
gadījumi] **apstrādā [[HasProperty]]
pareizi, bet to ir svarīgi pārbaudīt.).
Piezīme: Šī atbilde ir bezcerīgi novecojusi. Lai iegūtu mūsdienīgāku pieeju, apskatiet metodes, kas pieejamas masīvam. Interesantas metodes varētu būt šādas:
Standarta veids, kā iterēt masīvu JavaScript, ir vaniļas for
cilpa:
var length = arr.length,
element = null;
for (var i = 0; i < length; i++) {
element = arr[i];
// Do something with element
}
Tomēr ņemiet vērā, ka šī pieeja ir laba tikai tad, ja jums ir blīvs masīvs un katrs indekss ir aizņemts ar elementu. Ja masīvs ir reti blīvs, tad ar šo pieeju var rasties veiktspējas problēmas, jo tiks iterēti daudzi indeksi, kas masīvā reāli neeksistē. Šādā gadījumā for .. in
cilpa varētu būt labāka ideja. Tomēr ir jāizmanto atbilstoši drošības pasākumi, lai nodrošinātu, ka tiek apstrādātas tikai vēlamās masīva īpašības (t. i., masīva elementi), jo for..in
cilpa tiks uzskaitīta arī vecākajās pārlūkprogrammās vai arī tad, ja papildu īpašības ir definētas kā izskaitāmas
.
ECMAScript 5 būs forEach metode masīva prototipam, bet tā netiek atbalstīta vecākajās pārlūkprogrammās. Tāpēc, lai to varētu konsekventi izmantot, ir vai nu jāizmanto vide, kas to atbalsta (piemēram, Node.js servera puses JavaScript), vai arī jāizmanto "Polyfill". Tomēr šīs funkcionalitātes papildinājums ir triviāls, un, tā kā tas atvieglo koda lasāmību, to ir lietderīgi iekļaut.
Ja vēlaties veikt cilpu pār masīvu, izmantojiet standarta trīsdaļīgo for
cilpu.
for (var i = 0; i < myArray.length; i++) {
var arrayItem = myArray[i];
}
Jūs varat optimizēt veiktspēju, kešējot myArray.length
vai iterējot to atpakaļ.