Эта статья является третьей в обширной серии из пяти частей о прототипном наследовании.
Часть 1 — Понимание цепочки прототипов
Часть 2 — Наследование прототипов с помощью Object.create
Часть 3 — Наследование прототипов с функциями конструктора
Часть 4 — Скоро!
Часть 5 — Скоро!
Во второй части этой серии мы увидели, что, хотя мы можем использовать литералы объектов для создания цепочек прототипов, это становится непрактичным, когда нам нужно создать несколько объектов с одинаковой формой.
В этом посте я покажу, как создавать надежные цепочки прототипов с помощью функций-конструкторов. Я настоятельно рекомендую выполнить упражнения в самом конце этого поста, прежде чем переходить к части 4.
Если вы не знакомы с тем, что такое цепочка прототипов, как JavaScript проходит цепочку прототипов или как работает Object.create
, я настоятельно рекомендую пройти часть 1 и часть 2, прежде чем углубляться в функции конструктора.
Как всегда, буду рад вашим вопросам, отзывам и конструктивной критике!
Часть 3. Наследование прототипов с функциями конструктора
Функция-конструктор выглядит следующим образом:
function Animal(name) { this.name = name; }
Как видите, это просто старая добрая функция JavaScript. С технической точки зрения, Animal
является «конструктором» не больше, чем любая другая функция. В JavaScript любая функция может использоваться как функция-конструктор; все, что нам нужно сделать, это применить ключевое слово new
перед его вызовом, как в const someAnimal = new animal(‘BlippityDop’)
.
Несмотря на это, обратите внимание, как Animal
пишется с большой буквы? По соглашению, когда мы хотим, чтобы функция была функцией-конструктором, мы пишем ее с большой буквы. На самом деле некоторые линтеры жалуются, если мы вызываем new
для функции с именем в нижнем регистре или если мы не вызываем new
для функции, имя которой начинается с заглавной буквы.
Теперь, когда мы объявляем любую функцию F
, JavaScript автоматически создает новый объект F.prototype
. Таким образом, когда мы объявили Animal
выше, среда выполнения JavaScript сделала следующее:
- Создал функцию
Animal
и установил для ее ссылки [[prototype]] значениеFunction.prototype
(вспомните наше предыдущее обсуждение: прототипом функций являетсяFunction.prototype
). - Он создал новый объект
- Он добавил свойство
prototype
к функции Animal и указал на этот новый объект. - Установите прототип нового объекта на
Object.prototype
Вы можете визуализировать вышеизложенное следующим образом:
Очень важно отметить, что [[prototype]] ≠ .prototype.
function Animal(name) { this.name = name; } console.log(Object.getPrototypeOf(Animal) === Animal.prototype); // false
Более глубокое понимание функций конструктора поможет вам понять разницу между [[prototype]] и .prototype
. Таким образом, мы продолжим обсуждение конструкторов, а затем вернемся к [[prototype]] и .prototype
позже. А пока просто поймите, что они не одинаковы.
Я рекомендую выполнить упражнение 1, прежде чем продолжить.
Новое ключевое слово
Ранее мы упоминали, что когда ключевое слово new
используется перед вызовом функции, оно преобразует вызов функции в вызов конструктора. Так что же именно делает new
?
function Animal(name) { this.name = name; } const bloppy = new Animal('bloppy');
Когда мы вызвали new
в функции Animal
, среда выполнения JavaScript сделала следующее:
- Создал пустой объект,
bloppy
- Указывает [[prototype]]
bloppy
на свойство прототипаAnimal
. - Выполнил функцию-конструктор с заданными аргументами, привязав
bloppy
к контекстуthis
(т. е. все ссылки наthis
в функции-конструктореAnimal
теперь относятся к объектуbloppy
)
Вы можете визуализировать вышеизложенное следующим образом:
Опять же, очень важно отметить, что прототипом bloppy
является Animal.prototype
, НЕ Animal
. Я предлагаю выполнить приведенный ниже код и убедиться, что вы понимаете, почему каждое выражение приводит к соответствующему результату.
function Animal(name) { this.name = name; } const bloppy = new Animal('bloppy'); console.log(bloppy); // Animal { name: 'bloppy'} console.log(Animal.isPrototypeOf(bloppy)); // false console.log(Animal.prototype === Object.getPrototypeOf(bloppy)); // true console.log(Function.prototype.isPrototypeOf(bloppy)); // false console.log(Function.prototype.isPrototypeOf(Animal)); // true console.log(Object.prototype.isPrototypeOf(bloppy)); // true
Что произойдет, если мы вызовем функцию-конструктор без нового ключевого слова?
function Animal(name) { this.name = name; } const bloppy = Animal('bloppy'); {2} console.log(bloppy.name); // throws TypeError: cannot read properties of undefined (reading 'name')
Получаем TypeError
. По сути, когда приведенный выше код выполняется, происходит следующее:
- Функция
Animal
выполняется в текущей области видимости, а не как конструктор (поскольку мы не использовали ключевое словоnew
). this
внутриAnimal
не относится к вновь созданному объекту, а вместо этого зависит от контекста, в котором вызывается функция (попробуйтеconsole.log(this)
в функцииAnimal
, что вы получите?).- В JavaScript функции, которые явно не возвращают значение, по умолчанию возвращают
undefined
. Таким образом, в {2} мы присваиваемundefined
bloppy
. - При попытке доступа к
bloppy.name
в оператореconsole.log
выдаетсяTypeError
, посколькуbloppy
являетсяundefined
и не имеет свойстваtype
.
Таким образом, когда функция F
вызывается с ключевым словом new
, она работает как конструктор и выполняются следующие шаги:
- Создается новый пустой объект. Назовем этот объект как
newInstance
- Внутреннее свойство [[prototype]]
newInstance
устанавливается равным свойству «prototype» вызываемой функции (т. е.Object.getPrototypeOf(newInstance) === F.prototype
оценивается как true). Это устанавливает цепочку прототипов, позволяяnewInstance
наследовать свойства и методы отF.prototype
. - Функция выполняется с ключевым словом
this
, ссылающимся на вновь созданный объект,newInstance
. Это позволяет функции изменять или добавлять свойства и методы к объекту. - Если функция явно не возвращает объект, новое выражение автоматически возвращает вновь созданный объект. Если функция возвращает объект, вместо этого возвращается этот объект.
Ключевое слово new
существенно облегчает создание объектов определенного типа, определенного функцией-конструктором. Он помогает отличать вызовы конструктора от обычных вызовов функций и предоставляет способ инициализации и настройки объектов на основе логики конструктора.
Прежде чем двигаться дальше, настоятельно рекомендую выполнить Упражнения 2–5. Твердое понимание концепций, обсуждавшихся выше, имеет решающее значение для понимания содержания, которое следует далее.
Добавление методов к объекту-прототипу
Допустим, мы хотели, чтобы каждый объект, созданный вызовом new Animal(…)
, имел метод walk
. Технически мы могли бы сделать следующее:
function Animal(name) { this.name = name; this.walk = function walk() { console.log(`${this.name}: I am walking`) } } const bloppy = new Animal('bloppy'); bloppy.walk(); // bloppy: I am walking const floppy = new Animal('floppy'); floppy.walk(); // floppy: I am walking
Мы можем визуализировать приведенный выше фрагмент следующим образом:
Кажется, это работает нормально. Однако обратите внимание, что мы определили walk
непосредственно в контексте Animal
(переменная this
). По сути, мы добавляем новое свойство walk
к каждому объекту, созданному с помощью вызова new Animal(…)
. Оператор bloppy.walk === floppy.walk
оценивается как false
, потому что метод walk
каждого отдельного объекта находится в другом месте в памяти.
Вместо того, чтобы определять walk
в функции Animal
, вы должны добавить его в Animal.prototype
.
function Animal(name) { this.name = name; } Animal.prototype.walk = function() { console.log(`${this.name}: I am walking`); }; const bloppy = new Animal('bloppy'); bloppy.walk(); // bloppy: I am walking const floppy = new Animal('floppy'); floppy.walk(); // floppy: I am walking console.log(bloppy.walk === floppy.walk); // true
Теперь у нас есть что-то вроде этого:
Такой подход предлагает два ключевых преимущества:
- Эффективность использования памяти: когда вы определяете метод непосредственно в самой функции (например,
this.walk = function() { ... }
), каждый объект, созданный с помощью функции-конструктора, будет иметь собственную копию этого метода. Это может потреблять ненужную память, особенно если у вас много экземпляров. С другой стороны, при добавлении метода кAnimal.prototype
этот метод используется всеми экземплярами, что приводит к эффективному использованию памяти. - Динамическое добавление методов: добавляя методы в
Animal.prototype
, вы можете динамически расширять функциональные возможности существующих объектов. Поскольку все экземпляры наследуют прототип, любые новые методы, добавленные в прототип, будут доступны этим экземплярам.
Разберем второй пункт на примере.
Пример 1:
function Animal(name) { this.name = name; } Animal.prototype.walk = function() { console.log(`${this.name}: I am walking`); }; const cat = new Animal('Whiskers'); cat.walk(); // Whiskers: I am walking // Now let's dynamically add a new method to the Animal prototype Animal.prototype.sleep = function() { console.log(`${this.name}: Zzzz...`); }; cat.sleep(); // Whiskers: Zzzz... const dog = new Animal('Buddy'); dog.walk(); // Buddy: I am walking dog.sleep(); // Buddy: Zzzz...
Выше мы определяем функцию-конструктор Animal
с методом walk
, добавленным к Animal.prototype
. Этот метод используется всеми экземплярами Animal
.
Далее в коде мы динамически добавляем новый метод sleep
в Animal.prototype
с помощью Animal.prototype.sleep = function() { ... }
. Это означает, что любые существующие экземпляры, такие как cat
, а также будущие экземпляры, созданные конструктором Animal
, теперь будут иметь доступ к методу sleep
.
Динамически добавляя метод sleep
к прототипу, мы расширяем функциональность существующих объектов, не изменяя их отдельных определений. Это позволяет нам вводить новое поведение объектов на лету. В примере экземпляры cat
и dog
могут вызывать метод sleep
, даже если он был добавлен после их создания.
Таким образом, если вы хотите, чтобы метод был общим для всех объектов, созданных конкретным вызовом конструктора, добавьте их в прототип функции-конструктора.
Создание цепочки прототипов
До сих пор мы сохраняли наш рабочий пример довольно упрощенным. Мы создали функцию Animal
и использовали ее для создания экземпляров отдельных животных. Давайте попробуем добавить больше сложности, вернувшись к Примеру 2 во Части 2 этой серии. В этом примере мы использовали Object.create
для построения следующей цепочки прототипов:null ← Object.prototype ← animal ← bear ← baloo
Вот код, который мы использовали:
const animal = { walk: function walk() { console.log(`${this.name}: I am walking`) } }; const bear = Object.create(animal, { growl: { value: function growl() { console.log(`${this.name}: rrrrrrrr`) } } }); const baloo = Object.create(bear, { name: { value: 'Baloo' } }); baloo.growl(); // Baloo: rrrrrrrr baloo.walk(); // Baloo: I am walking
Теперь давайте попробуем переписать вышеприведенное, используя функции-конструкторы:
function Animal(name) { this.name = name; } Animal.prototype.walk = function walk() { console.log(`${this.name}: I am walking`); } function Bear(name) { Animal.call(this, name); } Bear.prototype = Object.create(Animal.prototype) Bear.prototype.growl = function growl() { console.log(`${this.name}: rrrrrr`); } const baloo = new Bear('Baloo'); baloo.walk(); // Baloo: I am walking baloo.growl(); // Baloo: rrrrrr console.log(Object.getPrototypeOf(baloo) === Bear.prototype); //true console.log(Object.getPrototypeOf(Bear.prototype) === Animal.prototype); //true console.log(Object.getPrototypeOf(Bear) === Function.prototype); //true console.log(Object.getPrototypeOf(Animal) === Function.prototype); //true console.log(Object.getPrototypeOf(Animal.prototype) === Object.prototype); //true
Приведенный выше код создает следующие отношения:
Или, проще говоря, он создает следующую цепочку прототипов:
null ← Object.prototype ← Function.prototype ← Animal.prototype ← Bear.prototype ← baloo
Давайте рассмотрим код более внимательно.
- Создаем функцию
Animal
и добавляем в ее прототип методwalk
- Мы создаем функцию
Bear
.
- ФункцияBear
включает кодAnimal.call(this, name)
для установки объектаthis
функцииAnimal
. При передачеthis
вAnimal.call
вновь созданный объект (в конечном итоге назначенныйbaloo
) также упоминается какthis
внутри конструктораBear
. Дополнительные аргументы, переданные вcall
, становятся аргументами функции, поэтому аргумент имени «Baloo» передается вAnimal
. В результатеbaloo.name
устанавливается на «Baloo». Без этой строки выполнениеbaloo.walk()
илиbaloo.growl()
приведет кundefined: I am walking
илиundefined: rrrrrr
соответственно. - Оператор
Bear.prototype = Object.create(Animal.prototype)
задает прототипBear.prototype
какAnimal.prototype
. Таким образом, все свойства и методы, определенные вAnimal.prototype
, доступны для объектов, расположенных дальше по цепочке прототипов, включая экземпляры конструктораBear
(например,baloo
). - Добавляем метод
growl
вBear.prototype
. - Мы используем конструктор
Bear
для создания объектаbaloo
. - Звоним
baloo.walk
. Когда мы это делаем, среда выполнения JavaScript проверяет, имеет лиbaloo
свойство walk. Это не. Поэтому он перемещается вверх по цепочке прототипов и проверяет, имеет лиBear.prototype
свойствоwalk
. Это не. Таким образом, он еще раз перемещается вверх по цепочке прототипов и проверяет, имеет лиAnimal.prototype
свойствоwalk
. Среда выполнения выполняет функциюwalk
, устанавливаяthis
вbaloo
. - Звоним
baloo.growl
. Как и выше, среда выполнения Javascript перемещается вверх по цепочке прототипов, пока не найдет методgrowl
.
Свойство конструктора
Наше обсуждение функций-конструкторов было бы неполным без рассмотрения свойства конструктора.
Ранее я упоминал, что когда мы объявляем функцию F
, JavaScript автоматически присваивает ей свойство prototype
, указывающее на пустой объект. Объект-прототип по умолчанию (во время объявления) получает общедоступное неперечислимое свойство с именем .constructor
, и это свойство является обратной ссылкой на функцию, с которой связан объект.
function Animal(name) { this.name = name; } console.log(Animal.prototype.constructor === Animal); // true
Теперь давайте вернемся к нашему кодовому блоку Animal-›Bear-›baloo из предыдущего раздела. Можете ли вы определить проблему?
Взгляните на это заявлениеBear.prototype = Object.create(Animal.prototype)
Подумайте о свойстве конструктора. Ты видишь проблему?
Когда мы объявили функцию Bear
, JavaScript автоматически создал пустой объект и присвоил его Bear.prototype
. Этот объект состоял из свойства constructor
, указывающего на функцию Bear
. Однако, когда мы выполнили Bear.prototype = Object.create(Animal.prototype)
, мы заменили исходный объект новым. Этот новый объект не имеет свойства constructor
. Вы можете ясно увидеть это здесь:
function Animal(name) { this.name = name; } function Bear(name) { Animal.call(this, name); } console.log(Bear.prototype.constructor); // [Function: Bear] console.log(Bear.prototype.constructor === Bear); // true console.log(Bear.prototype.hasOwnProperty('constructor')); // true const arnold = new Bear('arnold'); console.log(arnold); // Bear { name: 'arnold' } Bear.prototype = Object.create(Animal.prototype); console.log(Bear.prototype.hasOwnProperty('constructor')); // false - Bear.prototype no longer has a constructor property console.log(Bear.prototype.constructor); // [Function: Animal] const baloo = new Bear('Baloo'); console.log(baloo); // Animal { name: 'Baloo' } -- wait a minute, isn't baloo a bear?
После выполнения Bear.prototype = Object.create(Animal.prototype)
мы видим, что Bear
больше не имеет свойства constructor
. Но тогда почему, когда мы вызываем console.log(Bear.prototype.constructor)
, мы получаем результат, а не undefined
? Помните: цепочка прототипов. Прототипом Bear.prototype
является Animal.prototype
, а Animal.prototype
имеет свойство constructor
, указывающее на функцию Animal
. Таким образом, когда мы вызываем Bear.prototype.constructor
, движок JavaScript сначала проверяет, имеет ли Bear.prototype
свойство constructor
. Это не. Таким образом, движок проверяет свой прототип Animal.prototype
, у которого есть свойство конструктора.
Так как же решить эту проблему? Мы можем заменить Object.create
на Object.setPrototypeOf
. Наш новый код выглядит следующим образом:
function Animal(name) { this.name = name; } Animal.prototype.walk = function walk() { console.log(`${this.name}: I am walking`); } function Bear(name) { Animal.call(this, name); } Bear.prototype.growl = function growl() { console.log(`${this.name}: rrrrrr`); } Object.setPrototypeOf(Bear.prototype, Animal.prototype); const baloo = new Bear('Baloo'); console.log(baloo); // Bear { name: 'Baloo' } -- He is a Bear! :)
Вам может быть интересно, каковы последствия потери ссылки на конструктор в Bear
? Это действительно имеет значение? Обычно нет — язык почти никогда не считывает свойство constructor
объекта. Однако предположим, что у вас есть код, в котором вызывающая сторона использует свойство constructor
для доступа к исходному классу из экземпляра:
function Animal() {} function Bear() {} Bear.prototype = Object.create(Animal.prototype); function createObjectOfSameType(someObject) { return new someObject.constructor(); } const anAnimal = new Animal(); console.log(createObjectOfSameType(anAnimal)); // Animal {} const aBear = new Bear(); console.log(createObjectOfSameType(aBear)); // Animal {}
Как видите, мы пытались создать Медведя, но создали общее Животное.
Возвращаясь назад: [[prototype]] против .prototype
Ранее в этой статье мы упоминали, что [[prototype]] ≠ .prototype. К настоящему времени вы должны понять разницу. Тем не менее, их легко спутать, поэтому я решил повторить это еще раз.
[[прототип]]:
- Это внутреннее свойство объекта, определяющее его прототип.
- Он представляет собой ссылку на объект-прототип, от которого текущий объект наследует свойства и методы.
.прототип:
- Это свойство существует только у функций
- Он определяет объект, который будет назначен как [[прототип]] всех объектов, созданных с использованием этой функции в качестве конструктора.
Таким образом, [[prototype]] — это фактическая ссылка на объект-прототип экземпляра, позволяющая наследовать свойства и методы. С другой стороны, .prototype
— это свойство функций-конструкторов, которое определяет объект-прототип, назначаемый объектам, созданным этим конструктором.
Заключение
В этой статье мы рассмотрели, как использовать функции конструктора для создания нескольких объектов одинаковой формы. Мы видели, что:
- Используя ключевое слово
new
перед вызовом функции, мы можем преобразовать его в вызов конструктора. Этот процесс включает в себя создание нового объекта, установку его [[prototype]] в свойство конструктораprototype
и выполнение функции с новым объектом в качестве контекста. - Чтобы добавить общие методы к объектам, созданным конструктором, мы определяем их в прототипе конструктора.
Однако вы, вероятно, заметили, что использование функций-конструкторов для создания цепочек прототипов может быть громоздким. Это требует работы со сложными деталями конфигурации прототипа, что увеличивает вероятность внесения ошибок и может привести к необычному поведению, если оно не реализовано идеально (см. Упражнения 6–8).
И именно поэтому мы углубимся в классы JavaScript в части 4 — Прототипное наследование с классами (скоро).
Упражнения
Следующие упражнения предназначены для закрепления вашего понимания того, как использовать функции конструктора для построения цепочки прототипов.
Упражнение 1
Когда вы выполните приведенный ниже код,
- Что является прототипом
Shape
? - На что указывает
Shape.prototype
? - Что является прототипом
Shape.prototype
?
function Shape(type) { this.type = type; }
Решение:
- Прототипом
Shape
являетсяFunction.prototype
, так какShape
— это функция Shape.prototype
указывает на вновь созданный объект- Прототипом
Shape.prototype
(нового объекта) являетсяObject.prototype
.
Упражнение 2
Когда вы выполните приведенный ниже код,
- Что является прототипом
emma
? - Каков прототип прототипа Эммы?
- Что является прототипом
Employee
? - На что указывает
Employee.prototype
?
function Employee(name, hireDate) { this.name = name; this.hireDate = hireDate.toLocaleDateString('en-US'); const emma = new Employee('emma', new Date('January 10 2020'));
Решение:
- Прототипом
emma
являетсяEmployee.prototype
- Прототип прототипа Эммы (
Employee.prototype
) —Object.prototype
. - Прототипом
Employee
являетсяFunction.prototype
Employee.prototype
указывает на объект, который является прототипомemma
(следовательно, прототипомemma
являетсяEmployee.prototype
)
Упражнение 3
function Employee(name, hireDate) { this.name = name; this.hireDate = hireDate.toLocaleDateString('en-US'); } const emma = {}; Employee.call(emma, 'emma', new Date('January 10 2020')); console.log(emma); // { name: 'emma', hireDate: '1/10/2020' }
- Мы никогда не определяли метод
call
дляEmployee
, почему мы не получаемTypeError: Employee.call is not a function
? - Каков прототип объекта
emma
?
Решение:
console.log(Employee.hasOwnProperty('call')); // false console.log(Function.prototype.hasOwnProperty('call')); // true
- Поскольку
Employee
— это функция, ее прототип —Function.prototype
. Метод call существует наFunction.prototype
.
Напомним из части 1, при попытке доступа к свойству объекта поиск этого свойства выходит за пределы самого объекта. Он продолжается до прототипа объекта, прототипа прототипа и так далее. В этом случае исполняющая среда JavaScript увидела, чтоEmployee
не имеет функцииcall
, поэтому просмотрела цепочку прототипов и нашлаcall
вFunction.prototype
. В результате он выполнил методcall
из прототипа. - Прототипом объекта
emma
являетсяObject.prototype
, а НЕEmployee.prototype
. Мы не вызывалиnew Employee
для созданияemma
и не использовалиObject.create
. Таким образом, мы так и не установили прототип отношений междуEmployee
иemma
.
Упражнение 4
Что происходит, когда вы запускаете код ниже?
function Shape(type) { this.type = type; } const triangle = Shape('Triangle'); console.log(triangle.type);
Решение:
Вы получаете сообщение об ошибке TypeError: Cannot read properties of undefined (reading ‘type’)
. Поскольку ключевое слово new
не используется, функция Shape
выполняется в текущей области, и новый объект не создается. Свойство type
добавляется ко всему, на что this
ссылается в текущей области. Теперь, поскольку функции в JavaScript по умолчанию возвращают undefined
, треугольнику присваивается значение undefined
. Попытка доступа к свойству undefined
приводит к ошибке.
Если бы мы использовали ключевое слово new
, функция создала бы новый объект и привязала его к контексту this
. Затем он вернет этот вновь созданный объект.
Упражнение 5
Что оценивает оператор console.log
в приведенном ниже коде? Почему?
function Employee(name, hireDate) { this.name = name; this.hireDate = hireDate.toLocaleDateString('en-US'); return { name, hireDate: hireDate.toLocaleDateString('en-US') } } const emma = new Employee('emma', new Date()); console.log(Object.getPrototypeOf(emma) === Employee.prototype);
Решение:
Он оценивается как false
. Вспомните, когда мы вызываем Employee
с new
, среда выполнения JavaScript делает следующее:
- Создает новый объект. Для удобства обозначим его как
newObj
. - Связывает этот контекст с
newObj
- Создает еще один новый объект. Для удобства обозначим его как
newProto
. - Устанавливает прототип
newObj
вnewProto
- Устанавливает прототип
newProto
вEmployee.prototype
- Если функция НЕ возвращает ничего, возвращает только что созданный объект
newObj
. То естьemma
будет присвоеноnewObj
.
Однако наша функция Employee
вернула объект { name: ‘emma’, hireDate: ‘1/10/2020’ }
, поэтому он был присвоен переменной emma
. Мы никогда не устанавливали прототип этого объекта как Employee.prototype
. По умолчанию его прототипом является Object.prototype
.
Упражнение 6:
Почему вывод кода ниже undefined is flying the undefined
? Ты можешь починить это?
function Aircraft(pilot, type) { this.pilot = pilot; this.type = type; } Aircraft.prototype.fly = function() { console.log(`${this.pilot} is flying the ${this.type}`); } function Jet(pilot, type, maxAltitude) { this.maxAltitude = maxAltitude; } Object.setPrototypeOf(Jet.prototype, Aircraft.prototype); const jumboJet = new Jet('Rayan', 'Jet', 35000 ); jumboJet.fly() // undefined is flying the undefined
Решение:
Вывод кода — undefined is flying the undefined
, потому что мы забыли добавить Aircraft.call(this, pilot, type)
в функцию конструктора Jet. В результате this.pilot
и this.type
становятся undefined
при вызове метода fly
.
Вы можете исправить приведенный выше код, изменив конструктор Jet следующим образом:
function Jet(pilot, type, maxAltitude) { Aircraft.call(this, pilot, type); // Call Aircraft constructor this.maxAltitude = maxAltitude; }
Упражнение 7:
Приведенный ниже код приводит к ошибке. Можете ли вы обнаружить ошибку?
function Tree() {} Tree.prototype.grow = function() { console.log('The tree is growing.'); }; function Oak() {} Oak.prototype.dropLeaves = function() { console.log('The oak tree is dropping leaves.'); }; Oak.prototype = Object.create(Tree.prototype); const oakTree = new Oak(); oakTree.grow(); oakTree.dropLeaves();
Решение:
Ошибка в коде возникает из-за того, что строка Oak.prototype = Object.create(Tree.prototype);
помещается после определения метода dropLeaves()
в прототипе Oak
. Такой порядок операторов приводит к тому, что метод dropLeaves()
перезаписывается присваиванием Object.create(Tree.prototype)
, что приводит к ошибке при попытке вызвать oakTree.dropLeaves()
.
Чтобы решить эту проблему, вам нужно изменить порядок операторов, чтобы присваивание Object.create(Tree.prototype)
предшествовало добавлению метода dropLeaves()
. Вот исправленный код:
function Tree() {} Tree.prototype.grow = function() { console.log('The tree is growing.'); }; function Oak() {} Oak.prototype = Object.create(Tree.prototype); Oak.prototype.dropLeaves = function() { console.log('The oak tree is dropping leaves.'); }; const oakTree = new Oak(); oakTree.grow(); oakTree.dropLeaves();
Упражнение 8:
Когда мы запускаем приведенный ниже код, мы получаем TypeError: someHome.details is not a function
. Почему? Почини это.
function Residence(type, owner, size) { this.type = type; this.owner = owner; this.size = size; }; Residence.prototype.details = function details() { console.log(`This ${this.size} sq ft ${this.type} is owned by ${this.owner}`); } function TownHouse(type, owner, size, maintenanceFee) { Residence.call(this, type, owner, size); this.maintenanceFee = maintenanceFee; } Object.setPrototypeOf(TownHouse, Residence); const someHome = new TownHouse('Townhouse', 'Harry Potter', 3300, 400); someHome.details();
Причина, по которой вы получаете TypeError, заключается в том, что вы неправильно устанавливаете прототип TownHouse
в Residence
. Вместо Object.setPrototypeOf(TownHouse, Residence)
следует использовать Object.setPrototypeOf(TownHouse.prototype, Residence.prototype)
.
Вот исправленный код:
function Residence(type, owner, size) { this.type = type; this.owner = owner; this.size = size; } Residence.prototype.details = function details() { console.log(`This ${this.size} sq ft ${this.type} is owned by ${this.owner}`); }; function TownHouse(type, owner, size, maintenanceFee) { Residence.call(this, type, owner, size); this.maintenanceFee = maintenanceFee; } Object.setPrototypeOf(TownHouse.prototype, Residence.prototype); const someHome = new TownHouse('Townhouse', 'Harry Potter', 3300, 400); someHome.details(); // This 3300 sq ft Townhouse is owned by Harry Potter
Установив для TownHouse.prototype
значение Residence.prototype
, вы установите правильную цепочку прототипов, позволяя someHome
получить доступ к методу details
, определенному в прототипе Residence
.
Упражнение 9:
Перепишите следующее, используя функции конструктора
const Person = { init(name, age) { this.name = name; this.age = age; }, introduce() { console.log(`Hi, my name is ${this.name} and I am ${this.age} years old.`); } }; const Student = Object.create(Person); Student.init = function init(name, age, grade) { Person.init.call(this, name, age); this.grade = grade; } Student.study = function study() { console.log(`${this.name} is studying`); } const Teacher = Object.create(Person); Teacher.init = function init(name, age, subject){ Person.init.call(this, name, age); this.subject = subject; } Teacher.teach = function() { console.log(`${this.name} is teaching ${this.subject}.`); }; const Parent = Object.create(Person); Parent.init = function init(name, age, numChildren) { Person.init.call(this, name, age); this.numChildren = numChildren; }; Parent.care = function() { console.log(`${this.name} is taking care of their children.`); }; const faisal = Object.create(Student); const izzy = Object.create(Teacher); const babak = Object.create(Parent); faisal.init("Faisal", 18, 12); faisal.introduce(); // Hi, my name is Faisal and I am 18 years old. faisal.study(); // Faisal is studying. izzy.init("Izzy", 33, "Math"); izzy.introduce(); // Hi, my name is Izzy and I am 33 years old. izzy.teach(); // Izzy is teaching Math. babak.init("Babak", 43, 2); babak.introduce(); // Hi, my name is Babak and I am 43 years old. babak.care(); // Babak is taking care of their children.
Решение:
function Person(name, age) { this.name = name; this.age = age; } Person.prototype.introduce = function introduce() { console.log(`Hi, my name is ${this.name} and I am ${this.age} years old.`); } function Student (name, age, grade) { Person.call(this, name, age); this.grade = grade; } Student.prototype.study = function study() { console.log(`${this.name} is studying`); } Object.setPrototypeOf(Student.prototype, Person.prototype); function Teacher(name, age, subject) { Person.call(this, name, age); this.subject = subject; } Teacher.prototype.teach = function teach() { console.log(`${this.name} is teaching ${this.subject}.`); } Object.setPrototypeOf(Teacher.prototype, Person.prototype); function Parent(name, age, numChildren) { Person.call(this, name, age); this.numChildren = numChildren; } Parent.prototype.care = function care() { console.log(`${this.name} is taking care of their children.`); } Object.setPrototypeOf(Parent.prototype, Person.prototype); const faisal = new Student('Faisal', 18, 12); faisal.introduce(); faisal.study(); const izzy = new Teacher('Izzy', 33, 'Math'); izzy.introduce(); izzy.teach(); const babak = new Parent('Babak', 43, 2); babak.introduce(); babak.care()
Упражнение 10
- Напишите конструктор Employee, который инициализирует имя и зарплату из аргументов.
- Все сотрудники должны иметь возможность
requestRaise
. Сотрудник, который просит о повышении, говорит: «Меня зовут ‹имя›. Мне нужно повышение» - Менеджер – это тип работника. Напишите конструктор менеджера, который может
promoteEmployee
. Продвижение сотрудника увеличивает заработную плату этого сотрудника. Менеджеры не могут повышать свою зарплату или зарплату другого менеджера. - Разработчик — это тип наемного работника. Разработчики должны иметь возможность
code
. - Фронтенд-разработчик — это тип разработчика. Интерфейсный разработчик, который кодирует, должен сказать: «Я создаю интерфейс».
- Бэкенд-разработчик — это тип разработчика. Бэкенд-разработчик, который кодирует, должен сказать: «Я создаю бэкэнд».
Протестируйте свой код с помощью следующего
const rana = new FrontEndDeveloper('Rana', 130000); const lamya = new BackEndDeveloper('Lamya', 140000); const fadi = new Manager('Fadi', 120000); const wessam = new Manager('Wessam', 160000); lamya.requestRaise(); // My name is Lamya. I need a raise. fadi.promoteEmployee(lamya, 10000); console.log(lamya.salary); //150000 rana.requestRaise(); // My name is Rana. I need a raise. fadi.promoteEmployee(rana, 20000); console.log(rana.salary); //150000 fadi.requestRaise(); // My name is Fadi. I need a raise. fadi.promoteEmployee(fadi, 100000); // Fadi, you cannot promote yourself wessam.promoteEmployee(fadi, 100000); // Wessam, managers cannot promote managers. console.log(fadi.salary); // 120000 lamya.code(); // "I am building the back end." rana.code(); // "I am building the front end." lamya.promoteEmployee(fadi); // This should throw a TypeError: lamya.promoteEmployee is not a function fadi.code(); // This should throw a TypeError: fadi.code is not a function
Решение:
function Employee(name, salary) { this.name = name; this.salary = salary; } Employee.prototype.requestRaise = function requestRaise() { console.log(`My name is ${this.name}. I need a raise.`); } function Manager(name, salary) { Employee.call(this, name, salary); } Manager.prototype.promoteEmployee = function promoteEmployee(employee, amount) { if(employee === this) { return console.log(`${this.name}, you cannot promote yourself.`); } if(Manager.prototype.isPrototypeOf(employee)) { return console.log(`${this.name}, managers cannot promote managers.`); } employee.salary += amount; } Object.setPrototypeOf(Manager.prototype, Employee.prototype); function Developer(name, salary) { Employee.call(this, name, salary); } Developer.prototype.code = function code() { let workType; if(FrontEndDeveloper.prototype.isPrototypeOf(this)) { workType = "front end"; } if(BackEndDeveloper.prototype.isPrototypeOf(this)) { workType = "back end" } console.log(`I am building the ${workType}.`); } Object.setPrototypeOf(Developer.prototype, Employee.prototype); function FrontEndDeveloper(name, salary) { Developer.call(this, name, salary); } Object.setPrototypeOf(FrontEndDeveloper.prototype, Developer.prototype); function BackEndDeveloper(name, salary) { Developer.call(this, name, salary); } Object.setPrototypeOf(BackEndDeveloper.prototype, Developer.prototype);