Õppige selgeks JavaScripti privaatsed väljad (#) andmete varjamiseks ja klasside kapseldamiseks. Tutvuge süntaksi, eeliste ja mustritega praktiliste näidete abil.
JavaScripti privaatsed väljad: Süvaülevaade klasside tõelisest kapseldamisest ja andmete varjamisest
Tarkvaraarenduse maailmas on robustsete, hooldatavate ja turvaliste rakenduste loomine esmatähtis. Selle eesmärgi saavutamise nurgakivi, eriti objektorienteeritud programmeerimises (OOP), on kapseldamise põhimõte. Kapseldamine on andmete (omaduste) sidumine meetoditega, mis nende andmetega opereerivad, ja otsese juurdepääsu piiramine objekti sisemisele olekule. Aastaid on JavaScripti arendajad igatsenud keele-tasemel jõustatud viisi tõeliselt privaatsete klassiliikmete loomiseks. Kuigi konventsioonid ja mustrid pakkusid lahendusi, ei olnud need kunagi lollikindlad.
See ajastu on möödas. Privaatsete klassiväljade ametliku lisamisega ECMAScript 2022 spetsifikatsiooni pakub JavaScript nüüd lihtsat ja võimsat süntaksit tõeliseks andmete varjamiseks. See funktsioon, mida tähistatakse trellimärgiga (#), muudab põhjalikult seda, kuidas me saame oma klasse disainida ja struktureerida, viies JavaScripti OOP-võimekuse rohkem vastavusse keeltega nagu Java, C# või Python.
See põhjalik juhend viib teid sügavale JavaScripti privaatsete väljade maailma. Uurime nende vajalikkuse tagamaid, analüüsime privaatsete väljade ja meetodite süntaksit, avastame nende peamised eelised ja käime läbi praktilised, reaalse elu stsenaariumid. Olenemata sellest, kas olete kogenud arendaja või alles alustate JavaScripti klassidega, on selle kaasaegse funktsiooni mõistmine professionaalse taseme koodi kirjutamiseks ülioluline.
Vana viis: Privaatsuse simuleerimine JavaScriptis
Et täielikult mõista # süntaksi tähtsust, on oluline aru saada ajaloost, kuidas JavaScripti arendajad üritasid privaatsust saavutada. Need meetodid olid nutikad, kuid lõppkokkuvõttes ei suutnud pakkuda tõelist, jõustatud kapseldamist.
Allkriipsu konventsioon (_)
Kõige levinum ja pikaajalisem lähenemine oli nimekonventsioon: omaduse või meetodi nime ette allkriipsu lisamine. See oli signaal teistele arendajatele: "See on sisemine omadus. Palun ärge seda otse puudutage."
Vaatleme lihtsat `BankAccount` klassi:
class BankAccount {
constructor(ownerName, initialBalance) {
this.ownerName = ownerName;
this._balance = initialBalance; // Konventsioon: See on 'privaatne'
}
deposit(amount) {
if (amount > 0) {
this._balance += amount;
console.log(`Deposited: ${amount}. New balance: ${this._balance}`);
}
}
// Avalik getter saldo turvaliseks lugemiseks
getBalance() {
return this._balance;
}
}
const myAccount = new BankAccount('John Doe', 1000);
console.log(myAccount.getBalance()); // 1000
// Probleem: Konventsiooni saab ignoreerida
myAccount._balance = -5000; // Otsene manipuleerimine on võimalik!
console.log(myAccount.getBalance()); // -5000 (Vigane olek!)
Põhiline nõrkus on selge: allkriips on vaid soovitus. Puudub keeletasandi mehhanism, mis takistaks välist koodi otse `_balance`'ile juurde pääsemast või seda muutmast, mis võib potentsiaalselt rikkuda objekti olekut ja mööda minna igasugusest valideerimisloogikast meetodites nagu `deposit`.
Sulgurid ja mooduli muster
Robustsem tehnika hõlmas sulgurite kasutamist privaatse oleku loomiseks. Enne `class` süntaksi kasutuselevõttu saavutati see sageli tehasefunktsioonide ja mooduli mustriga.
function createBankAccount(ownerName, initialBalance) {
let balance = initialBalance; // See muutuja on sulguri tõttu privaatne
return {
getOwner: () => ownerName,
getBalance: () => balance, // Paljastab avalikult saldo väärtuse
deposit: function(amount) {
if (amount > 0) {
balance += amount;
console.log(`Deposited: ${amount}. New balance: ${balance}`);
}
},
withdraw: function(amount) {
if (amount > 0 && amount <= balance) {
balance -= amount;
console.log(`Withdrew: ${amount}. New balance: ${balance}`);
} else {
console.log('Insufficient funds or invalid amount.');
}
}
};
}
const myAccount = createBankAccount('Jane Smith', 2000);
console.log(myAccount.getBalance()); // 2000
myAccount.deposit(500); // Deposited: 500. New balance: 2500
// Katse privaatsele muutujale juurde pääseda ebaõnnestub
console.log(myAccount.balance); // undefined
myAccount.balance = 9999; // Loob uue, seostamata omaduse
console.log(myAccount.getBalance()); // 2500 (Sisemine olek on turvaline!)
See muster tagab tõelise privaatsuse. Muutuja `balance` eksisteerib ainult `createBankAccount` funktsiooni skoobis ja on väljastpoolt ligipääsmatu. Siiski on sellel lähenemisel oma puudused: see võib olla sõnaohtram, vähem mäluefektiivne (igal isendil on oma koopia meetoditest) ja ei integreeru nii puhtalt kaasaegse `class` süntaksi ja selle funktsioonidega nagu pärilus.
Tõelise privaatsuse tutvustus: Trellimärgi # süntaks
Privaatsete klassiväljade kasutuselevõtt trellimärgi (#) eesliitega lahendab need probleemid elegantselt. See pakub sulgurite tugevat privaatsust klasside puhta ja tuttava süntaksiga. See ei ole konventsioon; see on range, keelega jõustatud reegel.
Privaatne väli tuleb deklareerida klassi keha ülatasemel. Väljastpoolt klassi privaatsele väljale juurdepääsu katse tulemuseks on kompileerimisajal `SyntaxError` või käivitusajal `TypeError`, mis teeb privaatsuspiiri rikkumise võimatuks.
Põhisüntaks: Privaatsed isendiväljad
Refaktoorime oma `BankAccount` klassi, kasutades privaatset välja.
class BankAccount {
// 1. Deklareeri privaatne väli
#balance;
constructor(ownerName, initialBalance) {
this.ownerName = ownerName; // Avalik väli
// 2. Initsialiseeri privaatne väli
if (initialBalance > 0) {
this.#balance = initialBalance;
} else {
throw new Error('Initial balance must be positive.');
}
}
deposit(amount) {
if (amount > 0) {
this.#balance += amount;
console.log(`Deposited: ${amount}.`);
}
}
withdraw(amount) {
if (amount > 0 && amount <= this.#balance) {
this.#balance -= amount;
console.log(`Withdrew: ${amount}.`);
} else {
console.error('Withdrawal failed: Invalid amount or insufficient funds.');
}
}
getBalance() {
// Avalik meetod pakub kontrollitud juurdepääsu privaatsele väljale
return this.#balance;
}
}
const myAccount = new BankAccount('Alice', 500);
myAccount.deposit(100);
console.log(myAccount.getBalance()); // 600
// Nüüd proovime seda lõhkuda...
try {
// See ebaõnnestub. See ei ole soovitus; see on range reegel.
console.log(myAccount.#balance);
} catch (e) {
console.error(e); // TypeError: Cannot read private member #balance from an object whose class did not declare it
}
// See ei muuda privaatset välja. See loob uue, avaliku omaduse.
myAccount['#balance'] = 9999;
console.log(myAccount.getBalance()); // 600 (Sisemine olek jääb turvaliseks!)
See on murranguline. Väli #balance on tõeliselt privaatne. Sellele pääseb ligi või seda saab muuta ainult `BankAccount` klassi kehas kirjutatud kood. Meie objekti terviklikkus on nüüd kaitstud JavaScripti mootori enda poolt.
Privaatsed meetodid
Sama # sĂĽntaks kehtib ka meetoditele. See on uskumatult kasulik sisemiste abifunktsioonide jaoks, mis on osa klassi implementatsioonist, kuid mida ei tohiks selle avaliku API osana paljastada.
Kujutage ette `ReportGenerator` klassi, mis peab enne lõpliku aruande koostamist tegema keerukaid sisemisi arvutusi.
class ReportGenerator {
#data;
constructor(rawData) {
this.#data = rawData;
}
// Privaatne abimeetod sisemiseks arvutuseks
#calculateTotalSales() {
console.log('Performing complex and secret calculations...');
return this.#data.reduce((total, item) => total + item.price * item.quantity, 0);
}
// Privaatne abimeetod vormindamiseks
#formatCurrency(amount) {
// Reaalses stsenaariumis kasutaks see Intl.NumberFormat'i globaalsele publikule
return `$${amount.toFixed(2)}`;
}
// Avalik API meetod
generateSalesReport() {
const totalSales = this.#calculateTotalSales(); // Kutsub välja privaatse meetodi
const formattedTotal = this.#formatCurrency(totalSales); // Kutsub välja teise privaatse meetodi
return {
reportDate: new Date(),
totalSales: formattedTotal,
itemCount: this.#data.length
};
}
}
const salesData = [
{ price: 10, quantity: 5 },
{ price: 25, quantity: 2 },
{ price: 5, quantity: 20 }
];
const generator = new ReportGenerator(salesData);
const report = generator.generateSalesReport();
console.log(report); // { reportDate: ..., totalSales: '$200.00', itemCount: 3 }
// Katse kutsuda privaatset meetodit väljastpoolt ebaõnnestub
try {
generator.#calculateTotalSales();
} catch (e) {
console.error(e.name, e.message);
}
Tehes #calculateTotalSales ja #formatCurrency privaatseks, saame tulevikus vabalt nende implementatsiooni muuta, neid ümber nimetada või isegi eemaldada, muretsemata `ReportGenerator` klassi kasutava koodi lõhkumise pärast. Avalik leping on määratletud ainult `generateSalesReport` meetodiga.
Privaatsed staatilised väljad ja meetodid
Märksõna `static` saab kombineerida privaatse süntaksiga. Privaatsed staatilised liikmed kuuluvad klassile endale, mitte ühelegi klassi isendile.
See on kasulik teabe salvestamiseks, mida tuleks jagada kõigi isendite vahel, kuid mis peaks jääma avalikust skoobist varjatuks. Klassikaline näide on loendur, mis jälgib, mitu klassi isendit on loodud.
class DatabaseConnection {
// Privaatne staatiline väli isendite loendamiseks
static #instanceCount = 0;
// Privaatne staatiline meetod sisemiste sĂĽndmuste logimiseks
static #log(message) {
console.log(`[DBConnection Internal]: ${message}`);
}
constructor(connectionString) {
this.connectionString = connectionString;
DatabaseConnection.#instanceCount++;
DatabaseConnection.#log(`New connection created. Total: ${DatabaseConnection.#instanceCount}`);
}
connect() {
console.log(`Connecting to ${this.connectionString}...`);
}
// Avalik staatiline meetod loenduri saamiseks
static getInstanceCount() {
return DatabaseConnection.#instanceCount;
}
}
const conn1 = new DatabaseConnection('server1/db');
const conn2 = new DatabaseConnection('server2/db');
console.log(`Total connections created: ${DatabaseConnection.getInstanceCount()}`); // Total connections created: 2
// Väljastpoolt privaatsetele staatilistele liikmetele juurdepääs on võimatu
console.log(DatabaseConnection.#instanceCount); // SyntaxError
DatabaseConnection.#log('Trying to log'); // SyntaxError
Miks kasutada privaatseid välju? Peamised eelised
NĂĽĂĽd, kui oleme sĂĽntaksiga tutvunud, kinnistame oma arusaama, miks see funktsioon on kaasaegse tarkvaraarenduse jaoks nii oluline.
1. Tõeline kapseldamine ja andmete varjamine
See on peamine eelis. Privaatsed väljad jõustavad piiri klassi sisemise implementatsiooni ja selle avaliku liidese vahel. Objekti olekut saab muuta ainult selle avalike meetodite kaudu, tagades, et objekt on alati kehtivas ja järjepidevas olekus. See takistab välisel koodil tegemast suvalisi, kontrollimata muudatusi objekti sisemistes andmetes.
2. Robustsete ja stabiilsete API-de loomine
Kui teete klassi või mooduli teistele kasutamiseks kättesaadavaks, määratlete te lepingu ehk API. Tehes sisemised omadused ja meetodid privaatseks, kommunikeerite selgelt, millised teie klassi osad on tarbijatele turvalised. See annab teile, autorile, vabaduse hiljem sisemist implementatsiooni refaktoorida, optimeerida või täielikult muuta, ilma et see lõhuks kõigi teie klassi kasutajate koodi. Kui kõik oleks avalik, võiks iga muudatus olla rikkuda toimivust.
3. Juhusliku muutmise vältimine ja invariantide jõustamine
Privaatsed väljad koos avalike meetoditega (getterid ja setterid) võimaldavad teil lisada valideerimisloogikat. Objekt saab jõustada oma reegleid ehk 'invariante' – tingimusi, mis peavad alati tõesed olema.
class Circle {
#radius;
constructor(radius) {
this.setRadius(radius);
}
// Avalik setter valideerimisega
setRadius(newRadius) {
if (typeof newRadius !== 'number' || newRadius <= 0) {
throw new Error('Radius must be a positive number.');
}
this.#radius = newRadius;
}
get radius() {
return this.#radius;
}
get area() {
return Math.PI * this.#radius * this.#radius;
}
}
const c = new Circle(10);
console.log(c.area); // ~314.159
c.setRadius(20); // Töötab ootuspäraselt
console.log(c.radius); // 20
try {
c.setRadius(-5); // Ebaõnnestub valideerimise tõttu
} catch (e) {
console.error(e.message); // 'Radius must be a positive number.'
}
// Sisemine #radius ei saa kunagi vigast olekut.
console.log(c.radius); // 20
4. Parem koodi selgus ja hooldatavus
Süntaks # on selgesõnaline. Kui teine arendaja loeb teie klassi, ei ole selle kavandatud kasutuses ebaselgust. Nad teavad kohe, millised osad on sisemiseks kasutamiseks ja millised on osa avalikust API-st. See isedokumenteeriv olemus muudab koodi lihtsamini mõistetavaks, arusaadavamaks ja aja jooksul hooldatavaks.
Praktilised stsenaariumid ja täiustatud mustrid
Uurime, kuidas privaatseid välju saab rakendada keerukamates, reaalsetes stsenaariumides, millega arendajad üle maailma iga päev kokku puutuvad.
Stsenaarium 1: Turvaline `User` klass
Igas rakenduses, mis tegeleb kasutajaandmetega, on turvalisus esmatähtis. Te ei tahaks kunagi, et tundlik teave nagu parooli räsi või isikukood oleks kasutajaobjektil avalikult kättesaadav.
import { hash, compare } from 'some-bcrypt-library'; // Väljamõeldud teek
class User {
#passwordHash;
#personalIdentifier;
#lastLoginTimestamp;
constructor(username, password, pii) {
this.username = username; // Avalik kasutajanimi
this.#passwordHash = hash(password); // Salvesta ainult räsi ja hoia see privaatsena
this.#personalIdentifier = pii;
this.#lastLoginTimestamp = null;
}
async authenticate(passwordAttempt) {
const isMatch = await compare(passwordAttempt, this.#passwordHash);
if (isMatch) {
this.#lastLoginTimestamp = Date.now();
console.log('Authentication successful.');
return true;
}
console.log('Authentication failed.');
return false;
}
// Avalik meetod mittetundliku info saamiseks
getProfileData() {
return {
username: this.username,
lastLogin: this.#lastLoginTimestamp ? new Date(this.#lastLoginTimestamp) : 'Never'
};
}
// Puudub getter passwordHash'ile või personalIdentifier'ile!
}
const user = new User('globaldev', 'superS3cret!', 'ID-12345');
// Tundlikud andmed on väljastpoolt täielikult ligipääsmatud.
console.log(user.username); // 'globaldev'
console.log(user.#passwordHash); // SyntaxError!
Stsenaarium 2: Sisemise oleku haldamine kasutajaliidese komponendis
Kujutage ette, et ehitate korduvkasutatavat kasutajaliidese komponenti, näiteks pildikarusselli. Komponent peab jälgima oma sisemist olekut, näiteks hetkel aktiivse slaidi indeksit. Seda olekut tohiks manipuleerida ainult komponendi avalike meetodite (`next()`, `prev()`, `goToSlide()`) kaudu.
class Carousel {
#slides;
#currentIndex;
#containerElement;
constructor(containerSelector, slidesData) {
this.#containerElement = document.querySelector(containerSelector);
this.#slides = slidesData;
this.#currentIndex = 0;
this.#render();
}
// Privaatne meetod kõigi DOM-i uuenduste käsitlemiseks
#render() {
const currentSlide = this.#slides[this.#currentIndex];
// Loogika DOM-i uuendamiseks, et kuvada praegust slaidi...
console.log(`Rendering slide ${this.#currentIndex + 1}: ${currentSlide.title}`);
}
// Avalikud API meetodid
next() {
this.#currentIndex = (this.#currentIndex + 1) % this.#slides.length;
this.#render();
}
prev() {
this.#currentIndex = (this.#currentIndex - 1 + this.#slides.length) % this.#slides.length;
this.#render();
}
getCurrentSlide() {
return this.#slides[this.#currentIndex];
}
}
const myCarousel = new Carousel('#carousel-widget', [
{ title: 'Tokyo Skyline', image: 'tokyo.jpg' },
{ title: 'Paris at Night', image: 'paris.jpg' },
{ title: 'New York Central Park', image: 'nyc.jpg' }
]);
myCarousel.next(); // Renders slide 2
myCarousel.next(); // Renders slide 3
// Te ei saa komponendi olekut väljastpoolt segi ajada.
// myCarousel.#currentIndex = 10; // SyntaxError! See kaitseb komponendi terviklikkust.
Levinud lõksud ja olulised kaalutlused
Kuigi võimsad, on privaatsete väljadega töötamisel mõned nüansid, millest tuleb teadlik olla.
1. Privaatsed väljad on süntaks, mitte lihtsalt omadused
Oluline erinevus on see, et privaatne väli `this.#field` ei ole sama, mis string-omadus `this['#field']`. Te ei saa privaatsetele väljadele juurde pääseda dünaamilise nurksulgude notatsiooniga. Nende nimed on fikseeritud autoriseerimise ajal.
class MyClass {
#privateField = 42;
getPrivateFieldValue() {
return this.#privateField; // OK
}
getPrivateFieldDynamically(fieldName) {
// return this[fieldName]; // See ei tööta privaatsete väljadega
}
}
const instance = new MyClass();
console.log(instance.getPrivateFieldValue()); // 42
// console.log(instance['#privateField']); // undefined
2. Tavalistel objektidel pole privaatseid välju
See funktsioon on eksklusiivne `class` süntaksile. Te ei saa luua privaatseid välju tavalistel JavaScripti objektidel, mis on loodud objektiliteraalide süntaksiga.
3. Pärilus ja privaatsed väljad
See on nende disaini põhiaspekt: alamklass ei pääse ligi oma vanemklassi privaatsetele väljadele. See jõustab väga tugeva kapseldamise. Alamklass saab vanema sisemise olekuga suhelda ainult vanema avalike või kaitstud meetodite kaudu (JavaScriptis pole `protected` märksõna, kuid seda saab simuleerida konventsioonidega).
class Vehicle {
#fuel;
constructor(initialFuel) {
this.#fuel = initialFuel;
}
drive(kilometers) {
const fuelNeeded = kilometers / 10; // Simple consumption model
if (this.#fuel >= fuelNeeded) {
this.#fuel -= fuelNeeded;
console.log(`Driven ${kilometers} km.`);
return true;
}
console.log('Not enough fuel.');
return false;
}
}
class Car extends Vehicle {
constructor(initialFuel) {
super(initialFuel);
}
checkFuel() {
// See põhjustab vea!
// Auto ei saa otse juurde pääseda Sõiduki #fuel'ile.
// console.log(this.#fuel);
// Selle toimimiseks peaks Sõiduki klass pakkuma avaliku `getFuel()` meetodi.
}
}
const myCar = new Car(50);
myCar.drive(100); // Driven 100 km.
// myCar.checkFuel(); // Would throw a SyntaxError
4. Silumine ja testimine
Tõeline privaatsus tähendab, et te ei saa lihtsalt brauseri arendajakonsoolist või Node.js silurist privaatse välja väärtust kontrollida, sisestades `instance.#field`. Kuigi see on kavandatud käitumine, võib see muuta silumise veidi keerulisemaks. Strateegiad selle leevendamiseks hõlmavad:
- Kasutades peatuspunkte klassi meetodites, kus privaatsed väljad on skoobis.
- Ajutiselt avaliku getteri meetodi lisamine arenduse ajal (nt `_debug_getInternalState()`) kontrollimiseks.
- Põhjalike ühikutestide kirjutamine, mis kontrollivad objekti käitumist selle avaliku API kaudu, väites, et sisemine olek peab olema õige, tuginedes vaadeldavatele tulemustele.
Globaalne perspektiiv: Brauseri- ja keskkonnatugi
Privaatsed klassiväljad on kaasaegne JavaScripti funktsioon, mis on ametlikult standardiseeritud ECMAScript 2022-s. See tähendab, et neid toetavad kõik suuremad kaasaegsed brauserid (Chrome, Firefox, Safari, Edge) ja Node.js-i uuemad versioonid (v14.6.0+ privaatsete meetodite jaoks, v12.0.0+ privaatsete väljade jaoks).
Projektide jaoks, mis peavad toetama vanemaid brausereid või keskkondi, vajate transpilerit nagu Babel. Kasutades `@babel/plugin-proposal-class-properties` ja `@babel/plugin-proposal-private-methods` pluginaid, teisendab Babel kaasaegse # süntaksi vanemaks, ühilduvaks JavaScripti koodiks, mis kasutab privaatsuse simuleerimiseks `WeakMap`e, võimaldades teil seda funktsiooni täna kasutada tagasiühilduvust ohverdamata.
Kontrollige alati ajakohaseid ühilduvustabeleid ressurssidest nagu Can I Use... või MDN Web Docs, et tagada selle vastavus teie projekti tugivajadustele.
Kokkuvõte: Kaasaegse JavaScripti omaksvõtmine parema koodi nimel
JavaScripti privaatsed väljad on enamat kui lihtsalt süntaktiline suhkur; need tähistavad olulist sammu edasi keele arengus, andes arendajatele võimaluse kirjutada turvalisemat, struktureeritumat ja professionaalsemat objektorienteeritud koodi. Pakkudes natiivset mehhanismi tõeliseks kapseldamiseks, kõrvaldab # süntaks vanade konventsioonide ebaselguse ja sulguritel põhinevate mustrite keerukuse.
Peamised järeldused on selged:
- Tõeline privaatsus:
#eesliide loob klassi liikmed, mis on tõeliselt privaatsed ja väljastpoolt klassi ligipääsmatud, jõustatud JavaScripti mootori enda poolt. - Robusted API-d: Kapseldamine võimaldab teil luua stabiilseid avalikke liideseid, säilitades samal ajal paindlikkuse sisemiste implementatsiooni detailide muutmiseks.
- Parem koodi terviklikkus: Kontrollides juurdepääsu objekti olekule, hoiate ära kehtetuid või juhuslikke muudatusi, mis viib vähemate vigadeni.
- Suurem selgus: Süntaks deklareerib selgesõnaliselt teie kavatsuse, muutes klassid teie globaalse meeskonna liikmetele lihtsamini mõistetavaks ja hooldatavaks.
Kui alustate oma järgmist JavaScripti projekti või refaktoorite olemasolevat, tehke teadlik pingutus privaatsete väljade kaasamiseks. See on võimas tööriist teie arendaja tööriistakastis, mis aitab teil ehitada turvalisemaid, hooldatavamaid ja lõppkokkuvõttes edukamaid rakendusi globaalsele publikule.