Gilus žvilgsnis į JavaScript prototipų grandinę, nagrinėjant jos pagrindinį vaidmenį kuriant objektus ir paveldėjimo modelius pasaulinei auditorijai.
Atskleidžiame JavaScript prototipų grandinę: paveldėjimo modeliai ir objektų kūrimas
JavaScript, iš esmės, yra dinamiška ir universali kalba, kuri dešimtmečius palaikė žiniatinklį. Nors daugelis kūrėjų yra susipažinę su jos funkciniais aspektais ir modernia sintakse, įvesta ECMAScript 6 (ES6) ir vėlesnėse versijose, suprasti jos pagrindinius mechanizmus yra būtina norint tikrai įvaldyti kalbą. Vienas iš fundamentaliausių, bet dažnai nesuprastų konceptų yra prototipų grandinė. Šis įrašas demistifikuos prototipų grandinę, nagrinėjant, kaip ji palengvina objektų kūrimą ir leidžia įgyvendinti įvairius paveldėjimo modelius, pateikiant pasaulinę perspektyvą kūrėjams visame pasaulyje.
Pagrindas: objektai ir savybės JavaScript
Prieš gilindamiesi į prototipų grandinę, leiskite mums suformuoti pagrindinį supratimą apie tai, kaip veikia objektai JavaScript. JavaScript, beveik viskas yra objektas. Objektai yra raktų ir reikšmių porų rinkiniai, kur raktai yra savybių pavadinimai (paprastai eilutės arba simboliai), o reikšmės gali būti bet kokio duomenų tipo, įskaitant kitus objektus, funkcijas ar primityvias reikšmes.
Apsvarstykite paprastą objektą:
const person = {
name: "Alice",
age: 30,
greet: function() {
console.log(`Hello, my name is ${this.name}.`);
}
};
console.log(person.name); // Output: Alice
person.greet(); // Output: Hello, my name is Alice.
Kai pasiekiate objekto savybę, pvz. person.name, JavaScript pirmiausia ieško tos savybės tiesiogiai pačiame objekte. Jei ji neranda, ji nesustoja. Štai čia į žaidimą įeina prototipų grandinė.
Kas yra prototipas?
Kiekvienas JavaScript objektas turi vidinę savybę, dažnai vadinamą [[Prototype]], kuri nurodo į kitą objektą. Šis kitas objektas vadinamas originalaus objekto prototipu. Kai bandote pasiekti savybę objekte, o ši savybė nerandama tiesiogiai objekte, JavaScript ieško jos objekto prototipe. Jei ten nerandama, ji ieško prototipo prototipe ir taip toliau, sudarydama grandinę.
Ši grandinė tęsiasi, kol JavaScript arba randa savybę, arba pasiekia grandinės pabaigą, kuri paprastai yra Object.prototype, kurios [[Prototype]] yra null. Šis mechanizmas žinomas kaip prototipinis paveldėjimas.
Prototipo pasiekimas
Nors [[Prototype]] yra vidinis skyrius, yra du pagrindiniai būdai sąveikauti su objekto prototipu:
Object.getPrototypeOf(obj): Tai yra standartinis ir rekomenduojamas būdas gauti objekto prototipą.obj.__proto__: Tai yra atmestas, bet plačiai palaikomas nestandartinis atributas, kuris taip pat grąžina prototipą. Paprastai patariama naudotiObject.getPrototypeOf()geresniam suderinamumui ir standartų laikymuisi.
const person = {
name: "Alice"
};
const personPrototype = Object.getPrototypeOf(person);
console.log(personPrototype === Object.prototype); // Output: true
// Naudojant atmestą __proto__
console.log(person.__proto__ === Object.prototype); // Output: true
Prototipų grandinė veikiant
Prototipų grandinė iš esmės yra susietas objektų sąrašas. Kai bandote pasiekti savybę (gauti, nustatyti ar ištrinti), JavaScript naršo šią grandinę:
- JavaScript patikrina, ar savybė egzistuoja tiesiogiai pačiame objekte.
- Jei nerasta, ji patikrina objekto prototipą (
obj.[[Prototype]]). - Jei vis tiek nerasta, ji patikrina prototipo prototipą ir taip toliau.
- Tai tęsiasi, kol savybė bus rasta arba grandinė baigsis objekte, kurio prototipas yra
null(paprastaiObject.prototype).
Iliustruokime su pavyzdžiu. Įsivaizduokite, kad turime pagrindinę `Animal` konstruktoriaus funkciją, o tada `Dog` konstruktoriaus funkciją, kuri paveldi iš `Animal`.
// Konstruktoriaus funkcija gyvūnui
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function() {
console.log(`${this.name} makes a sound.`);
};
// Konstruktoriaus funkcija šuniui
function Dog(name, breed) {
Animal.call(this, name); // Kviečiame tėvų konstruktorių
this.breed = breed;
}
// Prototipų grandinės nustatymas: Dog.prototype paveldi iš Animal.prototype
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog; // Koreguojame konstruktoriaus savybę
Dog.prototype.bark = function() {
console.log(`Woof! My name is ${this.name} and I'm a ${this.breed}.`);
};
const myDog = new Dog("Buddy", "Golden Retriever");
console.log(myDog.name); // Output: Buddy (rasta myDog objekte)
myDog.speak(); // Output: Buddy makes a sound. (rasta Dog.prototype per Animal.prototype)
myDog.bark(); // Output: Woof! My name is Buddy and I'm a Golden Retriever. (rasta Dog.prototype)
console.log(Object.getPrototypeOf(myDog) === Dog.prototype); // Output: true
console.log(Object.getPrototypeOf(Dog.prototype) === Animal.prototype); // Output: true
console.log(Object.getPrototypeOf(Animal.prototype) === Object.prototype); // Output: true
console.log(Object.getPrototypeOf(Object.prototype) === null); // Output: true
Šiame pavyzdyje:
myDogturi tiesiogines savybesnameirbreed.- Kai kviečiama
myDog.speak(), JavaScript ieškospeakmyDogobjekte. Ji nerandama. - Tada ji žiūri į
Object.getPrototypeOf(myDog), kuris yraDog.prototype.speakčia nerandama. - Tada ji žiūri į
Object.getPrototypeOf(Dog.prototype), kuris yraAnimal.prototype. Čiaspeakrandama! Funkcija yra vykdoma, othisvidujespeaknurodo įmyDog.
Objektų kūrimo modeliai
Prototipų grandinė yra neatsiejamai susijusi su tuo, kaip objektai kuriami JavaScript. Istoriškai, prieš ES6 klases, buvo naudojami keli modeliai objektų kūrimui ir paveldėjimui pasiekti:
1. Konstruktorių funkcijos
Kaip matyti aukščiau pateiktuose Animal ir Dog pavyzdžiuose, konstruktorių funkcijos yra tradicinis būdas kurti objektus. Kai naudojate new raktinį žodį su funkcija, JavaScript atlieka kelis veiksmus:
- Sukuriamas naujas tuščias objektas.
- Šis naujas objektas yra susiejamas su konstruktoriaus funkcijos
prototypesavybe (t. y.,newObj.[[Prototype]] = Constructor.prototype). - Konstruktoriaus funkcija yra kviečiama su nauju objektu, susietu su
this. - Jei konstruktoriaus funkcija aiškiai negrąžina objekto, naujai sukurtas objektas (
this) yra neaiškiai grąžinamas.
Šis modelis yra galingas kuriant kelis objektų egzempliorius su bendromis metodais, apibrėžtais konstruktoriaus prototipe.
2. Gamyklos funkcijos
Gamyklos funkcijos yra tiesiog funkcijos, kurios grąžina objektą. Jos nenaudoja new raktažodžio ir automatiškai nesusieja su prototipu taip, kaip konstruktorių funkcijos. Tačiau jos vis tiek gali pasinaudoti prototipais, aiškiai nustatydamos grąžinamo objekto prototipą.
function createPerson(name, age) {
const person = Object.create(personFactory.prototype);
person.name = name;
person.age = age;
return person;
}
personFactory.prototype.greet = function() {
console.log(`Hello, I'm ${this.name}`);
};
const john = createPerson("John", 25);
john.greet(); // Output: Hello, I'm John
Object.create() yra esminis metodas čia. Jis sukuria naują objektą, naudodamas esamą objektą kaip naujai sukurto objekto prototipą. Tai leidžia aiškiai kontroliuoti prototipų grandinę.
3. Object.create()
Kaip paminėta aukščiau, Object.create(proto, [propertiesObject]) yra esminė priemonė kuriant objektus su nurodytu prototipu. Ji leidžia visiškai apeiti konstruktorių funkcijas ir tiesiogiai nustatyti objekto prototipą.
const personPrototype = {
greet: function() {
console.log(`Hello, my name is ${this.name}`);
}
};
// Sukurti naują objektą 'bob' su 'personPrototype' kaip jo prototipu
const bob = Object.create(personPrototype);
bob.name = "Bob";
bob.greet(); // Output: Hello, my name is Bob
// Galima netgi perduoti savybes kaip antrą argumentą
const charles = Object.create(personPrototype, {
name: { value: "Charles", writable: true, enumerable: true, configurable: true }
});
charles.greet(); // Output: Hello, my name is Charles
Šis metodas yra itin galingas kuriant objektus su iš anksto apibrėžtais prototipais, leidžiantis lanksčias paveldėjimo struktūras.
ES6 klasės: sintaksinis cukrus
Su ES6 atsiradimu, JavaScript pristatė class sintaksę. Svarbu suprasti, kad JavaScript klasės iš esmės yra sintaksinis cukrus virš esamo prototipinio paveldėjimo mechanizmo. Jos suteikia švaresnę, labiau pažįstamą sintaksę kūrėjams, atėjusiems iš klasių pagrįstų objektinių kalbų.
// Naudojant ES6 klasės sintaksę
class AnimalES6 {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a sound.`);
}
}
class DogES6 extends AnimalES6 {
constructor(name, breed) {
super(name); // Kviečia tėvų klasės konstruktorių
this.breed = breed;
}
bark() {
console.log(`Woof! My name is ${this.name} and I'm a ${this.breed}.`);
}
}
const myDogES6 = new DogES6("Rex", "German Shepherd");
myDogES6.speak(); // Output: Rex makes a sound.
myDogES6.bark(); // Output: Woof! My name is Rex and I'm a German Shepherd.
// Po dangčiu, tai vis tiek naudoja prototipus:
console.log(Object.getPrototypeOf(myDogES6) === DogES6.prototype); // Output: true
console.log(Object.getPrototypeOf(DogES6.prototype) === AnimalES6.prototype); // Output: true
Kai apibrėžiate klasę, JavaScript iš esmės sukuria konstruktorių funkciją ir automatiškai nustato prototipų grandinę:
constructormetodas apibrėžia objekto egzemplioriaus savybes.- Metodai, apibrėžti klasės kūne (pvz.,
speakirbark), automatiškai dedami į su ta klase susietos konstruktoriaus funkcijosprototypesavybę. extendsraktinis žodis nustato paveldėjimo ryšį, susiejant vaiko klasės prototipą su tėvų klasės prototipu.
Kodėl prototipų grandinė svarbi pasauliniu mastu
Prototipų grandinės supratimas yra ne tik akademinis pratimas; jis turi didelės įtakos kuriant patikimas, efektyvias ir prižiūrimas JavaScript programas, ypač pasauliniu mastu:
- Našumo optimizavimas: Apibrėžiant metodus prototipe, o ne kiekviename atskirame objekto egzemplioriuje, taupoma atmintis. Visi egzemplioriai dalijasi tomis pačiomis metodų funkcijomis, todėl efektyviau naudojama atmintis, o tai yra kritiškai svarbu programoms, diegiamoms plačiame įrenginių ir tinklo sąlygų spektre visame pasaulyje.
- Kodų pakartotinis naudojimas: Prototipų grandinė yra pagrindinis JavaScript mechanizmas kodui pakartotinai naudoti. Paveldėjimas leidžia kurti sudėtingas objektų hierarchijas, plečiant funkcionalumą be kodo dubliavimo. Tai neįkainojama didelėms, paskirstytoms komandoms, dirbančioms tarptautiniuose projektuose.
- Gilus derinimasis: Kai įvyksta klaidos, prototipų grandinės sekimas gali padėti nustatyti netikėto elgesio šaltinį. Supratimas, kaip ieškoma savybių, yra raktas sprendžiant problemas, susijusias su paveldėjimu, aprėptimi ir
thissusiejimu. - Sprendimų sistemos ir bibliotekos: Daugelis populiarių JavaScript sprendimų sistemų ir bibliotekų (pvz., senesnės React, Angular, Vue.js versijos) labai priklauso nuo prototipų grandinės arba su ja sąveikauja. Tvirtas prototipų supratimas padeda suprasti jų vidinius veiksmus ir efektyviau juos naudoti.
- Kalbos suderinamumas: JavaScript lankstumas su prototipais leidžia lengviau integruotis su kitomis sistemomis ar kalbomis, ypač aplinkose, tokiose kaip Node.js, kur JavaScript sąveikauja su gimtosiomis modulėmis.
- Konceptualus aiškumas: Nors ES6 klasės abstrahuoja kai kurias sudėtingumo dalis, pagrindinis prototipų supratimas leidžia suprasti, kas vyksta po dangčiu. Tai gilina jūsų supratimą ir leidžia labiau pasitikėti kraštutiniais atvejais ir pažangiais scenarijais, nepriklausomai nuo jūsų geografinės padėties ar pasirinktos kūrimo aplinkos.
Dažnos klaidos ir geriausios praktikos
Nors ir galinga, prototipų grandinė gali sukelti sumaištį, jei nebus elgiamasi atsargiai. Štai keletas dažnų klaidų ir geriausių praktikų:
Klaida 1: Įtaisytųjų prototipų modifikavimas
Apskritai bloga idėja pridėti ar modifikuoti metodus įtaisytųjų objektų prototipuose, tokiuose kaip Array.prototype ar Object.prototype. Tai gali sukelti pavadinimų konfliktus ir nenuspėjamą elgesį, ypač dideliuose projektuose arba naudojant trečiųjų šalių bibliotekas, kurios gali remtis originaliu šių prototipų elgesiu.
Geriausia praktika: Naudokite savo konstruktorių funkcijas, gamyklos funkcijas ar ES6 klases. Jei reikia plėsti funkcionalumą, apsvarstykite galimybę kurti pagalbos funkcijas arba naudoti modulius.
Klaida 2: Netinkamas konstruktoriaus atributas
Rankiniu būdu nustatant paveldėjimą (pvz., Dog.prototype = Object.create(Animal.prototype)), naujo prototipo (Dog.prototype) constructor atributas rodys į originalų konstruktorių (Animal). Tai gali sukelti problemų su instanceof patikrinimais ir introspekcija.
Geriausia praktika: Visada aiškiai atkurkite constructor atributą po paveldimo nustatymo:
Dog.prototype = Object.create(Animal.prototype); Dog.prototype.constructor = Dog;
Klaida 3: this konteksto supratimas
this elgesys prototipo metoduose yra labai svarbus. this visada nurodo į objektą, kurio metodas yra kviečiamas, o ne ten, kur metodas yra apibrėžtas. Tai yra fundamentu, kaip metodai veikia per prototipų grandinę.
Geriausia praktika: Būkite sąmoningi, kaip metodai yra kviečiami. Naudokite `.call()`, `.apply()` arba `.bind()`, jei reikia aiškiai nustatyti this kontekstą, ypač kai metodai perduodami kaip grįžtamieji kvietimai.
Klaida 4: Sumaištis su klasėmis kitose kalbose
Kūrėjai, įpratę prie klasikinio paveldimo (kaip Java ar C++), gali rasti JavaScript prototipinį paveldėjimo modelį iš pradžių neintuityvų. Atminkite, kad ES6 klasės yra fasadas; pagrindinis mechanizmas vis dar yra prototipai.
Geriausia praktika: Priimkite JavaScript prototipinę prigimtį. Sutelkite dėmesį į tai, kaip objektai deleguoja savybių paiešką per savo prototipus.
Toliau nuo pagrindų: pažangūs konceptai
instanceof operatorius
instanceof operatorius patikrina, ar objekto prototipų grandinėje yra konkrečios konstruktoriaus prototype savybė. Tai yra galinga priemonė tipų patikrinimui prototipinėje sistemoje.
console.log(myDog instanceof Dog); // Output: true console.log(myDog instanceof Animal); // Output: true console.log(myDog instanceof Object); // Output: true console.log(myDog instanceof Array); // Output: false
isPrototypeOf() metodas
Object.prototype.isPrototypeOf() metodas tikrina, ar objektas yra bet kurioje kito objekto prototipų grandinėje.
console.log(Dog.prototype.isPrototypeOf(myDog)); // Output: true console.log(Animal.prototype.isPrototypeOf(myDog)); // Output: true console.log(Object.prototype.isPrototypeOf(myDog)); // Output: true
Savybių užgožimas
Objekto savybė sakoma užgožianti prototipo savybę, jei ji turi tą patį pavadinimą. Kai pasiekiate savybę, gaunama ta, kuri yra pačiame objekte, o prototipo savybė ignoruojama (kol objekto savybė nėra ištrinta). Tai taikoma tiek duomenų savybėms, tiek metodams.
class Person {
constructor(name) {
this.name = name;
}
greet() {
console.log(`Hello from Person: ${this.name}`);
}
}
class Employee extends Person {
constructor(name, id) {
super(name);
this.id = id;
}
// Užgožiant greet metodą iš Person
greet() {
console.log(`Hello from Employee: ${this.name}, ID: ${this.id}`);
}
}
const emp = new Employee("Jane", "E123");
emp.greet(); // Output: Hello from Employee: Jane, ID: E123
// Norint iškviesti tėvų greet metodą, mums reikėtų super.greet()
Išvada
JavaScript prototipų grandinė yra fundamentalus konceptas, kuris sudaro pagrindą tam, kaip objektai kuriami, kaip pasiekiamos savybės ir kaip pasiekiamas paveldėjimas. Nors moderni sintaksė, tokia kaip ES6 klasės, supaprastina jos naudojimą, prototipų supratimas yra būtinas kiekvienam rimtam JavaScript kūrėjui. Įvaldžius šį konceptą, jūs įgyjate galimybę rašyti efektyvesnį, pakartotinai naudojamą ir prižiūrimą kodą, o tai yra kritiškai svarbu efektyviai bendradarbiaujant tarptautiniuose projektuose. Nesvarbu, ar kuriate tarptautinei korporacijai, ar mažam startuoliui su tarptautine vartotojų baze, tvirtas JavaScript prototipinio paveldimo supratimas tarnaus kaip galinga priemonė jūsų kūrimo arsenalą.
Tęskite tyrinėjimą, mokykitės ir laimingai koduokite!