de Thomas Barrasso

Primul va fi ultimul cu matrice JavaScript

Primul va fi ultimul cu matrice JavaScript
Săruri de mare formând „matrice” naturale

Deci ultima va fi [0], și primul [length — 1].

– Luat din Matei 20:16

Voi sări peste catastrofa malthusiană și voi ajunge la ea: matricele sunt una dintre cele mai simple și mai importante structuri de date. În timp ce elementele terminale (primul și ultimul) sunt accesate frecvent, Javascript nu oferă nici o proprietate sau metodă convenabilă pentru a face acest lucru și utilizarea indicilor poate fi redundantă și predispusă la efecte secundare și erori off-by-one.

Unul mai puțin cunoscut, recentă propunere JavaScript TC39 oferă consolare sub forma a două „noi” proprietăți: Array.lastItem & Array.lastIndex.

Primul va fi ultimul cu matrice JavaScript
Jeff Atwood @codinghorror

Matrice Javascript

În multe limbaje de programare, inclusiv Javascript, matricile sunt indexate zero. Elementele terminale – primul și ultimul – sunt accesate prin [0] și [length — 1] indicii, respectiv. Această plăcere o datorăm unei precedent stabilit de C, unde un indice reprezintă un offset față de capul unui tablou. Asta face ca zero să fie primul index, deoarece acesta este capul matricei. De asemenea, Dijkstra a proclamat „zero ca număr cel mai natural.”Deci, să fie scris. Deci, să se facă.

Bănuiesc că dacă ați mediat accesul după index, ați găsi că elementele terminale sunt referite cel mai des. La urma urmei, matricile sunt utilizate în mod obișnuit pentru a stoca o colecție sortată și așa plasează elemente superlative (cel mai înalt, cel mai mic, cel mai vechi, cel mai nou etc.) la capete.

Spre deosebire de alte limbaje de scriptare (de exemplu PHP sau Elixir), Javascript nu oferă acces convenabil la elementele matricei de terminale. Luați în considerare un exemplu banal de schimbare a ultimelor elemente în două tablouri:

let faces = ["?", "?", "?", "?", "?"];let animals = ["?", "?", "?", "?", "?"];  
let lastAnimal = animals[animals.length - 1];animals[animals.length - 1] = faces[faces.length - 1];faces[faces.length - 1] = lastAnimal;

Logica de schimbare necesită 2 tablouri la care se face referire de 8 ori în 3 rânduri! În codul din lumea reală, acest lucru poate deveni rapid foarte repetitiv și dificil de analizat pentru un om (deși este perfect lizibil pentru o mașină).

Mai mult, folosind numai indici, nu puteți defini un tablou și obține ultimul element în aceeași expresie. S-ar putea să nu pară important, dar luați în considerare un alt exemplu în care funcția, getLogins(), efectuează un apel API asincron și returnează o matrice sortată. Presupunând că dorim cel mai recent eveniment de conectare la sfârșitul matricei:

let lastLogin = async () => {  let logins = await getLogins();  return logins[logins.length - 1];};

Cu excepția cazului în care lungimea este fixă ​​și cunoscută în avans, noi avea pentru a atribui tabloul unei variabile locale pentru a accesa ultimul element. O modalitate obișnuită de a aborda acest lucru în limbi cum ar fi Piton și Rubin este de a folosi indicii negativi. Apoi [length - 1] poate fi scurtat la [-1], eliminând necesitatea referinței locale.

găsesc -1 doar marginal mai lizibil decât length — 1, și în timp ce este posibil să se aproximeze indicii de matrice negativi în Javascript cu Proxy ES6 sau Array.slice(-1)[0], ambele vin cu implicații semnificative asupra performanței pentru ceea ce altfel ar trebui să constituie un acces aleatoriu simplu.

1611978129 672 Primul va fi ultimul cu matrice JavaScript

Underscore & Lodash

Unul dintre cele mai cunoscute principii în dezvoltarea de software este Don’t Repeat Yourself (DRY). Deoarece accesarea elementelor terminale este atât de obișnuită, de ce să nu scriem o funcție de ajutor pentru ao face? Din fericire, multor biblioteci le place Sublinia și Lodash oferă deja utilități pentru _.first & _.last.

Acest lucru oferă o mare îmbunătățire în lastLogin() exemplu de mai sus:

let lastLogin = async () => _.last(await getLogins());

Dar când vine vorba de exemplul schimbării ultimelor elemente, îmbunătățirea este mai puțin semnificativă:

let faces = ["?", "?", "?", "?", "?"];let animals = ["?", "?", "?", "?", "?"];  
let lastAnimal = _.last(animals);animals[animals.length - 1] = _.last(faces);faces[faces.length - 1] = lastAnimal;

Aceste funcții de utilitate au eliminat 2 din cele 8 referințe, abia acum am introdus o dependență externă care, în mod ciudat, nu include o funcție pentru setare elemente terminale.

Cel mai probabil, o astfel de funcție este exclusă în mod deliberat, deoarece API-ul său ar fi confuz și greu de citit. Primele versiuni ale lui Lodash au furnizat o metodă _.last(array, n) Unde n a fost numărul de articole de la final, dar în cele din urmă a fost eliminat în favoarea _.take(array, n).

Presupunând nums este o serie de numere, care ar fi comportamentul scontat _.last(nums, n)? Ar putea întoarce ultimele două elemente, cum ar fi _.take, sau ar putea seta valoarea ultimului element egală cu n.

Dacă ar fi să scriem o funcție pentru setarea ultimului element dintr-o matrice, există doar câteva abordări de luat în considerare utilizarea funcțiilor pure, înlănțuirea metodelor sau utilizarea prototipului:

let nums = ['d', 'e', 'v', 'e', 'l']; // set first = last
_.first(faces, _.last(faces));        // Lodash style
$(faces).first($(faces).last());      // jQuery style
faces.first(faces.last());            // prototype

Nu consider că niciuna dintre aceste abordări este o îmbunătățire. De fapt, aici se pierde ceva important. Fiecare efectuează o atribuire, dar niciunul nu folosește operatorul de atribuire (=Acest lucru ar putea fi mai evident cu convențiile de numire precum getLast și setFirst, dar care devine repede prea exagerat. Ca să nu mai vorbim de al cincilea cerc al iadului este plin de programatori forțați să navigheze pe codul vechi „autodocumentare”, unde singura modalitate de a accesa sau modifica datele este prin intermediul getters-urilor și seterilor.

Cumva, se pare că ne-am blocat [0] & [length — 1]

Sau suntem? ?

Propunerea

După cum sa menționat, o propunere a candidatului tehnic ECMAScript (TC39) încearcă să soluționeze această problemă prin definirea a două proprietăți noi pe Array obiect: lastItem & lastIndex. Această propunere este deja sprijinit în core-js 3 și utilizabil astăzi în Babel 7 și TypeScript. Chiar dacă nu utilizați un transpiler, această propunere include un polifund.

Personal, nu găsesc prea multă valoare în lastIndex și preferă denumirea mai scurtă a lui Ruby pentru first și last, deși acest lucru a fost exclus din cauza potențiale probleme de compatibilitate web. Sunt, de asemenea, surprins că această propunere nu sugerează o firstItem proprietate pentru consistență și simetrie.

Între timp, pot oferi o abordare fără dependență, Ruby-esque în ES6:

Primul Ultimul

Acum avem două noi proprietăți Array –first & last–Și o soluție care:

✓ Folosește operatorul de atribuire

✓ Nu clonează matricea

✓ Poate defini un tablou și obține un element terminal într-o singură expresie

✓ Este lizibil de către om

✓ Oferă o interfață pentru obținerea și setarea

Putem rescrie lastLogin() din nou într-o singură linie:

let lastLogin = async () => (await getLogins()).last;

Dar adevăratul câștig vine atunci când schimbăm ultimele elemente în două tablouri cu jumătate din numărul de referințe:

let faces = ["?", "?", "?", "?", "?"];let animals = ["?", "?", "?", "?", "?"];  
let lastAnimal = animals.last;animals.last = faces.last;faces.last = lastAnimal;

Totul este perfect și am rezolvat una dintre cele mai dificile probleme ale CS. Nu există legături rele ascunse în această abordare …

Prototip Paranoia

Cu siguranță nu este nimeni [programmer] pe pământ atât de neprihănit încât să facă binele fără să păcătuiască vreodată.– Adaptat din Eclesiastul 7:20

Mulți consideră extinderea prototipului unui obiect nativ un anti-model și o infracțiune pedepsită cu 100 de ani de programare în Java. Înainte de introducerea enumerable proprietate, extinzându-se Object.prototype ar putea schimba comportamentul for in bucle. De asemenea, ar putea duce la conflicte între diferite biblioteci, cadre și dependențe terțe.

Poate că cea mai insidioasă problemă este că, fără instrumente de compilare, ar putea fi o simplă greșeală de ortografie creați din greșeală o matrice asociativă.

let faces = ["?", "?", "?", "?", "?"];let ln = faces.length  
faces.lst = "?"; // (5) ["?", "?", "?", "?", "?", lst: "?"]  
faces.lst("?");  // Uncaught TypeError: faces.lst is not a function 
faces[ln] = "?"; // (6) ["?", "?", "?", "?", "?", "?"]  

Această preocupare nu este unică pentru abordarea noastră, se aplică tuturor prototipurilor de obiecte native (inclusiv matrici). Cu toate acestea, aceasta oferă siguranță într-o formă diferită. Matricile din Javascript nu sunt fixe în lungime și, în consecință, nu există IndexOutOfBoundsExceptions. Folosind Array.last se asigură că nu încercăm accidental să accesăm [length] și intră neintenționat undefined teritoriu.

Indiferent de abordarea pe care o luați, există capcane. Încă o dată, software-ul se dovedește a fi un arta de a face compromisuri.

Continuarea cu referința biblică străină, presupunând că nu credem că se extinde Array.prototype este un păcat etern, sau suntem dispuși să luăm o mușcătură din fructul interzis, putem folosi această sintaxă concisă și lizibilă astăzi!

Ultimele cuvinte

Programele trebuie scrise pentru ca oamenii să le citească și numai întâmplător pentru ca mașinile să fie executate. – Harold Abelson

În limbaje de scriptare precum Javascript, prefer codul funcțional, concis și lizibil. Când vine vorba de accesarea elementelor de matrice de terminal, găsesc Array.last proprietatea de a fi cea mai elegantă. Într-o aplicație front-end de producție, aș putea favoriza Lodash pentru a reduce la minimum conflictele și preocupările legate de browser. Dar în serviciile back-end ale nodului, unde controlez mediul, prefer aceste proprietăți personalizate.

Sunt cu siguranță nu primulși nici nu voi fi ultimul, care apreciez valoarea (sau precauția cu privire la implicațiile) proprietăților precum Array.lastItem, care, sperăm, va veni în curând la o versiune ECMAScript din apropiere.

1611978129 266 Primul va fi ultimul cu matrice JavaScript

Urmează-mă pe mine LinkedIn · GitHub · Mediu