Loading...

Прототиптуу мурастоо

Прототиптуу мурастоо

 

Программалоодо биз көбүнчө бир нерсени алып, аны кеңейтүүнү каалайбыз.

Мисалы, бизде 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@

Бул жерде линия прототиби катары (*)белгиленет .animalrabbit

Андан кийин, 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издейт .rabbitanimal

Эки гана чектөө бар:

  1. Шилтемелер чөйрөлөргө кире албайт. __proto__Тегерек боюнча дайындоого аракет кылсак, JavaScript ката кетирет .
  2. Маани __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колдонот .adminuser

Бул чындыгында абдан маанилүү бир деталь, анткени биз мураска ала турган көптөгөн ыкмалар менен чоң объектке ээ боло алабыз. Андан кийин мурастоочу объекттер анын ыкмаларын чакыра алат, бирок алар негизги объекттин абалын эмес, алардын абалын өзгөртүшөт.

Мисалы, бул жерде animal"ыкма дүкөнү", жана rabbitаны колдонот.

Чакыруу объект үчүн rabbit.sleep()белгиленет :this.isSleepingrabbit

@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ж.б. сыяктуу башка объекттер болсо , алар да кире алышат . Бирок ар бир ыкманы чакырганда, ал чалуу болгон объектке (мезгилге чейин) дал келет, бирок . Ошондуктан, биз маалымат жазганда , бул объекттерде сакталат.snakeanimalanimalthisanimalthis

Натыйжада, методдор коомдук болуп саналат, ал эми объект абалы эмес.

үчүн... циклде

Цикл 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болсо кайтарат .objkey

Мындай чыпкалоонун мисалы:

@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.prototypeanimalсөзмө-сөз объект болгондуктан {...}, бул демейки болуп саналат), анан nullэң жогору жагында:

Дагы бир деталды белгилей кетели. Метод кайдан келди rabbit.hasOwnProperty? Биз аны так аныктай элекпиз. Эгер прототиби чынжырды карасаңыз, анын Object.prototype.hasOwnProperty. Башкача айтканда, тукум куучулук.

…Бирок эмне үчүн hasOwnPropertyал циклде көрүнбөйт for..in, окшошпойт eatsжана jumps? Ал бардык тукум кууп өткөн касиеттерди санайт.

Жооп жөнөкөй: аны санап чыгуу мүмкүн эмес. Башкача айтканда, анын башка касиеттери сыяктуу ички желеги enumerableбар . Ошондуктан, ал циклде пайда болбойт.falseObject.prototype

Дээрлик бардык башка ачкыч/баа алуу ыкмалары тукум кууп өткөн касиеттерге көңүл бурбайт

Object.keysObject.valuesжана башкалар сыяктуу ачкычтарды/маанилерди алган дээрлик бардык башка ыкмалар тукум кууган касиеттерге көңүл бурбайт.

Алар объекттин прототибин эмес, өзүнүн касиеттерин гана эске алышат.

Бардыгы

  • JavaScript-те бардык объекттердин жашыруун касиети бар [[Prototype]], ал башка объект же null.
  • Биз ага жетүү үчүн колдоно алабыз obj.__proto__(тарыхый шартталган алуучунун/жөндөөчүсү, бир аздан кийин башка жолдору бар).
  • Белгиленген объект [[Prototype]]"прототип" деп аталат.
  • Эгерде биз касиетти окугубуз келсе objже -де жок методду чакыргыбыз келсе obj, анда JavaScript аны прототиптен табууга аракет кылат.
  • Жазуу/жок кылуу операциялары түздөн-түз объектте иштейт, алар прототипти колдонушпайт (эгерде бул жөндөөчү эмес, кадимки касиет болсо).
  • Эгерде биз чакырсак obj.method(), жана метод прототиптен алынган болсо, анда ал thisдагы эле obj. Ошентип, методдор тукум кууп өткөн болсо дагы, ар дайым учурдагы объектиде иштейт.
  • Цикл for..inөзүнүн да, тукум кууган касиеттери боюнча да кайталанат. Ачкычтарды/баалуулуктарды алуунун калган ыкмалары объекттин өзүнүн касиеттери менен гана иштейт.

Tasks

маанилүүлүгү: 5

Төмөндөгү код эки объектти түзөт жана өзгөртөт.

Кодду аткаруу учурунда кандай баалуулуктар көрсөтүлөт?

@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 жооп болушу керек.

чечим
маанилүүлүгү: 5

Тапшырма эки бөлүктөн турат.

Бизде объекттер бар:

@A@let head = {
  glasses: 1
};

let table = {
  pen: 3
};

let bed = {
  sheet: 1,
  pillow: 2
};

let pockets = {
  money: 2000
};@A@
  1. Кыймылдын жардамы менен __proto__прототиптерди аныктаңыз, ошондой эле каалаган сыпат төмөнкү жол боюнча изделет: pockets→ bed→ table→ head. Мисалы, pockets.penмаанини 3( ичинде табылган table) жана bed.glassesмаанини 1( ичинде табылган head) кайтарышы керек.
  2. Суроого жооп бериңиз: бааны кантип тезирээк алса болот glasses- аркылуу pockets.glassesже аркылуу head.glasses? Зарыл болсо, издөө чынжырларын жасап, аларды салыштырыңыз.
чечим
маанилүүлүгү: 5

Объект rabbitобъекттен мураска алат animal.

Кайсы объект мүлктү fullкачан алат rabbit.eat()animalже rabbit?

@A@let animal = {
  eat() {
    this.full = true;
  }
};

let rabbit = {
  __proto__: animal
};

rabbit.eat();@A@
чечим
маанилүүлүгү: 5

Бизде эки хомяк бар: шамдагай ( 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@