Az elsőnek utolsónak kell lennie JavaScript tömbökkel

Tehát az utolsó lesz [0], és az első [hossza - 1]. - Máté 20:16

Kihagyom a malthusi katasztrófát, és eljutok hozzá: a tömbök az egyik legegyszerűbb és legfontosabb adatstruktúra. Míg a terminál elemekhez (az első és az utolsó) gyakran hozzáférnek, a Javascript nem biztosít kényelmes tulajdonságot vagy módszert erre, és az indexek használata felesleges lehet, és hajlamos a mellékhatásokra és az egyesével kapcsolatos hibákra.

Egy kevésbé ismert, nemrégiben kiadott JavaScript TC39 javaslat két „új” tulajdonság formájában nyújt vigaszt: Array.lastItem& Array.lastIndex.

Javascript tömbök

Sok programozási nyelvben, beleértve a Javascript-et is, a tömbök nulla indexelésűek. Az első és az utolsó terminálelemekhez a, [0]illetve az [length — 1]indexeken keresztül lehet hozzáférni . Ezt az örömöt egy C által létrehozott precedensnek köszönhetjük, ahol egy index egy tömb fejétől számított eltolást képviseli. Ez lehetővé teszi a nulla az első index, mert ez a tömb fejét. Dijkstra szintén „a nullát a legtermészetesebb számnak” hirdette. Tehát legyen megírva. Tehát legyen kész.

Azt gyanítom, hogy ha átlagosan indexenként szeretné elérni, akkor a terminálokra hivatkoznak a leggyakrabban. Végül is a tömböket általában egy válogatott gyűjtemény tárolására használják, és így felsőbb szintű elemeket (legmagasabb, legalacsonyabb, legrégebbi, legújabb stb.) Helyeznek el a végén.

Más script nyelvektől (mondjuk PHP vagy Elixir) eltérően a Javascript nem biztosít kényelmes hozzáférést a terminál tömb elemeihez. Vegyünk egy triviális példát az utolsó elemek két tömbben történő felcserélésére:

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

A felcserélési logikához 2 tömbre van szükség, amelyekre 8 sorban 3 sorban hivatkozunk! A valós kódokban ez gyorsan ismétlődhet és nehezen értelmezhető az ember számára (bár egy gép számára tökéletesen olvasható).

Ráadásul kizárólag indexek segítségével nem definiálhat tömböt, és nem kaphatja meg ugyanabban a kifejezésben az utolsó elemet. Lehet, hogy ez nem tűnik fontosnak, de vegyünk egy másik példát, ahol a függvény, getLogins()aszinkron API-hívást indít és rendezett tömböt ad vissza. Feltételezve, hogy a tömb végén szeretnénk a legfrissebb bejelentkezési eseményt:

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

Kivéve, ha a hossza fix és előre ismert, azt kell rendelni a tömböt egy helyi változót hozzáférni az utolsó elem. Ennek egyik olyan módja, hogy ezt olyan nyelveken kezeljük, mint a Python és a Ruby, a negatív indexek használata. Ezután [length - 1]lerövidíthető, így [-1]nincs szükség helyi referenciára.

-1Csak kissé olvashatóbbnak találom, mint length — 1, és bár a Javascript negatív tömbindexeit hozzá lehet közelíteni az ES6 Proxy-hoz, vagy Array.slice(-1)[0]mindkettő jelentős teljesítmény-következményekkel jár arra nézve, ami egyébként egyszerű véletlenszerű hozzáférést jelent.

Aláhúzás és Lodash

A szoftverfejlesztés egyik legismertebb alapelve a Ne ismételje meg magát (DRY). Mivel a terminálelemekhez való hozzáférés olyan gyakori, miért nem írunk hozzá segítő funkciót? Szerencsére sok olyan könyvtár, mint az Aláhúzás és a Lodash, már rendelkezik segédprogramokkal a _.first& számára _.last.

Ez nagy előrelépést kínál a lastLogin()fenti példában:

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

De ami az utolsó elemek cseréjét illeti, a fejlődés kevésbé jelentős:

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

Ezek a segédfunkciók eltávolították a 8 referencia közül kettőt, csak most vezettünk be egy külső függőséget, amely furcsa módon nem tartalmaz funkciót a terminál elemek beállításához .

Valószínűleg egy ilyen funkciót szándékosan kizárnak, mert az API zavaros és nehezen olvasható. A Lodash korai verziói olyan módszert szolgáltattak, _.last(array, n)ahol n az elemek száma a végétől, de végül a javára került _.take(array, n).

Ha feltételezzük, hogy numsegy tömb szám, akkor mi lenne a várható viselkedés _.last(nums, n)? Visszaadhatja az utolsó két elemet, mint például _.take, vagy beállíthatja az utolsó elem értékét n-nek .

Ha egy tömb utolsó elemének beállításához írnánk függvényt, csak néhány megközelítést kell figyelembe venni a tiszta függvények, a módszerláncolás vagy a prototípus használatával:

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

Ezen megközelítések közül egyiket sem tartom nagy javulásnak. Valójában itt elveszik valami fontos. Mindegyik elvégez egy hozzárendelést, de egyik sem használja a hozzárendelés operátort ( =). Ez nyilvánvalóbbá válhat az olyan elnevezési konvenciókkal, mint a getLastés setFirst, de ez gyorsan túlságosan bőbeszédűvé válik. Nem is beszélve a pokol ötödik köréről, amely tele van olyan programozókkal, akik kénytelenek navigálni az „öndokumentáló” örökölt kódban, ahol az adatok elérésére vagy módosítására csak a getterek és a beállítók szolgálnak.

Valahogy úgy tűnik, hogy ragaszkodunk a [0]& [length — 1]

Vagy mi vagyunk? ?

A javaslatot

Mint említettük, az ECMAScript műszaki jelölt (TC39) javaslata megkísérli megoldani ezt a problémát két új tulajdonság definiálásával az Arrayobjektumon: lastItem& lastIndex. Ezt a javaslatot a core-js 3 már támogatja, és ma már használható a Babel 7 és TypeScript fájlokban. Még akkor is, ha nem használ transzpilert, ez a javaslat tartalmaz egy polifillet.

Személy szerint nem találok sok értéket a lastIndexRuby rövidebb elnevezésének , és jobban szeretem , firstés lastbár ezt kizárták a lehetséges web-kompatibilitási problémák miatt. Az is meglep, hogy ez a javaslat nem javasolja firstItema következetesség és a szimmetria tulajdonságait.

Időközben függetlenség nélküli, Ruby-jellegű megközelítést kínálok az ES6-ban:

Első Utolsó

Most két új Array tulajdonságunk van first- lastés egy megoldás, amely:

✓ A hozzárendelés operátort használja

✓ Nem klónozza a tömböt

✓ Meg tud definiálni egy tömböt, és kap egy terminál elemet egy kifejezésbe

✓ Emberileg olvasható

✓ Egy felületet biztosít a megszerzéshez és beállításhoz

Újra átírhatjuk lastLogin()egyetlen sorba:

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

De az igazi győzelem akkor következik be, amikor az utolsó elemeket két tömbben cseréljük felére, a hivatkozások számának felével:

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

Minden tökéletes, és megoldottuk a CS egyik legnehezebb problémáját. Ebben a megközelítésben nem rejtőznek gonosz szövetségek ...

Paranoia prototípus

Biztosan nincs a földön senki [programozó], aki olyan igazat tenne, hogy valaha vétkezés nélkül jót tenne. - Adagolt 7:20

Sokan úgy gondolják, hogy a natív Object prototípusának kibővítése antiminta és bűncselekmény, amelyet 100 év Java-programozással büntetnek. A enumerabletulajdonság bevezetése előtt a kiterjesztés Object.prototypemegváltoztathatja a for inhurkok viselkedését . Konfliktushoz vezethet a különféle könyvtárak, keretrendszerek és harmadik féltől származó függőségek között is.

Talán a leg alattomosabb kérdés, hogy fordítási idejű eszközök nélkül egy egyszerű helyesírási hiba akaratlanul asszociatív tömböt hozhat létre.

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

Ez az aggodalom nem csak a mi megközelítésünkre vonatkozik, hanem minden natív Object prototípusra (beleértve a tömböket is). Ez mégis más formában kínálja a biztonságot. A Javascript tömbök hossza nem rögzített, következésképpen nincsenek is IndexOutOfBoundsExceptions. A használat Array.lastbiztosítja, hogy véletlenül ne próbáljunk meg belépni [length]és akaratlanul belépni a undefinedterületre.

Nem számít, melyik megközelítést választja, vannak buktatók. A szoftver ismét a kompromisszumok művészetének bizonyul.

Folytatva az idegen bibliai utalást, feltételezve, hogy nem hisszük, hogy Array.prototypea kiterjesztés örök bűn, vagy hajlandóak vagyunk beleharapni a tiltott gyümölcsbe, használhatjuk ezt a tömör és olvasható szintaxist ma is!

Utolsó szavak

Programokat kell írni az emberek számára az olvasáshoz, és csak mellékesen a gépek végrehajtásához. - Harold Abelson

Az olyan szkriptnyelvekben, mint a Javascript, a funkcionális, tömör és olvasható kódot részesítem előnyben. A terminál tömb elemeinek elérése kapcsán az Array.lastingatlant találom a legelegánsabbnak. Egy éles front-end alkalmazásban előnyben részesíthetem a Lodash-ot, hogy minimalizálhassam a konfliktusokat és a böngészőkön keresztüli aggályokat. De a Node háttérszolgáltatásokban, ahol én irányítom a környezetet, ezeket az egyéni tulajdonságokat részesítem előnyben.

Természetesen nem én vagyok az első, és nem is az utolsó, aki értékelem az olyan tulajdonságok értékét (vagy óvatosságot a következményekkel kapcsolatban) Array.lastItem, amely remélhetőleg hamarosan megjelenik az Ön közelében lévő ECMAScript verzióval.

Kövessen a LinkedIn · GitHub · Közepes oldalon