Понимание прототипов и прототипного наследования

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

Конструктор:

Вы можете думать о конструкторе как об инициализации вашего объекта; место, где вы можете определить значения по умолчанию. Эти значения по умолчанию можно использовать позже. Это удерживает вас от переписывания кода. Конструктор выглядит так:

function Car(options={}) {
 this.make = options.make || "honda",
 this.color = options.color || "black", 
 this.seats = options.seats || 4
}

Первое, на что я должен обратить внимание, это синтаксис options={}, он просто дает аргументу options значение объекта по умолчанию, если options не передается в функцию-конструктор. Это соглашение ES6, и вы можете узнать о нем больше здесь.

Итак, созданный нами конструктор — это просто функция, которая определяет некоторые атрибуты, а также присваивает всем атрибутам значения по умолчанию. Единственное, на что вам нужно обратить внимание, — это ключевое слово this . Мы собираемся выяснить, на что он указывает чуть позже.

Методы

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

Car.prototype.getCarDescription = function(){
return `This beautiful ${this.color} ${this.make} will be great for    any occasion, capable of carrying up to ${this.seats} passengers`
}

Примечание. В приведенном выше коде используются шаблонные литералы. Подробнее об этом можно узнать здесь

В приведенном выше коде важно отметить ключевое слово this . Это ссылка на следующую и последнюю часть, о которой мы собираемся поговорить: экземпляр. Помните, что в конструкторе мы также использовали this. Это также указывает на то же самое; экземпляр. Давайте посмотрим на это:

Экземпляр

Итак, теперь у нас есть наш конструктор и метод, но что хорошего во всем этом? Что ж, конструктор можно использовать при создании нового объекта. Этот новый объект называется экземпляром конструктора и унаследует все свойства и методы конструктора. Давайте применим это на практике. Мы собираемся создать новую машину, используя конструктор. Это будет наш экземпляр Car.

var tesla = new Car({
 make: "Model S", 
 color: "red"
});
// ==> Car {make: "Model S", color: "red", seats: 4}

Итак, создав новый объект, Тесла, мы присвоили ему марку и цвет. Но мы могли бы сделать это, просто создав обычный объект. Нам нужно заметить, что объект tesla также имеет свойство seats, установленное на 4. Мы не устанавливали его сами, оно просто унаследовано от конструктора Car. Мы получили все методы и свойства от Car. Они унаследованы и переданы нам, и если мы не перезапишем их, как мы сделали с маркой и цветом, мы получим значения по умолчанию. Это действительно мощно.

Но подождите, если мы видим свойства сделать, цвет и места, где находится созданный нами метод? Мы создали getCarDescription, поэтому мы должны иметь возможность его использовать и видеть, верно? Ну, мы можем его использовать, и мы можем его видеть, просто он как бы спрятан.

__прото__

Когда вы вызываете tesla в консоли JS, вы получаете обратно все свойства объекта: сделать, цвет, места, но вы также получите объект с именем __proto__. Это как-то странно. Мы не создавали этот объект, но его легко понять.

Объект __proto__ был создан для нас, когда мы создали экземпляр конструктора Car с новымключевым словом. При создании объекта __proto__ ему были переданы все методы объекта-прототипа Car. Итак, поскольку getCarDescription находится в прототипе Car, он также находится в атрибуте __proto__ tesla! Если вы откроете __proto__ и заглянете внутрь, вы увидите метод getCarDescription.

Есть еще кое-что довольно интересное в объекте __proto__. Вы можете себе представить, что для вызова функции getCarDescription вам нужно написать что-то вроде этого:

tesla.__proto__.getCarDescription()

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

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

tesla.getCarDescription()
//==> "This beautiful red Model S will be great for any occasion, capable of carrying up to 4 passengers"

Если вы запустите приведенный выше код, может показаться удивительным, что он работает. Что здесь происходит? getCarDescription не находится непосредственно в объекте tesla, так как же это работает? Ответ на самом деле не так сложен, как вы думаете.

Когда JavaScript просматривает объект, чтобы найти свойство, он смотрит не только на сам объект. Он также смотрит на объект __proto__. Это похоже на просмотр списка, пока вы не найдете то, что ищете. Итак, когда вы запускаете tesla.getCarDescription(), движок JavaScript ищет getCarDescription на tesla. Очевидно, что его там нет, поэтому он переходит к объекту __proto__ и ищет его там, и, поскольку функция была добавлена ​​при создании tesla, движок находит getCarDescription. Это цепочка прототипов. Если вы сможете понять это, вы овладеете огромной частью языка. Цепочка может быть сколь угодно длинной, с движком javascript, проверяющим объект __proto__ по гигантскому списку, но концепция всегда будет одна и та же.

Как уже говорилось, это фундаментальная концепция для понимания JavaScript. Это не только улучшит ваше собственное программирование, но и поможет вам понять, что происходит в самом языке. Давайте просто использовать простой пример.

Давайте представим, что мы создаем массив, а затем сохраняем этот массив в переменной. Что-то вроде этого:

let ar = [1, 2, 3, 4, 5];

После того, как переменная ar создана, есть множество методов, которые вы можете использовать прямо по умолчанию. Вы можете вызвать join, keys, pop, map, и это лишь некоторые из них. Каждый из этих методов доступен вам благодаря цепочке прототипов. На самом деле это просто работает так:

Array.prototype.map = function(options) { 
//do something with options 
}

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

Итак, с этим знанием есть одна последняя вещь, которую мы должны знать, что мы можем сделать; мы можем добавить наши собственные методы и свойства в конструктор Array, а также в конструктор Object, конструктор Function и конструктор Number. Да, когда вы создаете функцию, объект, число или массив, вы вызываете функцию-конструктор. И мы можем добавлять свойства и методы к этим конструкторам, как мы это делали с Car. Давайте попробуем ниже:

/*********
*create a function that iterates over an array, and then 
*multiplies each item on the array by the number passed 
*to the function
**********/
Array.prototype.multiply = function(num) {
 var container = [];
 for (var i = 0; i < this.length; i++) {
  container.push(this[i] * num);
 }
 return container;
}

В приведенном выше методе ключевое слово this представляет массив, который будет передан методу, также называемому экземпляром. Это показано ниже:

var arr = [1, 4, 6, 3, 2, 5, 6, 8, 9,]; 
arr.multiply(10);
// ==> [10, 40, 60, 30, 20, 50, 60, 80, 90]

Что мы только что сделали, так это создали метод из конструктора Array, который можно использовать в любом экземпляре Array, который вы создаете, точно так же, как push, pop, map и что-то еще. Это очень мощно и выведет ваше программирование на JavaScript на новый уровень.

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