В отличие от классических объектно-ориентированных языков, JavaScript не реализует наследование на основе классов. Механизм под названием DELEGATION позволяет оценивать свойства объектов в контексте другого объекта, выступающего в качестве прототипа. По сути, объект может наследовать свойства от других объектов.

У прототипа объекта есть собственный прототип, который, в свою очередь, имеет собственный прототип. Этот шаблон продолжается до тех пор, пока не будет достигнут объект, прототип которого имеет значение null - этот объект называется Object. Эта серия отношений называется ЦЕПОЧКОЙ ПРОТОТИПА.

Цепочка прототипов работает следующим образом:

Когда свойство, к которому осуществляется доступ для объекта, недоступно, механизм JavaScript переходит к следующему объекту в цепочке прототипов. Значение свойства возвращается, как только будет найден первый объект с этим свойством. Если поиск продолжается до тех пор, пока не будет найден объект (с нулевым прототипом), возвращается значение undefined.

Object действует как предок почти всех объектов в JavaScript и находится на вершине цепочки прототипов.

Объект, будь то настраиваемый или встроенный, может иметь СОБСТВЕННУЮ СОБСТВЕННОСТЬ, которая существует на объекте, или НАСЛЕДОВАННУЮ СОБСТВЕННОСТЬ, которая существует на другом объекте в его цепочке прототипов.

В JavaScript объекты имеют внутреннее свойство [[prototype]], которое содержит ссылку на их прототипы. В ES2015 методы getPrototypeOf и setPrototypeOf были определены для получения и установки прототипа объекта соответственно.

let obj1 = {
  name: "John",
  greet() {
    return "Hello!"
  }
}
let obj2 = {
  name: "Jane"
}
Object.setPrototypeOf(obj2, obj1);
obj2.greet()
  // => "Hello!"
Object.getPrototypeOf(obj2)
  // => Object { name: "John", greet: greet() }

Альтернативой является нестандартный __proto__, реализованный в большинстве браузеров для доступа к прототипу объекта.

let obj1 = {
  name: "John",
  greet() {
    return "Hello!"
  }
}
let obj2 = {
  name: "Jane"
}
Object.setPrototypeOf(obj2, obj1);
console.log(obj2.greet())
   // => "Hello!"
console.log(obj2.__proto__)
   // => Object { name: "John", greet: greet() }

Прямо из коробки каждая встроенная цепочка прототипов заканчивается Object, для которого, как я упоминал ранее, для прототипа установлено значение null.

Вот примеры некоторых встроенных цепочек прототипов:

[] --> Array.prototype --> Object.prototype --> null 
"hello" --> String.prototype --> Object.prototype --> null
34 --> Number.prototype --> Object.prototype --> null 
{} --> Object.prototype --> null
true --> Boolean.prototype --> Object.prototype --> null
function(){} --> Function.prototype --> Object.prototype --> null

При создании объектов существуют методы, которые позволяют настраивать прототип объекта, тем самым создавая настраиваемую цепочку прототипов.

  1. Object.setPrototypeOf
let objA = {
  a: 10
}
let objB = {
  b: 20
}
console.log(objB.__proto__) // => Object { ... }
// objB --> Object.prototype --> null

Object.setPrototypeOf(objB, objA)
// objB --> objA.prototype -> Object.prototype --> null
console.log(objB.__proto__) // => Object { a: 10 }

Позвольте мне разобрать этот пример. Для объектных литералов по умолчанию задан прототип Object.prototype - это показано в первой цепочке прототипов.

objB --> Object.prototype --> null

Object.setPrototypeOf позволяет установить objB.prototype в objA, и это создает новую цепочку прототипов

objB --> objA.prototype -> Object.prototype --> null

objB.__proto__ дает разные объекты-прототипы до и после того, как новый Object.setPrototypeOf устанавливает objB.prototype в objA.

2) Конструктор

Конструктор в JavaScript - это просто объект функции, который можно вызвать с помощью оператора new. Он возвращает объект с prototype, установленным в prototype конструктора. Новый оператор - это просто синтаксический сахар. Чтобы понять, как устанавливается прототип возвращаемого объекта, мы должны взглянуть на то, что происходит за сценой, когда конструктор вызывается с new.

Этот:

function Car (model) {
  this.model = model;
}
var car = new Car('Toyota'); // Car { model: 'Toyota' }

Такой же как:

function Car (model) {
  this.model = model;
}
var car = Object.create(Car.prototype);
Car.call(car, 'Toyota');
console.log(car); // Car { model: 'Toyota' }

По сути, оператор new устанавливает для ссылки [[prototype]] вновь созданного объекта экземпляра значение Car.prototype.

3) Object.create

Вы можете использовать Object.create, чтобы явно установить prototype только что созданного объекта. Первый аргумент указывает, какой объект установить как prototype для вновь созданного объекта. Есть второй необязательный аргумент, который является дескриптором свойства для нового объекта; мы не будем здесь вдаваться в подробности, но это сама по себе интересная тема. Ниже показано, как использовать Object.create объект с настраиваемой цепочкой прототипов:

var objX = {a: 20};
var objY = Object.create(objY);
console.log(objY.__proto__); // { a:20 }
console.log(objY.a); // 20

Вместо цепочки прототипов по умолчанию Object.create () позволяет нам установить объект, который будет использоваться в качестве объекта-прототипа для objY.

4) Синтаксис класса

Если вы когда-либо использовали или пробовали использовать классический объектно-ориентированный язык (ООП), такой как Java, Ruby и т. Д., Ключевое слово class будет иметь интуитивный смысл. Логично предположить, что это введение в ES6 было сделано для облегчения перехода на JavaScript с языков ООП.

При этом ключевое слово class - это просто синтаксический сахар. За кулисами наследование по-прежнему основано на прототипах.

class Car {
  constructor(make, model) {
    this.make = make;
    this.model = model;
  }
}
var c = new Car('Toyota', 'Camry')
console.log(c.__proto__); // { constructor: Car() }

С помощью такого транспилятора, как Babel, вы можете взглянуть на обычный JavaScript, который делает возможным использование ключевого слова class.

Существует множество книг и блогов, в которых более подробно рассматривается прототипное наследование. Надеюсь, этот пост облегчит понимание концепции и поможет оценить одну из вещей, которые делают JavaScript по-настоящему интересным языком.