Всегда помните, что мы хотим наследовать свойства и методы от родителя, это все, что нам нужно для реализации наследования.
Наследование через цепочку прототипов
мы знакомы с цепочкой прототипов, поэтому, если мы хотим наследовать от какого-либо родителя, все, что нам нужно сделать, это соединиться с цепочкой родительских прототипов.
function Coin(){
this.type = "finance"
this.keywords = [1,4,7]
}
function BitCoin(){
this.name = "bitcoin"
}
BitCoin.prototype = new Coin();
let b1 = new BitCoin();
let b2 = new BitCoin();
console.log(b1.keywords) // [1,4,7]
console.log(b1.type) // finance
console.log(b2.keywords) // [1,4,7]
console.log(b2.type) // finance
мы унаследовали свойства, но проблема уже есть. b1, b2 имеют одинаковые ключевые слова:
b1.keywords.push(10); console.log(b2.keywords) // [1,4,7,10]
да, у нас есть все свойства, но все экземпляры имеют одну и ту же ссылку на любые свойства ссылочного типа, это не сработает. Как мы подходим к этому? сделать мелкую копию? или просто создать новый?
Наследование через конструктор
мы можем попытаться создать новый каждый раз.
function Coin(){
this.type = "finance"
this.keywords = [1,4,7]
}
function BitCoin(){
Coin.call(this);
this.name = "bitcoin"
}
let b1 = new BitCoin();
let b2 = new BitCoin();
console.log(b1.keywords)
console.log(b1.type)
console.log(b2.keywords)
console.log(b2.type)
b1.keywords.push(10);
console.log(b2.keywords) // [1,4,7]
Здесь мы вызываем Coin (родительскую функцию) в рамках BitCoin. «Это» каждый раз указывает на вновь созданный экземпляр BitCoin, поэтому он каждый раз создает новый объект с новым полем ключевых слов каждый раз.
Круто, но опять же, нам нужно наследовать не только свойства, но и методы.
что, если у Coin есть метод на прототипе, Coin.call не имеет к нему доступа.
Coin.prototype.trade = () => {console.log("coins for trade.")}
Что ж, но цепочка прототипов может помочь вам в этом, кажется хорошей идеей объединить эти две реализации.
Наследование через цепочки прототипов и конструктор в сочетании
function Coin(){
this.type = "finance"
this.keywords = [1,4,7]
}
Coin.prototype.trade = () => {console.log("coins for trade.")}
function BitCoin(){
Coin.call(this);
this.name = "bitcoin"
}
BitCoin.prototype = new Coin();
let b1 = new BitCoin();
let b2 = new BitCoin();
console.log(b1.keywords)
console.log(b1.type)
console.log(b2.keywords)
console.log(b2.type)
b1.trade() //coins for trade
b1.keywords.push(10);
console.log(b2.keywords)
Собственно, этого уже достаточно. Однако для того, чтобы объединить их, мы вызываем конструктор Parent два раза, так что в основном это пустая трата вычислительной мощности. Так можем ли мы улучшить больше?
Наследование с Object.create()
То, что мы сделали, было очень «традиционным» наследованием ООП, делая наследование с использованием концепции «класса». Ну, у JS нет класса, JS использует функцию, чтобы имитировать то, что класс делает, как это делал приведенный выше код. что, если мы сделаем это способом JS, а не способом «Java»?
В JS все является объектом, хотя это и не точно, но имеет смысл.
так что насчет этого?
let parent = {
name:"Dave",
getName: function(){
console.log(this.name)
}
}
let child = Object.create(parent);
console.log(child.getName()); // Dave
мы достигли нашей цели, у потомка есть свойства и методы родителя, вернемся к первому предложению этой статьи: Всегда помните, что мы хотим наследовать свойства и методы от родителя, это все, что нам нужно для реализации наследования. >
Но родитель и дочерний объект — это объекты, они экземпляры, имеет ли это значение? вы можете попробовать приведенный ниже код, ребенок может иметь свое собственное имя и наследовать функцию getName для отображения своего собственного имени, он может иметь свой собственный метод getFriends, так что для меня это пока имеет смысл.
let parent = {
name:"Dave",
getName: function(){
console.log(this.name)
}
}
let child = Object.create(parent);
child.name = "John";
child.getFriends = function(){}
child.getName();
Это может быть особым случаем для JS, но у ребенка есть свойства родителя, и он может выполнять метод, который есть у родителя, тогда это имеет смысл для меня.
Идеальная реализация наследования?
Кто-то должен сказать, что приведенный выше код просто обман, а то, что на самом деле делает Object.create(), является поверхностной копией.
Цитата MDN: метод
Object.create()создает новый объект, используя существующий объект в качестве прототипа вновь созданного объекта.
Я признаю, что то, что делает Object.create(), является поверхностной копией. Я считаю, что в JS есть два типа наследования, вам просто нужно «думать иначе». Один – способ Функция, второй – способ Объект.
Функциональный способ: он фокусируется на стороне конструктора, функции действуют как «классы», сами являются конструкторами, как Coin, BitCoin. Они используют «новый» для создания экземпляров. И экземпляры наследуются от конструкторов.
Объектный способ: фокусируется на объектной стороне, нет «нового» или «функции». Экземпляр наследуется от объекта.
Однако это ничего не решает, даже если вы используете Object.create(), у него все еще есть проблемы со ссылками. вы знаете, мелкие копии!
Хорошо, как насчет того, чтобы все совместить?
используйте вызов для создания копий (свойств), используйте Object.create() для копирования прототипа (методов).
ну, так как прототип - это сам объект. Мы используем способ «Объект» для наследования от прототипа, способ «Функция» для наследования всех свойств. Затем мы можем сохранить вызов конструктора. См. код ниже.
Ссылка ниже объясняет, что Object.create() не выполняет функцию конструктора, а ключевое слово «new» выполняет.
function Super(name){
this.name = name;
this.colors = ["red","blue"];
}
Super.prototype.sayHi = function(){
console.log(this.name);
}
function Sub(name, age){
Super.call(this, name)
this.age = age;
}
inherit(Sub, Super);
var person = new Sub("Dave", 26);
function inherit(Sub, Super){
Sub.prototype = Object.create(Super.prototype);
Sub.prototype.constructor = Sub;
}
console.log(Sub.prototype.colors)
console.log(person.colors)
person.sayHi()
Таким образом, функция наследования помогает нам получить все методы, которые нам нужно наследовать, не забудьте установить конструктор обратно в Sub. Super.call поможет нам получить все свойства. Вот и все!
Хорошее объяснение ниже, почему мы должны установить конструктор обратно в Sub.
ES6
Вы можете использовать «класс» «extend»… и в ES6. Пока ваш интервьюер в порядке. Но под капотом почти то же самое.
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a noise.`);
}
}
class Dog extends Animal {
constructor(name) {
super(name); // call the super class constructor and pass in the name parameter
}
speak() {
console.log(`${this.name} barks.`);
}
}
let d = new Dog('Mitzie');
d.speak(); // Mitzie barks.
И на самом деле большинство браузеров уже поддерживают class в JS, так почему бы и нет?