В истинном смысле JavaScript не является объектно-ориентированным языком и не предоставляет традиционные методы наследования, но он обеспечивает способ наследования прототипа, используя свойства прототипа, которые он обеспечивает для достижения наследования.
Прототип и цепочка прототипа
Прежде чем говорить о прототипе наследования, мы должны сначала поговорить о прототипах и цепях прототипа. В конце концов, это основа для реализации прототипа наследования.
В JavaScript каждая функция имеет прототип прототипа атрибута, указывающий на свой собственный прототип, а объект, созданный этой функцией, также имеет атрибут __proto__, указывающий на этот прототип. Прототип функции является объектом, поэтому этот объект также будет иметь __proto__, указывающий на свой собственный прототип, так что он будет проходить более глубокий слой по слою до прототипа объекта, тем самым образуя прототипную цепь. Следующий рисунок объясняет взаимосвязь между прототипами и прототипами цепей в JavaScript.
Каждая функция является объектом, созданным функцией функции, поэтому каждая функция также имеет атрибут __proto__, указывающий на прототип функции функции. Здесь следует отметить, что то, что действительно формирует цепочку прототипа, является атрибутом __proto__ каждого объекта, а не атрибута прототипа функции, который очень важен.
Прототип наследование
Базовый режим
Кода -копия выглядит следующим образом:
var parent = function () {
this.name = 'parent';
};
Parent.prototype.getName = function () {
вернуть это. name;
};
Parent.prototype.obj = {a: 1};
var Child = function () {
this.name = 'ребенок';
};
Child.prototype = new Parent ();
var parent = new Parent ();
var Child = new Child ();
console.log (parent.getName ()); // родитель
console.log (child.getName ()); //ребенок
Это самый простой способ реализации прототипа наследования, непосредственно присваивая объект родительского класса прототипу конструктора подкласса, так что объекты подкласса могут получить доступ к свойствам в родительском классе и прототипе конструктора родительского класса. Диаграмма наследования прототипа этого метода следующая:
Преимущества этого метода очевидны, реализация очень проста и не требует никаких специальных операций; Недостатки также очевидны. Если подкласс должен выполнять те же действия инициализации, что и в конструкторе родительского класса, то вам нужно повторить операции в родительском классе в конструкторе подкласса:
Кода -копия выглядит следующим образом:
var parent = function (name) {
this.name = имя || «родитель»;
};
Parent.prototype.getName = function () {
вернуть это. name;
};
Parent.prototype.obj = {a: 1};
var Child = function (name) {
this.name = имя || 'ребенок' ;
};
Child.prototype = new Parent ();
var parent = new Parent ('myParent');
var Child = новый ребенок ('mychild');
console.log (parent.getName ()); // myParent
console.log (child.getName ()); // mychild
В вышеупомянутой ситуации это требует только инициализировать атрибут имени. Если работа по инициализации продолжает увеличиваться, этот метод очень неудобен. Поэтому есть способ улучшить следующее.
Заимствовать конструктор
Кода -копия выглядит следующим образом:
var parent = function (name) {
this.name = имя || «родитель»;
};
Parent.prototype.getName = function () {
вернуть это. name;
};
Parent.prototype.obj = {a: 1};
var Child = function (name) {
Parent.apply (это, аргументы);
};
Child.prototype = new Parent ();
var parent = new Parent ('myParent');
var Child = новый ребенок ('mychild');
console.log (parent.getName ()); // myParent
console.log (child.getName ()); // mychild
Приведенный выше метод выполняет ту же работу инициализации, применяя вызов к конструктору родительского класса в конструкторе подкласса, так что независимо от того, сколько работы инициализации выполняется в родительском классе, подкласс может выполнять одну и ту же работу по инициализации. Тем не менее, есть еще одна проблема с вышеуказанной реализацией. Конструктор родительского класса был выполнен дважды, один раз в конструкторе подкласса, и один раз в прототипе подкласса это много избыточно, поэтому нам нужно сделать улучшение:
Кода -копия выглядит следующим образом:
var parent = function (name) {
this.name = имя || «родитель»;
};
Parent.prototype.getName = function () {
вернуть это. name;
};
Parent.prototype.obj = {a: 1};
var Child = function (name) {
Parent.apply (это, аргументы);
};
Child.prototype = parent.prototype;
var parent = new Parent ('myParent');
var Child = новый ребенок ('mychild');
console.log (parent.getName ()); // myParent
console.log (child.getName ()); // mychild
Таким образом, нам нужно выполнить конструктор родительского класса только один раз в конструкторе подкласса, и в то же время мы можем наследовать свойства в прототипе родительского класса. Это в большей степени соответствует первоначальному намерению прототипа, который состоит в том, чтобы поместить контент, который необходимо использовать в прототипе, и мы только наследуем многократный контент в прототипе. Диаграмма прототипа приведенного выше метода заключается в следующем:
Режим временного конструктора (режим святого Грааля)
Есть еще проблема с версией, которая позаимствовала шаблон конструктора выше. Он непосредственно присваивает прототип родительского класса прототипу подкласса, который будет вызывать проблему, то есть, если прототип подкласса будет изменен, модификация также повлияет на прототип родительского класса, а затем повлияет на объект родительского класса. Это определенно не то, что все надеются увидеть. Чтобы решить эту проблему, доступен временный шаблон конструктора.
Кода -копия выглядит следующим образом:
var parent = function (name) {
this.name = имя || «родитель»;
};
Parent.prototype.getName = function () {
вернуть это. name;
};
Parent.prototype.obj = {a: 1};
var Child = function (name) {
Parent.apply (это, аргументы);
};
var f = new function () {};
F.prototype = parent.prototype;
Child.prototype = new f ();
var parent = new Parent ('myParent');
var Child = новый ребенок ('mychild');
console.log (parent.getName ()); // myParent
console.log (child.getName ()); // mychild
Диаграмма наследования прототипа этого метода следующая:
Легко увидеть, что, добавив временный конструктор F между прототипом родительского класса и прототипом класса дочернего класса, связь между прототипом класса дочернего класса и прототипом родительского класса отключается, чтобы прототип родительского класса не был затронут, когда прототип ребенка класса дочернего класса будет изменен.
Мой метод
Режим Святого Грааля заканчивается «режим Javascript», но независимо от того, какой метод выше, есть проблема, которую нелегко обнаружить. Вы можете видеть, что я добавил буквальный атрибут объекта OBJ к свойству прототипа «родителя», но это никогда не было полезно. Давайте посмотрим на следующую ситуацию, основанную на режиме Святого Грааля:
Кода -копия выглядит следующим образом:
var parent = function (name) {
this.name = имя || «родитель»;
};
Parent.prototype.getName = function () {
вернуть это. name;
};
Parent.prototype.obj = {a: 1};
var Child = function (name) {
Parent.apply (это, аргументы);
};
var f = new function () {};
F.prototype = parent.prototype;
Child.prototype = new f ();
var parent = new Parent ('myParent');
var Child = новый ребенок ('mychild');
console.log (Child.obj.a); // 1
console.log (parent.obj.a); // 1
Child.obj.a = 2;
console.log (Child.obj.a); // 2
console.log (parent.obj.a); // 2
В приведенном выше случае, когда я изменяю детский объект obj.a, также будет изменен OBJ.A в прототипе родительского класса, что вызовет ту же проблему, что и общий прототип. Это происходит потому, что при доступе к child.obj.a мы будем следовать цепочке прототипа и найдем прототип родительского класса, затем найдем атрибут OBJ, а затем изменим Obj.a. Давайте посмотрим на следующую ситуацию:
Кода -копия выглядит следующим образом:
var parent = function (name) {
this.name = имя || «родитель»;
};
Parent.prototype.getName = function () {
вернуть это. name;
};
Parent.prototype.obj = {a: 1};
var Child = function (name) {
Parent.apply (это, аргументы);
};
var f = new function () {};
F.prototype = parent.prototype;
Child.prototype = new f ();
var parent = new Parent ('myParent');
var Child = новый ребенок ('mychild');
console.log (Child.obj.a); // 1
console.log (parent.obj.a); // 1
Child.obj.a = 2;
console.log (Child.obj.a); // 2
console.log (parent.obj.a); // 2
Здесь есть ключевая проблема. Когда объект обращается к свойствам в прототипе, свойства в прототипе только для чтения к объекту. То есть дочерний объект может читать объект OBJ, но ссылка на объект OBJ в прототипе не может быть изменена. Следовательно, когда ребенок модифицирует OBJ, это не повлияет на OBJ в прототипе. Он просто добавляет атрибут OBJ к своему собственному объекту, перезаписывая атрибут OBJ в родительском прототипе. Когда дочерний объект изменяет obj.a, сначала читает ссылку на OBJ в прототипе. В настоящее время, child.obj и parent.prototype.obj указывают на тот же объект, поэтому модификация obj.a ребенка повлияет на значение родителя. Метод наследования в гнездовании $ в AngularJS реализуется путем моделирования прототипа наследования в Javasript.
Согласно вышеуказанному описанию, до тех пор, пока прототип, доступный к объекту подкласса, такой же, как и прототип родительского класса, приведенная выше ситуация произойдет. Поэтому мы можем скопировать прототип родительского класса, а затем назначить его на прототип подкласса. Таким образом, когда подкласс изменяет свойства в прототипе, он только изменяет копию прототипа родительского класса и не будет влиять на прототип родительского класса. Конкретная реализация заключается в следующем:
Кода -копия выглядит следующим образом:
var deepClone = function (source, target) {
источник = источник || {};
var tostr = object.prototype.tostring,
arrstr = '[object array]';
для (var i в источнике) {
if (source.hashownproperty (i)) {
var item = source [i];
if (typeof item === 'Object') {
Target [i] = (toStr.Apply (item) .tolowerCase () === arrstr): []? {};
DeepClone (Item, Target [i]);
}еще{
DeepClone (Item, Target [i]);
}
}
}
вернуть цель;
};
var parent = function (name) {
this.name = имя || «родитель»;
};
Parent.prototype.getName = function () {
вернуть это. name;
};
Parent.prototype.obj = {a: '1'};
var Child = function (name) {
Parent.apply (это, аргументы);
};
Child.prototype = deepClone (parent.prototype);
var Child = new Child ('ребенок');
var parent = new Parent ('parent');
console.log (Child.obj.a); // 1
console.log (parent.obj.a); // 1
child.obj.a = '2';
console.log (Child.obj.a); // 2
console.log (parent.obj.a); // 1
Основываясь на всех вышеупомянутых соображениях, конкретная реализация наследования JavaScript заключается в следующем. Только когда рассмотрим функции ребенка и родителя:
Кода -копия выглядит следующим образом:
var deepClone = function (source, target) {
источник = источник || {};
var tostr = object.prototype.tostring,
arrstr = '[object array]';
для (var i в источнике) {
if (source.hashownproperty (i)) {
var item = source [i];
if (typeof item === 'Object') {
Target [i] = (toStr.Apply (item) .tolowerCase () === arrstr): []? {};
DeepClone (Item, Target [i]);
}еще{
DeepClone (Item, Target [i]);
}
}
}
вернуть цель;
};
var extend = function (родитель, ребенок) {
Ребенок = ребенок || function () {};
if (родитель === не определен)
Возвращение ребенка;
// позаимствовано конструктор родительского класса
Child = function () {
Parent.apply (это, аргумент);
};
// наследуйте прототип родительского класса через глубокую копию
Child.prototype = deepClone (parent.prototype);
// сбросить атрибут конструктора
Child.prototype.constructor = ребенок;
};
Суммировать
Сказав так много, на самом деле, реализация наследования в JavaScript очень гибко и разнообразно, и лучшего метода нет. Различные методы наследования должны быть реализованы в соответствии с различными потребностями. Самое главное, чтобы понять принцип реализации наследования в JavaScript, то есть проблема прототипов и цепочек прототипов. Пока вы понимаете это, вы можете легко реализовать наследование самостоятельно.