Эта статья является третьей в обширной серии из пяти частей о прототипном наследовании.

Часть 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 сделала следующее:

  1. Создал функцию Animal и установил для ее ссылки [[prototype]] значение Function.prototype (вспомните наше предыдущее обсуждение: прототипом функций является Function.prototype).
  2. Он создал новый объект
  3. Он добавил свойство prototype к функции Animal и указал на этот новый объект.
  4. Установите прототип нового объекта на 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 сделала следующее:

  1. Создал пустой объект, bloppy
  2. Указывает [[prototype]] bloppy на свойство прототипа Animal.
  3. Выполнил функцию-конструктор с заданными аргументами, привязав 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. По сути, когда приведенный выше код выполняется, происходит следующее:

  1. Функция Animal выполняется в текущей области видимости, а не как конструктор (поскольку мы не использовали ключевое слово new).
  2. this внутри Animal не относится к вновь созданному объекту, а вместо этого зависит от контекста, в котором вызывается функция (попробуйте console.log(this) в функции Animal, что вы получите?).
  3. В JavaScript функции, которые явно не возвращают значение, по умолчанию возвращают undefined. Таким образом, в {2} мы присваиваем undefined bloppy.
  4. При попытке доступа к bloppy.name в операторе console.log выдается TypeError, поскольку bloppy является undefined и не имеет свойства type.

Таким образом, когда функция F вызывается с ключевым словом new, она работает как конструктор и выполняются следующие шаги:

  1. Создается новый пустой объект. Назовем этот объект как newInstance
  2. Внутреннее свойство [[prototype]] newInstance устанавливается равным свойству «prototype» вызываемой функции (т. е. Object.getPrototypeOf(newInstance) === F.prototype оценивается как true). Это устанавливает цепочку прототипов, позволяя newInstance наследовать свойства и методы от F.prototype.
  3. Функция выполняется с ключевым словом this, ссылающимся на вновь созданный объект, newInstance. Это позволяет функции изменять или добавлять свойства и методы к объекту.
  4. Если функция явно не возвращает объект, новое выражение автоматически возвращает вновь созданный объект. Если функция возвращает объект, вместо этого возвращается этот объект.

Ключевое слово 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

Теперь у нас есть что-то вроде этого:

Такой подход предлагает два ключевых преимущества:

  1. Эффективность использования памяти: когда вы определяете метод непосредственно в самой функции (например, this.walk = function() { ... }), каждый объект, созданный с помощью функции-конструктора, будет иметь собственную копию этого метода. Это может потреблять ненужную память, особенно если у вас много экземпляров. С другой стороны, при добавлении метода к Animal.prototype этот метод используется всеми экземплярами, что приводит к эффективному использованию памяти.
  2. Динамическое добавление методов: добавляя методы в 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

Давайте рассмотрим код более внимательно.

  1. Создаем функцию Animal и добавляем в ее прототип метод walk
  2. Мы создаем функцию 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 соответственно.
  3. Оператор Bear.prototype = Object.create(Animal.prototype) задает прототип Bear.prototype как Animal.prototype. Таким образом, все свойства и методы, определенные в Animal.prototype, доступны для объектов, расположенных дальше по цепочке прототипов, включая экземпляры конструктора Bear (например, baloo).
  4. Добавляем метод growl в Bear.prototype.
  5. Мы используем конструктор Bear для создания объекта baloo.
  6. Звоним baloo.walk. Когда мы это делаем, среда выполнения JavaScript проверяет, имеет ли baloo свойство walk. Это не. Поэтому он перемещается вверх по цепочке прототипов и проверяет, имеет ли Bear.prototype свойство walk. Это не. Таким образом, он еще раз перемещается вверх по цепочке прототипов и проверяет, имеет ли Animal.prototype свойство walk. Среда выполнения выполняет функцию walk, устанавливая this в baloo.
  7. Звоним 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 — это свойство функций-конструкторов, которое определяет объект-прототип, назначаемый объектам, созданным этим конструктором.

Заключение

В этой статье мы рассмотрели, как использовать функции конструктора для создания нескольких объектов одинаковой формы. Мы видели, что:

  1. Используя ключевое слово new перед вызовом функции, мы можем преобразовать его в вызов конструктора. Этот процесс включает в себя создание нового объекта, установку его [[prototype]] в свойство конструктора prototype и выполнение функции с новым объектом в качестве контекста.
  2. Чтобы добавить общие методы к объектам, созданным конструктором, мы определяем их в прототипе конструктора.

Однако вы, вероятно, заметили, что использование функций-конструкторов для создания цепочек прототипов может быть громоздким. Это требует работы со сложными деталями конфигурации прототипа, что увеличивает вероятность внесения ошибок и может привести к необычному поведению, если оно не реализовано идеально (см. Упражнения 6–8).

И именно поэтому мы углубимся в классы JavaScript в части 4 — Прототипное наследование с классами (скоро).

Упражнения

Следующие упражнения предназначены для закрепления вашего понимания того, как использовать функции конструктора для построения цепочки прототипов.

Упражнение 1

Когда вы выполните приведенный ниже код,

  1. Что является прототипом Shape?
  2. На что указывает Shape.prototype?
  3. Что является прототипом Shape.prototype?
function Shape(type) {
  this.type = type;
}

Решение:

  1. Прототипом Shape является Function.prototype, так как Shape — это функция
  2. Shape.prototype указывает на вновь созданный объект
  3. Прототипом Shape.prototype (нового объекта) является Object.prototype.

Упражнение 2

Когда вы выполните приведенный ниже код,

  1. Что является прототипом emma?
  2. Каков прототип прототипа Эммы?
  3. Что является прототипом Employee?
  4. На что указывает 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'));

Решение:

  1. Прототипом emma является Employee.prototype
  2. Прототип прототипа Эммы (Employee.prototype) — Object.prototype.
  3. Прототипом Employee является Function.prototype
  4. 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' }
  1. Мы никогда не определяли метод call для Employee, почему мы не получаем TypeError: Employee.call is not a function?
  2. Каков прототип объекта emma?

Решение:

console.log(Employee.hasOwnProperty('call')); // false
console.log(Function.prototype.hasOwnProperty('call')); // true
  1. Поскольку Employee — это функция, ее прототип — Function.prototype. Метод call существует на Function.prototype.
    Напомним из части 1, при попытке доступа к свойству объекта поиск этого свойства выходит за пределы самого объекта. Он продолжается до прототипа объекта, прототипа прототипа и так далее. В этом случае исполняющая среда JavaScript увидела, что Employee не имеет функции call , поэтому просмотрела цепочку прототипов и нашла call в Function.prototype. В результате он выполнил метод call из прототипа.
  2. Прототипом объекта 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 делает следующее:

  1. Создает новый объект. Для удобства обозначим его как newObj.
  2. Связывает этот контекст с newObj
  3. Создает еще один новый объект. Для удобства обозначим его как newProto.
  4. Устанавливает прототип newObj в newProto
  5. Устанавливает прототип newProto в Employee.prototype
  6. Если функция НЕ возвращает ничего, возвращает только что созданный объект 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

  1. Напишите конструктор Employee, который инициализирует имя и зарплату из аргументов.
  2. Все сотрудники должны иметь возможность requestRaise. Сотрудник, который просит о повышении, говорит: «Меня зовут ‹имя›. Мне нужно повышение»
  3. Менеджер – это тип работника. Напишите конструктор менеджера, который может promoteEmployee. Продвижение сотрудника увеличивает заработную плату этого сотрудника. Менеджеры не могут повышать свою зарплату или зарплату другого менеджера.
  4. Разработчик — это тип наемного работника. Разработчики должны иметь возможность code.
  5. Фронтенд-разработчик — это тип разработчика. Интерфейсный разработчик, который кодирует, должен сказать: «Я создаю интерфейс».
  6. Бэкенд-разработчик — это тип разработчика. Бэкенд-разработчик, который кодирует, должен сказать: «Я создаю бэкэнд».

Протестируйте свой код с помощью следующего

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);