Программалоодо биз көбүнчө бир нерсени алып, аны кеңейтүүнү каалайбыз.
Мисалы, бизде user
өзүнүн касиеттери жана ыкмалары бар объект бар жана биз объекттерди admin
жана guest
анын бир аз өзгөртүлгөн варианттары катары түзгүбүз келет. Биз объектте болгон нерсени кайра колдонгубуз келет user
, анын ыкмаларын көчүрбөй/жок кылбай, жөн гана анын негизинде жаңы объект түзгүбүз келет.
Прототипти мурастоо - бул жардам берген тил өзгөчөлүгү.
[[Прототип]]
JavaScript'те объекттер башка объектке [[Prototype]]
барабар же ага тиешелүү атайын жашыруун касиетке ээ (спецификацияда ошондой аталат) . null
Бул объект "прототип" деп аталат:
Прототип бизге кандайдыр бир "сыйкырды" берет. Качан биз касиетти окугубуз келсе object
жана ал жок болсо, JavaScript аны автоматтык түрдө прототиптен алат. Программалоодо бул механизм "прототип мурасы" деп аталат. Тилдин көптөгөн кызыктуу өзгөчөлүктөрү жана программалоо ыкмалары ага негизделген.
Мүлк [[Prototype]]
ички жана жашыруун, бирок аны орнотуунун көптөгөн жолдору бар.
Бири колдонуу керек __proto__
, мисалы:
@A@let animal = {
eats: true
};
let rabbit = {
jumps: true
};
rabbit.__proto__ = animal;@A@
Эгерде биз мүлктү издеп жаткан болсок rabbit
жана ал жок болсо, JavaScript аны автоматтык түрдө animal
.
Мисалы:
@A@let animal = {
eats: true
};
let rabbit = {
jumps: true
};
rabbit.__proto__ = animal; // (*)
// теперь мы можем найти оба свойства в rabbit:
alert( rabbit.eats ); // true (**)
alert( rabbit.jumps ); // true@A@
Бул жерде линия прототиби катары (*)
белгиленет .animal
rabbit
Андан кийин, alert
ал касиетти окууга аракет кылганда rabbit.eats
(**)
, ал ичинде эмес rabbit
, андыктан JavaScript шилтемени ээрчип [[Prototype]]
, аны ичинен табат animal
(төмөндөн өйдө карай):
animal
Бул жерде биз " прототиби rabbit
" же " rabbit
прототиптик жактан мураска алган animal
" деп айта алабыз .
Ошентип, көптөгөн пайдалуу касиеттер жана ыкмалар бар болсо animal
, анда алар автоматтык түрдө rabbit
. Мындай касиеттери "мураска" деп аталат.
Эгерде бизде метод бар болсо animal
, анда аны чакырса болот rabbit
:
@A@let animal = {
eats: true,
walk() {
alert("Animal walk");
}
};
let rabbit = {
jumps: true,
__proto__: animal
};
// walk взят из прототипа
rabbit.walk(); // Animal walk@A@
Метод автоматтык түрдө прототиптен алынат:
Прототип чынжыр узунураак болушу мүмкүн:
@A@let animal = {
eats: true,
walk() {
alert("Animal walk");
}
};
let rabbit = {
jumps: true,
__proto__: animal
};
let longEar = {
earLength: 10,
__proto__: rabbit
};
// walk взят из цепочки прототипов
longEar.walk(); // Animal walk
alert(longEar.jumps); // true (из rabbit)@A@
Эми, эгер биз бир нерсени окуп , ал жок болсо, JavaScript аны , андан кийин ичинде longEar
издейт .rabbit
animal
Эки гана чектөө бар:
- Шилтемелер чөйрөлөргө кире албайт.
__proto__
Тегерек боюнча дайындоого аракет кылсак, JavaScript ката кетирет . - Маани
__proto__
объект же болушу мүмкүнnull
. Башка түрлөрү эске алынбайт.
Бул абдан айкын, бирок дагы эле: бир гана болушу мүмкүн [[Prototype]]
. Объект башка эки объекттен мурас ала албайт.
__proto__
- бул тарыхый жактан аныкталган алуучу/сетер[[Prototype]]
Жаңыдан иштеп чыгуучулардын экөөнүн ортосундагы айырманы билбестен кетирген катасы.
Бул ички менчик менен бирдей эмес__proto__
экенин белгилеңиз . Бул үчүн алуу/жөндөөчү . Биз бул маанилүү болгон жагдайларды кийинчерээк көрөбүз, бирок азыр JavaScript тили боюнча түшүнүгүбүздү бекемдегенде муну эстейли.[[Prototype]]
[[Prototype]]
Мүлк __proto__
бир аз эскирген, ал тарыхый себептерден улам бар. Object.getPrototypeOf/Object.setPrototypeOf
Заманбап JavaScript прототибин алуу/ орнотуунун ордуна функцияларды колдонууну сунуш кылат . Бул өзгөчөлүктөрдү да кийинчерээк карап чыгабыз.
Спецификацияга ылайык, __proto__
ал браузерлер тарабынан гана колдоого алынышы керек, бирок чындыгында бардык чөйрөлөр, анын ичинде сервер, аны колдойт. Ошентип, биз аны абдан коопсуз колдонобуз.
Андан ары, биз мисалдарда колдонобуз __proto__
, анткени бул прототибин орнотуунун жана окуунун эң кыска жана интуитивдик жолу.
Жазуу операциясы прототипти колдонбойт
Прототип касиеттерин окуу үчүн гана колдонулат.
Жазуу/жок кылуу операциялары түздөн-түз объектте иштейт.
Төмөнкү мисалда биз rabbit
ыңгайлаштырылган ыкманы дайындап жатабыз walk
:
@A@let animal = {
eats: true,
walk() {
/* этот метод не будет использоваться в rabbit */
}
};
let rabbit = {
__proto__: animal
};
rabbit.walk = function() {
alert("Rabbit! Bounce-bounce!");
};@A@
rabbit.walk(); // Rabbit! Bounce-bounce!
Эми чалуу rabbit.walk()
методду түздөн-түз объекттен табат жана аны прототибин колдонбостон аткарат.
Аксессордун касиеттери өзгөчө болуп саналат, анткени аларга жазуу орнотуучу функция тарабынан иштетилет. Башкача айтканда, бул иш жүзүндө функция чакыруу.
Ушул себептен улам, admin.fullName
ал төмөндөгү коддо туура иштейт:
@A@let user = {
name: "John",
surname: "Smith",
set fullName(value) {
[this.name, this.surname] = value.split(" ");
},
get fullName() {
return `${this.name} ${this.surname}`;
}
};
let admin = {
__proto__: user,
isAdmin: true
};
alert(admin.fullName); // John Smith (*)
// срабатывает сеттер!
admin.fullName = "Alice Cooper"; // (**)
alert(admin.name); // Alice
alert(admin.surname); // Cooper@A@
Бул жерде сапта (*)
мүлктүн admin.fullName
прототипинде алуучу бар user
, ошондуктан ал деп аталат. Сапта, (**)
касиеттин прототипинде дагы бир орнотуучу бар, ал чакырылат.
"бул" сөзүнүн мааниси
Жогорудагы мисалда кызыктуу суроо туулат: this
ичиндеги маани кандай set fullName(value)
? касиеттери кайда this.name
жана this.surname
: ичинде user
же ичинде admin
?
Жооп жөнөкөй: прототиптер this
.
Метод кайда жайгашканы маанилүү эмес: объектте же анын прототипинде. Методду чакырганда this
, ар дайым чекиттин алдындагы объект.
Ошентип, орнотуучуну чакыруу admin.fullName=
, эмес , this
колдонот .admin
user
Бул чындыгында абдан маанилүү бир деталь, анткени биз мураска ала турган көптөгөн ыкмалар менен чоң объектке ээ боло алабыз. Андан кийин мурастоочу объекттер анын ыкмаларын чакыра алат, бирок алар негизги объекттин абалын эмес, алардын абалын өзгөртүшөт.
Мисалы, бул жерде animal
"ыкма дүкөнү", жана rabbit
аны колдонот.
Чакыруу объект үчүн rabbit.sleep()
белгиленет :this.isSleeping
rabbit
@A@// методы animal
let animal = {
walk() {
if (!this.isSleeping) {
alert(`I walk`);
}
},
sleep() {
this.isSleeping = true;
}
};
let rabbit = {
name: "White Rabbit",
__proto__: animal
};
// модифицирует rabbit.isSleeping
rabbit.sleep();
alert(rabbit.isSleeping); // true
alert(animal.isSleeping); // undefined (нет такого свойства в прототипе)@A@
Жыйынтыгы бар сүрөт:
Эгерде бизде мураска калган bird
ж.б. сыяктуу башка объекттер болсо , алар да кире алышат . Бирок ар бир ыкманы чакырганда, ал чалуу болгон объектке (мезгилге чейин) дал келет, бирок . Ошондуктан, биз маалымат жазганда , бул объекттерде сакталат.snake
animal
animal
this
animal
this
Натыйжада, методдор коомдук болуп саналат, ал эми объект абалы эмес.
үчүн... циклде
Цикл for..in
өзүнүн гана эмес, объектинин тукум кууп өткөн касиеттери аркылуу да өтөт.
Мисалы:
@A@let animal = {
eats: true
};
let rabbit = {
jumps: true,
__proto__: animal
};
// Object.keys возвращает только собственные ключи
alert(Object.keys(rabbit)); // jumps
// for..in проходит и по своим, и по унаследованным ключам
for(let prop in rabbit) alert(prop); // jumps, затем eats@A@
Эгерде бизге тукум кууп өткөн касиеттердин кереги жок болсо, биз аларды obj.hasOwnProperty(ачкыч) орнотулган ыкмасын колдонуп чыпкалай алабыз : ал өзүнүн мураска алынбаган касиетине ээ true
болсо кайтарат .obj
key
Мындай чыпкалоонун мисалы:
@A@let animal = {
eats: true
};
let rabbit = {
jumps: true,
__proto__: animal
};
for(let prop in rabbit) {
let isOwn = rabbit.hasOwnProperty(prop);
if (isOwn) {
alert(`Our: ${prop}`); // Our: jumps
} else {
alert(`Inherited: ${prop}`); // Inherited: eats
}
}@A@
Бул мисалда, мурас чынжыр төмөнкүдөй көрүнөт: rabbit
дан мурастайт animal
, андан мурастайт Object.prototype
( animal
сөзмө-сөз объект болгондуктан {...}
, бул демейки болуп саналат), анан null
эң жогору жагында:
Дагы бир деталды белгилей кетели. Метод кайдан келди rabbit.hasOwnProperty
? Биз аны так аныктай элекпиз. Эгер прототиби чынжырды карасаңыз, анын Object.prototype.hasOwnProperty
. Башкача айтканда, тукум куучулук.
…Бирок эмне үчүн hasOwnProperty
ал циклде көрүнбөйт for..in
, окшошпойт eats
жана jumps
? Ал бардык тукум кууп өткөн касиеттерди санайт.
Жооп жөнөкөй: аны санап чыгуу мүмкүн эмес. Башкача айтканда, анын башка касиеттери сыяктуу ички желеги enumerable
бар . Ошондуктан, ал циклде пайда болбойт.false
Object.prototype
Object.keys
, Object.values
жана башкалар сыяктуу ачкычтарды/маанилерди алган дээрлик бардык башка ыкмалар тукум кууган касиеттерге көңүл бурбайт.
Алар объекттин прототибин эмес, өзүнүн касиеттерин гана эске алышат.
Бардыгы
- JavaScript-те бардык объекттердин жашыруун касиети бар
[[Prototype]]
, ал башка объект жеnull
. - Биз ага жетүү үчүн колдоно алабыз
obj.__proto__
(тарыхый шартталган алуучунун/жөндөөчүсү, бир аздан кийин башка жолдору бар). - Белгиленген объект
[[Prototype]]
"прототип" деп аталат. - Эгерде биз касиетти окугубуз келсе
obj
же -де жок методду чакыргыбыз келсеobj
, анда JavaScript аны прототиптен табууга аракет кылат. - Жазуу/жок кылуу операциялары түздөн-түз объектте иштейт, алар прототипти колдонушпайт (эгерде бул жөндөөчү эмес, кадимки касиет болсо).
- Эгерде биз чакырсак
obj.method()
, жана метод прототиптен алынган болсо, анда алthis
дагы элеobj
. Ошентип, методдор тукум кууп өткөн болсо дагы, ар дайым учурдагы объектиде иштейт. - Цикл
for..in
өзүнүн да, тукум кууган касиеттери боюнча да кайталанат. Ачкычтарды/баалуулуктарды алуунун калган ыкмалары объекттин өзүнүн касиеттери менен гана иштейт.
Tasks
Төмөндөгү код эки объектти түзөт жана өзгөртөт.
Кодду аткаруу учурунда кандай баалуулуктар көрсөтүлөт?
@A@let animal = {
jumps: null
};
let rabbit = {
__proto__: animal,
jumps: true
};
alert( rabbit.jumps ); // ? (1)
delete rabbit.jumps;
alert( rabbit.jumps ); // ? (2)
delete animal.jumps;
alert( rabbit.jumps ); // ? (3)@A@
3 жооп болушу керек.
Тапшырма эки бөлүктөн турат.
Бизде объекттер бар:
@A@let head = {
glasses: 1
};
let table = {
pen: 3
};
let bed = {
sheet: 1,
pillow: 2
};
let pockets = {
money: 2000
};@A@
- Кыймылдын жардамы менен
__proto__
прототиптерди аныктаңыз, ошондой эле каалаган сыпат төмөнкү жол боюнча изделет:pockets
→bed
→table
→head
. Мисалы,pockets.pen
маанини3
( ичинде табылганtable
) жанаbed.glasses
маанини1
( ичинде табылганhead
) кайтарышы керек. - Суроого жооп бериңиз: бааны кантип тезирээк алса болот
glasses
- аркылууpockets.glasses
же аркылууhead.glasses
? Зарыл болсо, издөө чынжырларын жасап, аларды салыштырыңыз.
Объект rabbit
объекттен мураска алат animal
.
Кайсы объект мүлктү full
качан алат rabbit.eat()
: animal
же rabbit
?
@A@let animal = {
eat() {
this.full = true;
}
};
let rabbit = {
__proto__: animal
};
rabbit.eat();@A@
Бизде эки хомяк бар: шамдагай ( speedy
) жана жалкоо ( lazy
); экөө тең жалпы объекттен мурасташат hamster
.
Биз бир хомякты тамактандырсак, экинчи хомяк жейт. Неге? Аны кантип оңдоо керек?
@A@let hamster = {
stomach: [],
eat(food) {
this.stomach.push(food);
}
};
let speedy = {
__proto__: hamster
};
let lazy = {
__proto__: hamster
};
// Этот хомяк нашёл еду
speedy.eat("apple");
alert( speedy.stomach ); // apple
// У этого хомяка тоже есть еда. Почему? Исправьте
alert( lazy.stomach ); // apple@A@