Реализация механизма наследования ECMAScript
- Предыдущая страница Пример механизма наследования
- Следующая страница Уроки по JavaScript на высоком уровне
Реализация механизма наследования
Чтобы реализовать механизм наследования с помощью ECMAScript, можно начать с базового класса, из которого нужно наследовать. Все классы, определенные разработчиками, могут быть использованы в качестве базового класса. Из соображений безопасности локальные классы и классы хоста не могут быть использованы в качестве базового класса, чтобы предотвратить общее доступ к компилированному коду уровня браузера, который может быть использован для злонамеренных атак.
После выбора базового класса можно создать его подкласс. Использование базового класса полностью зависит от вас. В некоторых случаях вы можете создать базовый класс, который нельзя использовать напрямую, и который предназначен только для предоставления общих функций подклассам. В этом случае базовый класс рассматривается как абстрактный класс.
Хотя ECMAScript не так строго определяет абстрактные классы, как другие языки, иногда он действительно создает классы, которые не могут быть использованы. Обычно такие классы называют абстрактными классами.
Созданные подклассы наследуют все свойства и методы суперкласса, включая реализацию конструкторов и методов. Запомните, все свойства и методы являются общими, поэтому подкласс может напрямую обращаться к этим методам. Подкласс также может добавлять новые свойства и методы, которые отсутствуют в суперклассе, или переопределять свойства и методы суперкласса.
Способы наследования
Как и другие функции, ECMAScript реализует наследование не одним, а несколькими способами. Это связано с тем, что механизм наследования в JavaScript не明确规定, а реализуется через имитацию. Это означает, что все детали наследования не полностью обрабатываются интерпретатором. В качестве разработчика, вы имеете право выбирать наиболее подходящий способ наследования.
Ниже мы рассмотрим несколько конкретных способов наследования.
Объектное подменяется
Когда первоначально разрабатывался ECMAScript, не планировалось проектировать объектное подменяется (object masquerading). Это развилось только после того, как разработчики начали понимать, как работают функции, особенно как использовать ключевое слово this в среде функции.
Принцип следующий: конструктор использует ключевое слово this для присвоения всех свойств и методов (т.е. использует способ объявления класса с помощью конструктора). Поскольку конструктор просто функция, можно сделать конструктор ClassA методом ClassB, а затем вызвать его. ClassB получит свойства и методы, определенные в конструкторе ClassA. Например, ClassA и ClassB можно определить следующим образом:
function ClassA(sColor) { this.color = sColor; this.sayColor = function () { alert(this.color); }; } function ClassB(sColor) { }
Помните? Ключевое слово this ссылается на объект, который в настоящее время создает конструктор. Но в этом методе this ссылается на объект, принадлежащий этому методу. Этот принцип заключается в том, чтобы использовать ClassA как обычную функцию для создания механизма наследования, а не как конструктор. Вот как можно реализовать механизм наследования с помощью конструктора ClassB:
function ClassB(sColor) { this.newMethod = ClassA; this.newMethod(sColor); delete this.newMethod; }
В этом фрагменте кода метод newMethod класса ClassA был присвоен (помните, имя функции просто указывает на нее). Затем был вызван этот метод, переданный ему параметром sColor из конструктора класса ClassB. Последняя строка кода удалила ссылку на ClassA, так что впоследствии его больше нельзя будет вызывать.
Все новые свойства и методы должны быть определены после удаления строки кода с новым методом. В противном случае, они могут перезаписать соответствующие свойства и методы суперкласса:
function ClassB(sColor, sName) { this.newMethod = ClassA; this.newMethod(sColor); delete this.newMethod; this.name = sName; this.sayName = function () { alert(this.name); }; }
Чтобы доказать, что предыдущий код работает, можно запустить следующий пример:
var objA = new ClassA("blue"); var objB = new ClassB("red", "John"); objA.sayColor(); //вывод "blue" objB.sayColor(); //вывод "red" objB.sayName(); //вывод "John"
Объектное подменяется может реализовать множественное наследование
Интересно, что объектное подменяется может поддерживать множественное наследование. То есть, один класс может наследовать от нескольких суперклассов. Механизм множественного наследования, представленным в UML, показан на следующем рисунке:

Например, если существует два класса ClassX и ClassY, класс ClassZ хочет наследовать от этих классов, можно использовать следующий код:
function ClassZ() { this.newMethod = ClassX; this.newMethod(); delete this.newMethod; this.newMethod = ClassY; this.newMethod(); delete this.newMethod; }
Существует один недостаток, если у двух классов ClassX и ClassY есть同名ные атрибуты или методы, ClassY имеет более высокий приоритет, потому что он наследуется из класса, который появляется позже. За исключением этой маленькой проблемы,多重ное наследование механизма с помощью объектного обмана легко реализовать.
Из-за популярности этого метода inheritance ECMAScript третьего поколения добавил два метода к объекту Function, namely call() и apply().
Метод call()
Метод call() наиболее похож на классический метод объектного обмана. Первый параметр используется в качестве объекта this. Другие параметры передаются напрямую функции. Например:
function sayColor(sPrefix,sSuffix) { alert(sPrefix + this.color + sSuffix); }; var obj = new Object(); obj.color = "blue"; sayColor.call(obj, "The color is ", "a very nice color indeed.");
В этом примере функция sayColor() определена вне объекта, даже если она не принадлежит любому объекту, можно ссылаться на ключевое слово this. Атрибут color объекта obj равен blue. При вызове метода call() первым параметром является obj, что означает, что этому ключу слова this в функции sayColor() должно быть赋予 значение obj. Вторым и третьим параметрами являются строки. Они соответствуют параметрам sPrefix и sSuffix в функции sayColor(). В конце концов, сообщение "The color is blue, a very nice color indeed." будет отображено.
Чтобы использовать этот метод с методами объектного обмана, которые наследуются, просто замените три первых строки присваивания, вызова и удаления кода:
function ClassB(sColor, sName) { //this.newMethod = ClassA; //this.newMethod(color); //delete this.newMethod; ClassA.call(this, sColor); this.name = sName; this.sayName = function () { alert(this.name); }; }
Здесь нам нужно сделать, чтобы ключевое слово this в ClassA было равно новому созданному объекту ClassB, поэтому this является первым параметром. Вторым параметром sColor для обоих классов является уникальный параметр.
apply() метод
apply() метод имеет два параметра, которые служат объектом для this и массивом параметров, передаваемых функции. Например:
function sayColor(sPrefix,sSuffix) { alert(sPrefix + this.color + sSuffix); }; var obj = new Object(); obj.color = "blue"; sayColor.apply(obj, new Array("The color is ", "a very nice color indeed."));
Этот пример аналогичен предыдущему, только теперь вызывается метод apply(). При вызове метода apply() первым аргументом по-прежнему является obj, что означает, что значение ключевого слова this в функции sayColor() должно быть obj. Вторым аргументом является массив из двух строк, который соответствует параметрам sPrefix и sSuffix в функции sayColor(), и в конечном итоге сгенерированное сообщение все еще будет "The color is blue, a very nice color indeed." и будет отображаться.
Этот метод также используется для замены первых трех строк кода, включающих присваивание, вызов и удаление нового метода:
function ClassB(sColor, sName) { //this.newMethod = ClassA; //this.newMethod(color); //delete this.newMethod; ClassA.apply(this, new Array(sColor)); this.name = sName; this.sayName = function () { alert(this.name); }; }
Таким же образом, первым аргументом по-прежнему является this, а вторым аргументом является массив, содержащий только один элемент color. Можно передать весь объект arguments ClassB в качестве второго аргумента методу apply():
function ClassB(sColor, sName) { //this.newMethod = ClassA; //this.newMethod(color); //delete this.newMethod; ClassA.apply(this, arguments); this.name = sName; this.sayName = function () { alert(this.name); }; }
Конечно, параметры объекта можно передавать только в том случае, если порядок параметров в суперклассе полностью совпадает с порядком параметров в подклассе. Если это не так, необходимо создать отдельный массив, в котором параметры будут расположены в правильном порядке. Кроме того, можно использовать метод call().
Прототипная цепочка (prototype chaining)
Этот вид наследования originally использовался в ECMAScript для прототипной цепочки. В предыдущей главе介绍了 способ определения класса через прототип. Прототипная цепочка расширяет этот способ, реализуя механизм наследования в интересном виде.
Как мы узнали в предыдущей главе, объект prototype является шаблоном, на котором основываются все объекты, которые необходимо инстанцировать. В общем, любые свойства и методы объекта prototype передаются всем экземплярам этого класса. Прототипная цепочка использует эту функцию для реализации механизма наследования.
Если использовать прототипный способ перedefiniruния класса из предыдущего примера, он изменится до следующей формы:
function ClassA() { } ClassA.prototype.color = "blue"; ClassA.prototype.sayColor = function () { alert(this.color); }; function ClassB() { } ClassB.prototype = new ClassA();
Чудесное свойство прототипного способа заключается в выделенном синим цветом коде. Здесь attribute prototype класса ClassB устанавливается в instance класса ClassA. Это интересно, потому что мы хотим, чтобы все свойства и методы ClassA были доступны, но не хотим их перечислять в attribute prototype ClassB. Есть ли лучше способ, чем присвоить instance ClassA attribute prototype?
Внимание:Вызов constructor ClassA без передачи ему параметров. Это стандартное поведение в prototype chain. Убедитесь, что constructor не имеет параметров.
Как и подделка объекта, все свойства и методы subclasses должны出现在 после присвоения attribute prototype,因为在 этом времени все методы будут удалены. Почему? Потому что attribute prototype заменяется новым объектом, и оригинальный объект с методами будет уничтожен. Поэтому, чтобы добавить attribute name и method sayName() к классу ClassB, код будет выглядеть так:
function ClassB() { } ClassB.prototype = new ClassA(); ClassB.prototype.name = ""; ClassB.prototype.sayName = function () { alert(this.name); };
Этот код можно проверить, запустив следующий пример:
var objA = new ClassA(); var objB = new ClassB(); objA.color = "blue"; objB.color = "red"; objB.name = "John"; objA.sayColor(); objB.sayColor(); objB.sayName();
Кроме того, в prototype chain способ работы оператора instanceof также уникален. Для всех экземпляров ClassB instanceof возвращает true для ClassA и ClassB. Например:
var objB = new ClassB(); alert(objB instanceof ClassA); // Вывод "true" alert(objB instanceof ClassB); // Вывод "true"
В мире слабых типов ECMAScript это极其 полезный инструмент, но его нельзя использовать при подделке объекта.
Недостаток prototype chain в том, что он не поддерживает множественное наследование. Запомните, prototype chain будет перезаписывать attribute prototype класса другим типом объекта.
Смешанный способ
Этот способ наследования использует constructor для определения класса, а не прототип. Основная проблема подделки объекта в том, что необходимо использовать constructor, это не лучший выбор. Однако, если использовать prototype chain, то нельзя использовать constructor с параметрами. Как разработчик выбрать? Ответ прост: используйте оба.
В предыдущей главе мы уже рассказывали о том, что лучшим способом создания класса является определение свойств с помощью конструктора, а методов с помощью прототипа. Этот метод также подходит для механизма наследования, с помощью объекта имитируется наследование свойств конструктора, а с помощью прототипной цепочки наследуется метод объекта prototype. Используя эти два метода, можно переписать предыдущий пример, код如下: }}
function ClassA(sColor) { this.color = sColor; } ClassA.prototype.sayColor = function () { alert(this.color); }; function ClassB(sColor, sName) { ClassA.call(this, sColor); this.name = sName; } ClassB.prototype = new ClassA(); ClassB.prototype.sayName = function () { alert(this.name); };
В этом примере механизм наследования реализован двумя выделенными синими строками кода. В первой выделенной строке кода, в конструкторе ClassB, с помощью объекта имитируется наследование свойства sColor класса ClassA. Во второй выделенной строке кода, с помощью прототипной цепочки наследуется метод класса ClassA. Поскольку в этом смешанном методе используется прототипная цепочка, оператор instanceof все еще работает корректно.
Ниже приведен пример, тестирующий этот код:
var objA = new ClassA("blue"); var objB = new ClassB("red", "John"); objA.sayColor(); //вывод "blue" objB.sayColor(); //вывод "red" objB.sayName(); //вывод "John"
- Предыдущая страница Пример механизма наследования
- Следующая страница Уроки по JavaScript на высоком уровне