Viimased aastad on näidanud, et veebiarendus on muutumas. Kuna veebilehitsejatele lisati palju funktsioone ja APIsid, pidime neid õigesti kasutama. Keel, millele me selle au sees võlgneme, oli JavaScript.
Esialgu ei olnud arendajad veendunud, kuidas see oli kavandatud, ja neil olid selle skripti kasutamisel enamasti negatiivsed muljed. Aja jooksul selgus, et selles keeles on suur potentsiaal ja hilisemad ECMAScript standardid muudavad mõned mehaanikad inimlikumaks ja lihtsalt paremaks. Selles artiklis vaatleme mõningaid neist.
Väärtuste tüübid JS-is
Tuntud tõde selle kohta, et JavaScript on see, et kõik siin on objekt. Tõesti, kõik: massiivid, funktsioonid, stringid, numbrid ja isegi boole'id. Kõik väärtuste tüübid on esitatud objektidena ja neil on oma meetodid ja väljad. Kuid me võime need jagada kahte kategooriasse: primitiivid ja strukturaalsed objektid. Esimese kategooria väärtused on muutumatud, mis tähendab, et me saame mõnda muutujat uue väärtusega ümber määrata, kuid olemasolevat väärtust ennast muuta ei saa. Teine esindab väärtusi, mida saab muuta, seega tuleks neid tõlgendada kui omaduste kogumikke, mida me saame asendada või lihtsalt kutsuda meetodeid, mis on selleks mõeldud.
Deklareeritud muutujate ulatus
Enne kui läheme sügavamale, selgitame, mida tähendab ulatus. Võime öelda, et scope on ainus ala, kus me saame kasutada deklareeritud muutujaid. Enne ES6 standardit võisime muutujaid deklareerida avaldusega var ja anda neile globaalse või lokaalse ulatuse. Esimene neist on valdkond, mis võimaldab meil pääseda ligi mõnele muutujale ükskõik millises kohas rakenduses, teine on vaid konkreetsele piirkonnale - peamiselt funktsioonile - pühendatud.
Kuna standard ES2015, JavaScript on kolm viisi muutujate deklareerimiseks, mis erineb võtmesõnaga. Esimest on juba kirjeldatud: var võtmesõnaga deklareeritud muutujad on piiratud praeguse funktsiooni kehaga. ES6 standard võimaldas meil deklareerida muutujaid inimlikumal viisil - vastupidiselt var avaldustele on const ja let avaldustega deklareeritud muutujad skopeeritud ainult plokile. Kuid JS kohtleb const avaldist üsna ebatavaliselt, kui võrrelda teiste programmeerimiskeeled - püsiva väärtuse asemel säilitab see püsiva viite väärtusele. Lühidalt, me võime muuta const-avaldusega deklareeritud objekti omadusi, kuid me ei saa selle muutuja viidet üle kirjutada. Mõned ütlevad, et var alternatiiv ES6-s on tegelikult let avaldis. Ei, see ei ole, ja var-avaldist ei ole ja seda ilmselt kunagi tagasi ei võeta. Hea tava on vältida var-avalduste kasutamist, sest enamasti tekitavad need meile rohkem probleeme. Seevastu const-avaldusi peame kuritarvitama, kuni me ei pea selle viidet muutma - siis peaksime kasutama let-avaldusi.
Näide ootamatu ulatuse käitumise kohta
Alustame järgmisest kood:
(() => {
for (var i = 0; i {
console.log(`Väärtus "i": ${i}`);
}, 1000);
}
})();
Kui me vaatame seda, siis tundub, et for loop iteratsiooni i väärtust ja ühe sekundi pärast logib see väärtused iterator: 1, 2, 3, 4, 5. Noh, see ei tee seda. Nagu me eespool mainisime, on var avaldise puhul tegemist muutuja väärtuse säilitamisega kogu funktsiooni keha jooksul; see tähendab, et teises, kolmandas jne. iteratsioonis asendatakse muutuja i väärtus järgmise väärtusega. Lõpuks lõpeb tsükkel ja timeout-tikid näitavad meile järgmist: 5, 5, 5, 5, 5, 5. Parim viis iteraatori jooksva väärtuse hoidmiseks on kasutada selle asemel let avaldist:
(() => {
for (let i = 0; i {
console.log(`Väärtus "i": ${i}`);
}, 1000);
}
})();
Ülaltoodud näites hoiame i väärtuse ulatust praeguses iteratsiooniblokis, see on ainus valdkond, kus me saame seda muutujat kasutada ja miski ei saa seda väljaspool seda valdkonda ületada. Tulemus on sel juhul ootuspärane: 1 2 3 3 4 5. Vaatame, kuidas seda olukorda var-avaldusega käsitleda:
(() => {
for (var i = 0; i {
setTimeout(() => {
console.log(`Väärtus "j": ${j}`);
}, 1000);
})(i);
}
})();
Kuna var-avalduses on tegemist väärtuse hoidmisega funktsioonibloki sees, peame kutsuma defineeritud funktsiooni, mis võtab argumendi - iteraatori praeguse oleku väärtuse - ja siis lihtsalt midagi tegema. Mitte midagi väljaspool deklareeritud funktsiooni ei saa j väärtust üle sõita.
Näiteid objektide väärtuste valede ootuste kohta
Kõige sagedasem kuritegu, mida ma märkasin, puudutab struktuuride võimsuse eiramist ja nende omaduste muutmist, mida muudetakse ka teistes kooditükkides. Võtke kiire pilk:
const DEFAULT_VALUE = {
favoriteBand: 'The Weeknd'
};
const currentValue = DEFAULT_VALUE;
const bandInput = document.querySelector('#favorite-band');
const restoreDefaultButton = document.querySelector('#restore-button');
bandInput.addEventListener('input', () => { {
currentValue.favoriteBand = bandInput.value;
}, false);
restoreDefaultButton.addEventListener('click', () => {
currentValue = DEFAULT_VALUE;
}, false);
Alustuseks: oletame, et meil on mudel vaikimisi omadustega, mis on salvestatud objektina. Tahame, et meil oleks nupp, mis taastab selle sisendväärtused vaikimisi. Pärast sisendi täitmist mõnede väärtustega uuendame mudelit. Hetke pärast arvame, et vaikimisi valik oli lihtsalt parem, seega tahame selle taastada. Vajutame nupule... ja midagi ei juhtu. Miks? Sellepärast, et eiratakse viidatud väärtuste võimsust.
See osa: const currentValue = DEFAULTVALUE ütleb JS-ile järgmist: võtke viide DEFAULT-ileVALUE väärtus ja määrake muutuja currentValue sellega. Tegelik väärtus salvestatakse mällu ainult üks kord ja mõlemad muutujad osutavad sellele. Mõne omaduse muutmine ühes kohas tähendab nende muutmist teises kohas. Meil on paar võimalust, kuidas selliseid olukordi vältida. Üks, mis täidab meie vajadusi, on levikuoperaator. Parandame oma koodi:
const DEFAULT_VALUE = {
favoriteBand: 'The Weeknd'
};
const currentValue = { ...DEFAULT_VALUE };
const bandInput = document.querySelector('#favorite-band');
const restoreDefaultButton = document.querySelector('#restore-button');
bandInput.addEventListener('input', () => { {
currentValue.favoriteBand = bandInput.value;
}, false);
restoreDefaultButton.addEventListener('click', () => {
currentValue = { ...DEFAULT_VALUE };
}, false);
Sellisel juhul töötab levikuoperaator nii: ta võtab objektilt kõik omadused ja loob uue objekti, mis on nendega täidetud. Tänu sellele ei osuta currentValue'i ja DEFAULT_VALUE'i väärtused enam samale kohale mälus ja kõik muudatused, mida kohaldatakse ühele neist, ei mõjuta teisi.
Ok, küsimus on siis selles, kas kõik on seotud maagilise levikuoperaatori kasutamisega? Antud juhul - jah, kuid meie mudelid võivad vajada rohkem keerukust kui see näide. Juhul, kui me kasutame nested objekte, massiive või muid struktuure, mõjutab ülemise tasandi viidatud väärtuse levikuoperaator ainult ülemist tasandit ja viidatud omadused jagavad endiselt sama kohta mälus. Selle probleemiga tegelemiseks on palju lahendusi, kõik sõltub teie vajadustest. Me võime kloonida objekte igal sügavustasemel või keerulisemate operatsioonide puhul kasutada selliseid vahendeid nagu immer, mis võimaldab kirjutada muutumatut koodi peaaegu valutult.
Sega see kõik kokku
Kas teadmiste kombinatsioon ulatusest ja väärtuste tüüpidest on kasutatav? Loomulikult on! Ehitame midagi, mis kasutab mõlemat:
const useValue = (defaultValue) => {
const value = [...defaultValue];
const setValue = (newValue) => {
value.length = 0; // keeruline viis massiivi tühjendamiseks.
newValue.forEach((item, index) => {
value[index] = item;
});
// tee veel mõned asjad
};
return [value, setValue];
};
const [animals, setAnimals] = useValue(['cat', 'dog']);
console.log(animals); // ['cat', 'dog']
setAnimals(['hobune', 'lehm']);
console.log(animals); // ['horse', 'cow']);
Selgitame, kuidas see kood rida-realt töötab. Noh, funktsioon useValue loob massiivi, mis põhineb defaultValue argumendil; see loob muutuja ja teise funktsiooni, selle modifikaatori. See modificator võtab uue väärtuse, mida keerulisel viisil rakendatakse olemasolevale väärtusele. Funktsiooni lõpus tagastame väärtuse ja selle modifikaatori massiivi väärtustena. Järgnevalt kasutame loodud funktsiooni - deklareerime loomad ja setAnimals tagastatud väärtustena. Kasutame nende modifikaatorit, et kontrollida, kas funktsioon mõjutab loomade muutujat - jah, see toimib!
Aga oota, mis täpselt on selles koodis nii vinget? Viide säilitab kõik uued väärtused ja te saate sellesse modifikaatorisse sisestada oma loogika, näiteks mõned APId või osa ökosüsteemist mis paneb teie andmevoo ilma vaevata tööle. Seda keerulist mustrit kasutatakse sageli moodsamates JS-raamatukogudes, kus funktsionaalne paradigma programmeerimises võimaldab meil hoida koodi vähem keerulisena ja teiste programmeerijate jaoks lihtsamini loetavana.
Kokkuvõte
Arusaam sellest, kuidas keele mehaanika töötab kapoti all, võimaldab meil kirjutada teadlikumat ja kergemat koodi. Isegi kui JS ei ole madala taseme keel ja sunnib meid omama mõningaid teadmisi sellest, kuidas mälu määratakse ja salvestatakse, peame objektide muutmisel ikkagi silma peal hoidma ootamatute käitumiste suhtes. Teisest küljest ei ole väärtuste kloonide kuritarvitamine alati õige tee ja ebaõigel kasutamisel on rohkem miinuseid kui plusse. Õige viis andmevoo planeerimisel on kaaluda, mida on vaja ja milliseid võimalikke takistusi võib rakenduse loogika rakendamisel ette tulla.
Loe edasi: