Первоначально опубликовано в 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: передовые концепции Андрея Негойе.