Первоначально опубликовано в blog.shams-nahid.com

Ключевое слово class в Javascript — это синтаксический сахар. Внутри он использует прототипное наследование. Прототипное наследование похоже на классическое наследование Java или Python. Он позволяет объекту получать доступ к методам и свойствам другого объекта.

Помогает

  • Реализовать наследование
  • Избегайте повторяющихся функций
  • Создание инновационных парадигм программирования

И array, и function являются объектами в Javascript. Таким образом, любые object, array и function могут использовать свойства и методы родительской цепочки прототипов object.

Обзор прототипов

Пример 1. Цепочка прототипов объектов

Давайте объявим объект

const obj = {};

Мы найдем base object, если проверим цепочку прототипов.

obj.__proto__

Пример 2. Цепочка прототипов массивов

const array = [];

Здесь мы создали массив. Этот объект массива создается конструктором объекта. Мы можем получить доступ к этим конструкторам и другим методам массива, таким как concat, fill, find, push, pop и т. д., используя следующее:

array.__proto__;

Используя prototypes chain, мы можем получить доступ к базовому объекту, используя следующее:

array.__proto__.__proto__;

Из этого базового объекта мы можем получить доступ к base object свойствам и методам, таким как constructor, hasOwnProperty, toString, valueOf и т. д.

Теперь мы можем использовать метод базового объекта, используя цепочку прототипов. Например, чтобы использовать метод toString из базового объекта, мы можем написать

array.toString();

Поскольку наш массив пуст, он вернет пустую строку.

Пример 3. Цепочка прототипов функций

Давайте создадим метод

function a() {}

Используя цепочку прототипов, мы можем наблюдать native function. native function — это тот, кто создал все остальные functions.

a.__proto__;

Используя prototypes chain, если мы подойдем на шаг ближе, мы найдем base object.

a.__proto__.__proto__;

Создание собственных прототипов

Прежде чем создавать собственные прототипы, мы должны знать,

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

Чтобы создать prototypes-chain, мы можем использовать функцию Object.create().

const human = {
  mortal: true
};
const socrates = Object.create(human);
socrates.age = 80;
console.log(`Age of socrates: ${socrates.age}`);
console.log(
  `Is human a prototypes of socrates: ${human.isPrototypeOf(socrates)}`
);

Это даст нам следующий вывод,

Age of socrates: 80
Is human a prototypes of socrates: true

Прототипы в действии

Момент истины. Мы увидим, как мы можем извлечь выгоду из прототипов.

Давайте создадим двух персонажей,

const dragon = {
  name: 'Tanya',
  fire: true,
  fight() {
    return 5;
  },
  sing() {
    return `I am ${this.name}, the breather of fire`;
  }
};
const lizard = {
  name: 'Kiki',
  fight() {
    return 1;
  }
};

Теперь мы хотим использовать метод sing() для lizard. Но у lizard нет метода sing(). В этом случае мы должны использовать метод dragon объекта sing().

Чтобы использовать метод объектов dragon, мы можем использовать свойство bind.

const singLizard = dragon.sing.bind(lizard);
singLizard();

Это даст результат

I am Kiki, the breather of fire

Теперь, если мы обновим метод dragon объектов sing(), то для пения потребуется свойство fire, то новый объект dragon будет,

const dragon = {
  name: 'Tanya',
  fire: true,
  fight() {
    return 5;
  },
  sing() {
    if (this.fire) {
      return `I am ${this.name}, the breather of fire`;
    }
  }
};

В этом случае, если мы вызовем singLizard(), метод sing не вернет оператор.

Чтобы вернуть оператор, мы должны связать свойство fire для lizard также из объекта dragon.

Чтобы решить эту проблему, вместо привязки мы можем использовать файл prototypes chain.

lizard.__proto__ = dragon;
lizard.sing();

В этом случае, когда метод sing() не будет найден в объекте lizard, он проверит цепочку прототипов и оттуда выполнит метод sing().

После использования prototypes chain, если мы вызовем метод fight() для объекта lizard,

lizard.__proto__ = dragon;
lizard.fight();

Это вернет

1

Потому что этот метод fight уже существует в объекте lizard. Таким образом, он не перейдет к методу prototypes-chain for fight() объекта dragon.

Мы можем, если dragon является прототипом lizard, следующим образом:

dragon.isPrototypeOf(lizard);

Это будет true. А этот isPrototypeOf вышел из base object, тоже с использованием prototype chain.

Свойства прототипов

Свойство 1: прототип не копирует свойства, он наследуется

Для примеров object, array и function мы увидим, что все цепочки прототипов просто унаследовали свойство от объекта-прототипа.

Прототипы Чиан с объектом

Используя предыдущий пример,

const dragon = {
  name: 'Tanya',
  fire: true,
  fight() {
    return 5;
  },
  sing() {
    return `I am ${this.name}, the breather of fire`;
  }
};
const lizard = {
  name: 'Kiki',
  fight() {
    return 1;
  }
};
lizard.__proto__ = dragon;
for (let prop in lizard) {
  console.log(
    `Is ${prop} lizards own property: ${lizard.hasOwnProperty(prop)}`
  );
}

Это даст нам вывод

Is name lizards own property: true
Is fight lizards own property: true
Is fire lizards own property: false
Is sing lizards own property: false

Итак, мы видим, что только name и fight являются собственностью lizard. Оба свойства fire и sing произошли от prototypes-chain.

Поскольку это не копирование свойств, это полезно. Например, если мы используем метод sing объекта dragon в нескольких местах, мы можем использовать один единственный экземпляр. Мы не повторяемся и сохраняем память.

Прототипы Chian с массивом

давайте определим массив.

const myArr = [];

Мы знаем, что массив javascript имеет свойство с именем map. Давай проверим

myArr.hasOwnProperty('map');

Это вернет false. Потому что map проходят через prototypes-chain. Когда мы создаем массив, он создается объектом Array. Используя цепочку prototypes, мы можем получить доступ к Array и проверить существование свойства map.

myArr.__proto__.hasOwnProperty('map');

Это должно вернуть true.

Прототипы Chian с функцией

Давайте создадим function

function myMethod() {}

Мы знаем, что function — это особый тип object в Javascript.

Таким образом, следующий оператор должен возвращать true.

myMethod.hasOwnProperty('call');

Но это вернет false.

Поскольку эти методы call, bind, apply появляются в методе через расширение prototypes-chain.

__proto__ объекта myMethod, связанного с собственным базовым объектом функции prototypes.

Этот объект базовой функции prototypes содержит все следующее:

  • call
  • apply
  • bind
  • __proto__, это указывает на базовый объект Javascript
  • Многое другое …
myMethod.___proto__.hasOwnProperty('call');

Это вернет true.

И это __proto__ цепочки базовой функции prototypes к свойству prototype базового объекта.

Базовый объект __proto__ свойства prototypes указывает на null.

Свойство 2: только функция и базовый объект имеют объект prototype

Давайте создадим объект и функцию и проверим, есть ли объект prototype.

const obj = {};
function myFunc() {}
console.log(obj.hasOwnProperty('prototype'));
console.log(myFunc.hasOwnProperty('prototype'));

Это вернет

false
true

Теперь мы знаем, что базовый объект Javascript Object имеет свойство property.

Object.hasOwnProperty('prototype');

Это вернет true.

На самом деле Object javascript — это функция, а не объект.

Использование прототипов в повседневном программировании

Для меня это самый интересный раздел. Мы рассмотрим несколько реальных сценариев, в которых мы можем реализовать концепцию прототипов.

Добавить новую функциональность в объект Date

Давайте добавим новую функцию с именем lastYear объекта Date, которая будет возвращать год на 1 раньше, чем текущий год.

Date.prototype.lastYear = function () {
  return this.getFullYear() - 1;
};
new Date('1900-10-10').lastYear();

Здесь мы используем ключевое слово function вместо метода arrow, чтобы использовать dynamic-scope вместо lexical-scope.

При использовании ключевого слова function контекстом this является new Date.

Но с arrow method контекстом this является сам arrow method, указанный ниже,

Date.prototype.lastYear = () => {
  return this.getFullYear() - 1;
};
new Date('1900-10-10').lastYear();

Это выдаст ошибку,

TypeError: this.getFullYear is not a function

Управление функциональностью map массива

Мы добавим текст manipulated перед каждым значением массива,

Array.prototype.map = function (args) {
  const newArray = [];
  this.forEach(val => newArray.push(`manipulated ${val}`));
  return newArray;
};
console.log([1, 2, 3].map());

Это даст нам вывод

[ 'manipulated 1', 'manipulated 2', 'manipulated 3' ]

Последние мысли

Прототипы — одна из основных основ JavaScript. Дайте мне знать по любым вопросам. Кроме того, вы можете поделиться своими мыслями о том, как вы используете прототипы в повседневном программировании.

Ссылки: JavaScript: передовые концепции Андрея Негойе.