Класс мурастоо - бул бир классты экинчи класска кеңейтүү жолу.
Ошентип, биз учурдагы функцияга жаңы функцияларды кошо алабыз.
ключевой соз "extends"
Бизде класс бар дейли Animal
:
@A@class Animal {
constructor(name) {
this.speed = 0;
this.name = name;
}
run(speed) {
this.speed = speed;
alert(`${this.name} бежит со скоростью ${this.speed}.`);
}
stop() {
this.speed = 0;
alert(`${this.name} стоит неподвижно.`);
}
}
let animal = new Animal("Мой питомец");@A@
Бул жерде биз объектти animal
жана классты Animal
графикалык түрдө кантип көрсөтө алабыз:
…Биз дагы бирөөнү түзгүбүз келет class Rabbit
.
Коёндор жаныбарлар болгондуктан, класс "жалпы" жаныбарлар жасай ала турган нерселерди жасай алышы үчүн, класс жаныбарлардын ыкмаларына негизделиши керек Rabbit
.Animal
Башка классты кеңейтүү синтаксиси: class Child extends Parent
.
class Rabbit
Төмөнкүдөн мурас алганды түзөлү Animal
:
@A@class Rabbit extends Animal {
hide() {
alert(`${this.name} прячется!`);
}
}
let rabbit = new Rabbit("Белый кролик");
rabbit.run(5); // Белый кролик бежит со скоростью 5.
rabbit.hide(); // Белый кролик прячется!@A@
Класс объектиси Rabbit
эки методго , Rabbit
мисалы , rabbit.hide()
методдорго жетүү мүмкүнчүлүгүнө ээ .Animal
rabbit.run()
Ички, ачкыч сөз extends
жакшы эски прототиби механикасына ылайык иштейт. Ал Rabbit.prototype.[[Prototype]]
орнотулат Animal.prototype
. Ошентип, эгер метод ичинде көрүнбөсө Rabbit.prototype
, JavaScript аны Animal.prototype
.
Мисалы, ыкманы табуу үчүн rabbit.run
кыймылдаткыч текшерет (сүрөттө ылдыйдан өйдө карай):
- Объект
rabbit
(жокrun
). - Анын прототиби, б.а.
Rabbit.prototype
(барhide
, бирок жокrun
). - Анын прототиби, б.а. (байланыштуу
extends
)Animal.prototype
, акырындаrun
.
Курулган прототиптер бөлүмүндө эсибизде болгондой , JavaScript өзү орнотулган объекттер үчүн прототип мурасын колдонот. Мисалы, Date.prototype.[[Prototype]]
is Object.prototype
, ошондуктан даталардын жалпы объект ыкмалары бар.
extends
Ар кандай туюнтмаларга уруксат берилгенден кийинextends
Класс түзүү синтаксиси класстан кийин гана эмес, каалаган туюнтманы да көрсөтүүгө мүмкүндүк берет .
Аталык классты түзүүчү функцияны чакыруунун мисалы:
@A@function f(phrase) {
return class {
sayHi() { alert(phrase); }
};
}
class User extends f("Привет") {}
new User().sayHi(); // Привет@A@
Бул жерде, class User
ал чалуу натыйжасынан мураска алат f("Привет")
.
Бул көптөгөн шарттарга жараша класстарды түзүү үчүн функцияларды колдонуп, андан кийин алардан мурас ала турган өнүккөн дизайн ыкмалары үчүн пайдалуу болушу мүмкүн.
Өткөрүүчү методдор
Эми алдыга барып, ыкманы жокко чыгаралы. Демейки боюнча, класста көрсөтүлбөгөн бардык методдор Rabbit
түздөн-түз класстан "кандай болсо, ошондой" алынат Animal
.
Rabbit
Бирок, мисалы, өзүбүздүн ыкмабызда көрсөтсө stop()
, анда анын ордуна колдонулат:
@A@class Rabbit extends Animal {
stop() {
// ...теперь это будет использоваться для rabbit.stop()
// вместо stop() из класса Animal
}
}@A@
Бирок, биз адатта ата-энелик ыкманы толугу менен алмаштыргыбыз келбейт, тескерисинче, анын негизинде жаңысын жасап, анын функционалдуулугун өзгөртүп же кеңейтебиз. Биз методубузда бир нерсе жасайбыз жана ата-эне ыкмасын мурун/кийин же жүрүп жаткан деп атайбыз.
"super"
Класстарда мындай учурлар үчүн ачкыч сөз бар .
super.method(...)
ата-эне ыкмасын чакырат.super(...)
ата-энелик конструкторду чакыруу (конструктордун ичинде гана иштейт).
Биздин коён токтогондо автоматтык түрдө жашынсын:
@A@class Animal {
constructor(name) {
this.speed = 0;
this.name = name;
}
run(speed) {
this.speed = speed;
alert(`${this.name} бежит со скоростью ${this.speed}.`);
}
stop() {
this.speed = 0;
alert(`${this.name} стоит неподвижно.`);
}
}
class Rabbit extends Animal {
hide() {
alert(`${this.name} прячется!`);
}
stop() {
super.stop(); // вызываем родительский метод stop
this.hide(); // и затем hide
}
}
let rabbit = new Rabbit("Белый кролик");
rabbit.run(5); // Белый кролик бежит со скоростью 5.
rabbit.stop(); // Белый кролик стоит. Белый кролик прячется!@A@
Класста азыр ата-энени аткаруу убагында чакырган Rabbit
метод бар .stop
super.stop()
super
Кайталануучу жебе функцияларында айтылгандай , жебе функциялары жок super
.
Жебе функциясына киргенде super
, ал тышкы функциядан алынат:
@A@class Rabbit extends Animal {
stop() {
setTimeout(() => super.stop(), 1000); // вызывает родительский stop после 1 секунды
}
}@A@
Мисалда super
стрелка функциясы менен бирдей stop()
, ошондуктан ыкма күтүлгөндөй иштейт. Эгерде биз бул жерде "кадимки" функцияны белгилеген болсок, ката болмок:
@A@// Unexpected super
setTimeout(function() { super.stop() }, 1000);@A@
Конструкторду жокко чыгаруу
Конструкторлор бир аз кыйыныраак.
Буга чейин анын Rabbit
өзүнүн конструктору болгон эмес.
Спецификацияга ылайык , эгерде класс башка классты кеңейтсе жана конструктор жок болсо, анда мындай "бош" конструктор автоматтык түрдө түзүлөт:
@A@class Rabbit extends Animal {
// генерируется для классов-потомков, у которых нет своего конструктора
constructor(...args) {
super(...args);
}
}@A@
Көрүнүп тургандай, ал жөн гана ата-эне класстын конструкторун чакырат. Бул биз өзүбүздүн конструктор түзмөйүнчө уланат.
үчүн конструктор кошолу Rabbit
. earLength
Ал кошумча түрдө орнотулат name
:
@A@class Animal {
constructor(name) {
this.speed = 0;
this.name = name;
}
// ...
}
class Rabbit extends Animal {
constructor(name, earLength) {
this.speed = 0;
this.name = name;
this.earLength = earLength;
}
// ...
}
// Не работает!
let rabbit = new Rabbit("Белый кролик", 10); // Error: this is not defined.@A@
Ой! Коенду түзүүдө - ката! Эмне туура эмес?
Кыскасы, анда:
- Тукум кууган класстардагы конструкторлор дайыма чакырып
super(...)
, (!) колдонуудан мурун ушундай кылышы керекthis
. .
…Бирок эмне үчүн? Эмне болуп жатат? Бул талап абдан кызыктай көрүнөт.
Албетте, ар бир нерсенин түшүндүрмөсү бар. Эмне болуп жатканын чындап түшүнүү үчүн майда-чүйдөсүнө чейин кирип көрөлү.
JavaScript'те "класстын конструктор функциясын мурастоо" жана башка бардык нерсенин ортосунда айырма бар. Мурас кылуучу класста тиешелүү конструктор функциясы атайын ички касиет менен белгиленет [[ConstructorKind]]:"derived"
.
айырмасы төмөнкүдөй:
- Кадимки конструктор аткарылганда, ал бош объектти түзүп, аны
this
. - Мурасталган класстын конструктору иштетилгенде, ал иштебейт. Анын ордуна, ата-эне классынын конструктору муну күтөт.
Ошондуктан, эгерде биз өзүбүздүн конструктор түзсөк, анда биз чакырышыбыз керек super
, антпесе for объекти this
түзүлбөйт жана биз ката алабыз.
Конструктор иштеши үчүн , аны колдонуудан мурун эч кандай ката болбошу үчүн Rabbit
чакыруу керек :super()
this
@A@class Animal {
constructor(name) {
this.speed = 0;
this.name = name;
}
// ...
}
class Rabbit extends Animal {
constructor(name, earLength) {
super(name);
this.earLength = earLength;
}
// ...
}
// теперь работает
let rabbit = new Rabbit("Белый кролик", 10);
alert(rabbit.name); // Белый кролик
alert(rabbit.earLength); // 10@A@
Класс талааларын кайра аныктоо: кылдат эскертүү
Бул бөлүм сизде класстарды, балким, башка программалоо тилдеринде кандайдыр бир тажрыйбаңыз бар деп болжолдойт.
Бул тилди жакшыраак түшүнүүгө мүмкүндүк берет, ошондой эле каталардын булагы болушу мүмкүн болгон жүрүм-турумду түшүндүрөт (бирок көп учурда эмес).
Эгер сиз бул материалды түшүнүү өтө кыйын болсоңуз, жөн гана окууну улантып, бир аздан кийин ага кайтып келиңиз.
Биз методдорду гана эмес, класстык талааларды да жокко чыгара алабыз.
Бирок, биз ата-энелик конструктордогу жокко чыгарылган талаага киргенде, бул жүрүм-турум башка программалоо тилдеринен айырмаланат.
Бул мисалды карап көрөлү:
@A@class Animal {
name = 'animal';
constructor() {
alert(this.name); // (*)
}
}
class Rabbit extends Animal {
name = 'rabbit';
}
new Animal(); // animal
new Rabbit(); // animal@A@
Бул жерде класс өз мааниси менен талааны Rabbit
кеңейтет жана кайра аныктайт.Animal
name
B Rabbit
өзүнүн конструктору жок, ошондуктан конструктор деп аталат Animal
.
Кызыктуусу, эки учурда тең: new Animal()
жана new Rabbit()
, alert
сызык (*)
көрсөтөт animal
.
Башкача айтканда, ата-энелик конструктор ар дайым жокко чыгарылган эмес, өзүнүн талаа маанисин колдонот.
Мунун эмнеси кызык?
Эгерде ал азырынча так эмес болсо, методдор менен салыштырыңыз.
Бул жерде ошол эле код, бирок талаанын ордуна this.name
методду чакырабыз this.showName()
:
@A@class Animal {
showName() { // вместо this.name = 'animal'
alert('animal');
}
constructor() {
this.showName(); // вместо alert(this.name);
}
}
class Rabbit extends Animal {
showName() {
alert('rabbit');
}
}
new Animal(); // animal
new Rabbit(); // rabbit@A@
Натыйжа азыр башкача экенин белгилей кетүү керек.
Жана бул биз табигый түрдө күткөн нерсе. Ата-энелик конструктор туунду класста чакырылганда, ал жокко чыгарылган ыкманы колдонот.
...Бирок бул класстык талаалар үчүн андай эмес. Жогоруда айтылгандай, ата-эне конструктор ар дайым аталык талааны колдонот.
Эмне айырма бар?
Ооба, себеби талаалар инициализациялоо иретинде. Класс талаасы инициализацияланат:
- Негизги класстын конструкторунан мурун (ал эч нерсе кеңейтпейт),
super()
Туунду класс үчүн туура кийин .
Биздин учурда Rabbit
, бул туунду класс. Анын конструктору жок constructor()
. Мурда айтылгандай, бул бир гана камтыган бош конструктор болгон сыяктуу super(...args)
.
Ошентип, new Rabbit()
ал ата-энелик конструкторду ушинтип аткарууга чакырат super()
жана (туунду класстар үчүн эреже боюнча) андан кийин гана анын классынын талаалары инициализацияланат. Ата-энелик конструкторду ишке ашыруу учурунда класстын талаалары жок Rabbit
, андыктан талаалар колдонулат Animal
.
Талаалар менен методдордун ортосундагы бул тымызын айырма JavaScript үчүн мүнөздүү.
Бактыга жараша, бул жүрүм-турум ата-эне конструктордо жокко чыгарылган талаа колдонулганда гана пайда болот. Анда эмне болуп жатканын түшүнүү кыйын болушу мүмкүн, ошондуктан биз бул жерде түшүндүрөбүз.
Эгер бул көйгөй болуп калса, аны талаалардын ордуна ыкмаларды же алуучуларды/жөндөөчүлөрдү колдонуу менен чечсе болот.
Түзмөк супер, [[HomeObject]]
Эгер сиз окуу куралын биринчи жолу окуп жатсаңыз, бул бөлүмдү өткөрүп жиберсеңиз болот.
Ал мурастын жана чакыруунун ички өзгөчөлүктөрү жөнүндө айтат super
.
Капоттун астын карап көрөлү super
. Бул жерде кээ бир кызыктуу жагдайлар бар.
Жалпысынан, ушул убакка чейин биздин билимибизге таянсак, super
ал такыр иштей албайт!
Ооба, чындап эле, өзүбүзгө суроо берели - бул таза техникалык жактан кантип иштеши керек? Объект ыкмасы аткарылганда, ал учурдагы объектти катары алат this
. Эгерде биз чакырсак super.method()
, анда кыймылдаткыч method
учурдагы объекттин прототибинен чыгышы керек. Анан кантип ал муну жасай алат?
Тапшырма жөнөкөй сезилиши мүмкүн, бирок андай эмес. Кыймылдаткыч учурдагыны билет this
жана ата-эне ыкмасын this.__proto__.method
. Бирок, тилекке каршы, мындай «наивный» жол иштебейт.
Келгиле, көйгөйдү көрсөтөлү. Класстарсыз, түшүнүктүү болуу үчүн жөнөкөй объекттерди колдонуу.
[[HomeObject]]
Эгер чоо-жайын билгиңиз келбесе, бул бөлүктү өткөрүп жиберип, төмөнкү бөлүмгө өтсөңүз болот . Эч кандай зыяны болбойт. Же түшүнгүң келсе оку.
Төмөнкү мисалда rabbit.__proto__ = animal
. Төмөнкү аркылуу rabbit.eat()
чалууга аракет кылалы :animal.eat()
this.__proto__
@A@let animal = {
name: "Animal",
eat() {
alert(`${this.name} ест.`);
}
};
let rabbit = {
__proto__: animal,
name: "Кролик",
eat() {
// вот как предположительно может работать super.eat()
this.__proto__.eat.call(this); // (*)
}
};
rabbit.eat(); // Кролик ест.@A@
Сапта биз прототиптен ( ) (*)
алып , аны учурдагы объекттин контекстинде чакырабыз. Бул жерде бир себеп бар экенине көңүл буруңуз : жөнөкөй чакыруу ата-энени учурдагы объект эмес, прототиптин контекстинде аткарат .eat
animal
.call(this)
this.__proto__.eat()
eat
Жогорудагы код ойдогудай иштейт: каалаган alert
.
Эми мурас чынжырына дагы бир объектти кошуп, нерселердин кандайча бузуларын карап көрөлү:
@A@let animal = {
name: "Животное",
eat() {
alert(`${this.name} ест.`);
}
};
let rabbit = {
__proto__: animal,
eat() {
// ...делаем что-то специфичное для кролика и вызываем родительский (animal) метод
this.__proto__.eat.call(this); // (*)
}
};
let longEar = {
__proto__: rabbit,
eat() {
// ...делаем что-то, связанное с длинными ушами, и вызываем родительский (rabbit) метод
this.__proto__.eat.call(this); // (**)
}
};
longEar.eat(); // Error: Maximum call stack size exceeded@A@
Эми код иштебейт! Ката чалууга аракет кылып жатканда пайда болот longEar.eat()
.
Бир караганда, баары ачык-айкын эмес, бирок биз чалууну байкасак longEar.eat()
, катанын себебин түшүнө алабыз. Эки сапта тең (*)
, жана (**)
мааниси this
учурдагы объект ( longEar
). Бул маанилүү: бардык объекттик ыкмалар үчүн this
прототипке же башка нерсеге эмес, учурдагы объектке ишарат кылат.
Ошентип, эки сапта тең (*)
жана (**)
маани this.__proto__
бирдей: rabbit
. Эки учурда тең, ыкма rabbit.eat
чалуу чынжырына көтөрүлбөстөн, чексиз циклде чакырылат.
Эмне болуп жатканын сүрөтү:
-
longEar.eat()
Саптын ичинде мааниси менен(**)
чакырат .rabbit.eat
this=longEar
@A@// внутри longEar.eat() у нас this = longEar this.__proto__.eat.call(this) // (**) // становится longEar.__proto__.eat.call(this) // то же что и rabbit.eat.call(this);
-
Сапта биз чакырууну чынжырга өткөргүбүз келет, бирок
(*)
, демек, кайра ге барабар !rabbit.eat
this=longEar
this.__proto__.eat
rabbit.eat
// внутри rabbit.eat() у нас также this = longEar this.__proto__.eat.call(this) // (*) // становится longEar.__proto__.eat.call(this) // или (снова) rabbit.eat.call(this);@A@
-
...
rabbit.eat
өзүн чексиз циклде чакырат, анткени ал чынжырдан ары кете албайт.
Бир эле маселени чечүү мүмкүн эмес this
.
[[HomeObject]]
Бул маселени чечүү үчүн, JavaScript функциялар үчүн атайын ички касиетти кошту: [[HomeObject]]
.
Функция класстын же объекттин ичинде метод катары жарыяланганда, анын касиети [[HomeObject]]
ошол объектке барабар болот.
Андан кийин super
ата-эненин прототибин жана анын ыкмаларын алуу үчүн колдонот.
Келгиле, анын кантип иштээрин карап көрөлү - кайра жөнөкөй объекттерди колдонуу:
@A@let animal = {
name: "Животное",
eat() { // animal.eat.[[HomeObject]] == animal
alert(`${this.name} ест.`);
}
};
let rabbit = {
__proto__: animal,
name: "Кролик",
eat() { // rabbit.eat.[[HomeObject]] == rabbit
super.eat();
}
};
let longEar = {
__proto__: rabbit,
name: "Длинноух",
eat() { // longEar.eat.[[HomeObject]] == longEar
super.eat();
}
};
// работает верно
longEar.eat(); // Длинноух ест.@A@
аркасында ойдогудай иштейт [[HomeObject]]
. Метод, мисалы longEar.eat
, өзүн билет [[HomeObject]]
жана анын прототипинен ата-эненин ыкмасын алат. Жалпысынан колдонбостон this
.
Методдор "акысыз" эмес
Биз буга чейин JavaScript'теги функциялар объекттерге байланбаган "акысыз" экенин көп жолу көргөнбүз. Аларды объекттердин ортосунда көчүрүп, каалаган менен чакырса болот this
.
Бирок бар болуунун өзү [[HomeObject]]
бул принципти бузат, анткени методдор өз объектилерин эстейт. [[HomeObject]]
өзгөртүү мүмкүн эмес, бул байланыш түбөлүктүү.
[[HomeObject]]
Бул тилде колдонулган бир гана жер super
. Ошондуктан, эгерде метод колдонбосо super
, анда биз аны бекер деп эсептеп, объекттердин ортосунда көчүрө алабыз. Бирок super
коддо бар болсо, анда терс таасирлери болушу мүмкүн.
Көчүрүүдөн кийин туура эмес жыйынтыктын мисалы super
:
@A@let animal = {
sayHi() {
console.log("Я животное");
}
};
// rabbit наследует от animal
let rabbit = {
__proto__: animal,
sayHi() {
super.sayHi();
}
};
let plant = {
sayHi() {
console.log("Я растение");
}
};
// tree наследует от plant
let tree = {
__proto__: plant,
sayHi: rabbit.sayHi // (*)
};
tree.sayHi(); // Я животное (?!?)@A@
Чакырыкта tree.sayHi()
"Мен жаныбармын" деп көрсөтүлөт. Албетте, туура эмес.
Себеби жөнөкөй:
- Сапта
(*)
, ыкмаданtree.sayHi
көчүрүлөтrabbit
. Балким, биз кодду кайталоодон качкыбыз келдиби? - Аныкы
[[HomeObject]]
,rabbit
анткени ал жылы жаратылганrabbit
. Мүлк[[HomeObject]]
эч качан өзгөрбөйт. - Коддо
tree.sayHi()
чакыруу барsuper.sayHi()
. Ал өйдө көтөрүлүпrabbit
, андан ыкманы алатanimal
.
Бул жерде эмне болуп жатканын диаграммасы:
Функциянын касиеттери эмес, методдору
Мүлк [[HomeObject]]
класстардын да, регулярдуу объекттердин да методдору үчүн аныкталган. method()
Бирок объекттер үчүн методдор , деп эмес, так жарыяланышы керек "method: function()"
.
Биз үчүн эч кандай айырмачылыктар жок, бирок алар JavaScript үчүн.
Төмөндөгү мисалда ыкма эмес, касиет-функция синтаксиси колдонулат. Демек, анда жок [[HomeObject]]
жана мурас иштебейт:
@A@let animal = {
eat: function() { // намеренно пишем так, а не eat() { ...
// ...
}
};
let rabbit = {
__proto__: animal,
eat: function() {
super.eat();
}
};
rabbit.eat(); // Ошибка вызова super (потому что нет [[HomeObject]])@A@
Бардыгы
- Класстан мурастоо үчүн
class Child extends Parent
:- Бул ге
Child.prototype.__proto__
барабар болотParent.prototype
, андыктан ыкмалар мураска калат.
- Бул ге
- Конструкторду жокко чыгарууда:
- Чакыруудан мурун
super()
конструктордогу ата-эне конструкторду чакыруу милдеттүү .Child
this
- Чакыруудан мурун
- Башка ыкманы жокко чыгарууда:
- Биз чалсак болот
super.method()
Child
Биз аталык ыкмага кайрылуу үчүн ыкманыParent
.
- Биз чалсак болот
- Ички маалыматтар:
- Методдор өз объектисин ички касиетте сактайт
[[HomeObject]]
. Мунун аркасында, ал иштейтsuper
, анын прототиби ата-эне ыкмаларын издейт. - Ошентип, колдонуу ыкмасын көчүрүү
super
ар кандай объекттер арасында колдонулган ыкманы көчүрүү кооптуу.
- Методдор өз объектисин ички касиетте сактайт
Ошондой эле:
this
Жебе функцияларынын өздүк жана . функциялары жокsuper
, ошондуктан алар тышкы контекстке "ачык" киргизилген.
Tasks
Төмөндөгү коддо класс Rabbit
мураска алат Animal
.
Тилекке каршы, класс объекти Rabbit
түзүлгөн эмес. Эмне туура эмес? Катаны оңдоңуз.
@A@class Animal {
constructor(name) {
this.name = name;
}
}
class Rabbit extends Animal {
constructor(name) {
this.name = name;
this.created = Date.now();
}
}
let rabbit = new Rabbit("Белый кролик"); // Error: this is not defined
alert(rabbit.name);@A@
Бизде класс бар Clock
. Эми ал секунд сайын убакытты басып чыгарат
@A@class Clock {
constructor({ template }) {
this.template = template;
}
render() {
let date = new Date();
let hours = date.getHours();
if (hours < 10) hours = '0' + hours;
let mins = date.getMinutes();
if (mins < 10) mins = '0' + mins;
let secs = date.getSeconds();
if (secs < 10) secs = '0' + secs;
let output = this.template
.replace('h', hours)
.replace('m', mins)
.replace('s', secs);
console.log(output);
}
stop() {
clearInterval(this.timer);
}
start() {
this.render();
this.timer = setInterval(() => this.render(), 1000);
}
}@A@
Туура жаңы классты ExtendedClock
мурастай турган Clock
жана параметрди кошо турган precision
- "кенелердин" ортосундагы миллисекунддордун саны. 1000
Демейки боюнча маанини (1 секунд) коюңуз .
- Кодуңузду файлга сактаңыз
extended-clock.js
- Классты алмаштырба
clock.js
. Аны кеңейтүү.