![]() |
![]() |
![]() |
![]() |
![]() |
![]() ![]() ![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
|
[2 декабря 2004 г.] |
|
Нет ничего более постоянного, чем временное. Народная мудрость |
Если помните, предыдущая набла закончилась полгода назад на том, что при программировании на JavaScript очень неплохо использовать прототипы объектов. Сейчас настало время уточнить данный термин, и заодно показать, как его применять еще эффективнее.
![]() |
В JavaScript каждый объект может иметь ассоциацию с другим |
В Интернете масса литературы, описывающей, что такое
Продемонстрируем «классическое» применение прототипов для реализации наследования в JavaScript.
Листинг 1 |
<pre><script> //** //** Базовый "класс" Car (Машина). //** function Car() { document.writeln("Вызван конструктор Car()."); } // Определяем новый метод "класса" Car. Car.prototype.drive = function() { document.writeln("Вызван Car.drive()"); } //** //** Производный "класс" Zaporojets (Запорожец - тоже Машина). //** function Zaporojets() { document.writeln("Вызван конструктор Zaporojets()."); } // Говорим, что прототип Car - "класс" Zaporojets. Zaporojets.prototype = new Car(); // Определяем новый метод "класса" Zaporojets. Zaporojets.prototype.crack = function() { document.writeln("Вызван Zaporojets.crack()"); } //** //** Основная программа. //** document.writeln("Программа запущена."); // Создаем объект производного "класса" Zaporojets. var vehicle = new Zaporojets(); vehicle.drive(); // (*) вызывается функция базового объекта // Создаем еще один объект того же класса. var other = new Zaporojets(); vehicle.crack(); // функция производного объекта </script></pre> |
Запустив данный пример, можно заметить, что с точки зрения "обычного" ООП результат выглядит несколько необычно:
Листинг 2 |
Вызван конструктор Car(). Программа запущена. Вызван конструктор Zaporojets(). Вызван Car.drive() Вызван конструктор Zaporojets(). Вызван Zaporojets.crack() |
В объектно-ориентированных языках с поддержкой классов (C++, Java, PHP, Perl, Python и т. д.) конструкторы базовых классов обычно вызываются непосредственно внутри конструкторов производных. В JavaScript, как было уже сказано в предыдущей набле, классов нет, есть только объекты. Здесь мы видим совершенно другую картину: конструктор
К сожалению, невозможно задать прототип для некоторого объекта, не создав предварительно объект базового класса. Если вы хотите присвоить
Подобное поведение, конечно, следует из того, как написана программа. Действительно, мы создали объект
![]() |
Вывод: в JavaScript «стандартное» наследование реализуется совсем не так, как в других, «класс-ориентированных» языках программирования. Понятие «конструктора» в |
Как и в дзене, чтобы лучше понять, что собой представляет некоторый термин, иногда бывает полезно уяснить, чем он точно не является. В тридцать девятой набле было сказано, что с каждым объектом (или, что то же самое, хэшем) может быть ассоциирован свой собственный хэш-прототип, просматриваемый интерпретатором в случае отсутствия некоторого свойства текущего объекта. Основываясь на этом, вы могли, обрадовавшись, тут же кинуться писать примерно следующий код:
Листинг 3 |
var obj = { // В самом объекте свойства prop нет. // Зато у него есть прототип... prototype: { // ...в котором данное свойство определяется... prop: 101 } // ...так что в итоге интерпрететор должен считать его. } // Проверим? alert("Значение свойства: " + obj.prop); // What a... |
Увы и ах: данный пример не работает, выдавая: "Значение свойства: undefined". А следовательно, присваивание свойству
Модифицируем теперь код программы:
Листинг 4 |
var obj = { // В самом объекте свойства prop нет. } // Пробуем обратиться к прототипу по-другому. obj.constructor.prototype.prop = 101; // Проверим? alert("Значение свойства: " + obj.prop); // В этом-то объекте свойства быть не должно... var newObj = {}; // пустой хэш alert("Пустота: " + newObj.prop); // А это еще откуда?! |
Результат "Значение свойства: 101" говорит нам, что программа заработала. Однако какой ценой? Свойство
![]() |
Какие выводы можно сделать из примера?
|
Новый объект в JavaScript может быть создан только одним способом: применением оператора
Листинг 5 |
var vehicle = new Car(); // создание нового объекта var hash = {}; // сокращенная запись для new Object() var array = []; // сокращенная запись для new Array() |
Немногие над этим задумываются, но первый оператор примера полностью эквивалентен такому коду:
Листинг 6 |
var vehicle = new window.Car(); // можно и так... var vehicle = new self.Car(); // в браузере self==window |
или даже такому:
Листинг 7 |
var clazz = self.Car; // ссылка на функцию Car() var vehicle = new clazz(); // неявное создание! |
Он также функционально не отличается от следующего примера:
Листинг 8 |
// Создание объекта стандартным способом. self.Car = function() { alert("Car") } var vehicle = new self.Car(); |
Ну что, понравилось? Начали улавливать закономерности? Вот еще примеры:
Листинг 9 |
// Создаем "класс" на лету. var clazz = function() { alert("Динамическая!") } var obj = new clazz(); // А можно и без промежуточной переменной. var obj = new (function() { alert("Wow!") })(); |
Иными словами, справа от
Так вот, после создания объекта интерпретатор присваивает его свойству
Листинг 10 |
// Создаем "класс" на лету. var clazz = function() { alert("Динамическая!") } var obj = new clazz(); alert(obj.constructor == clazz); // выводит true! |
Но позвольте, ведь справа от
Листинг 11 |
var clazz = {}; // clazz.constructor == self.Object var obj = new clazz(); // не работает! |
Что же можно использовать с оператором
Оказывается, что свойство
Теперь вы понимаете, почему JavaScript не рассматривает элемент
![]() |
Итак, вывод: прототипы объектов доступны по цепочке |
Данная набла имеет циклический характер, и сейчас, хорошо понимая, как работают прототипы и конструкторы, мы снова возвращаемся к самому первому примеру. Речь пойдет о создании базового и производных объектов в стиле «класс-ориентированного» программирования.
Итак, перед нами стоят следующие задачи:
Если программировать на «чистом» JavaScript, данные две задачи выливаются в довольно громоздкий код. Чтобы каждый раз его не писать, я предлагаю вам использовать совсем небольшую библиотечку, обеспечивающую удобное применение рассматриваемых подходов. С ее использованием создание производных классов выглядит весьма просто:
Листинг 12 |
<script src="Oop.js"></script> <pre><script> // Базовый "класс". Car = newClass(null, { constructor: function() { document.writeln("Вызван конструктор Car()."); }, drive: function() { document.writeln("Вызван Car.drive()"); } }); // Производный "класс". Zaporojets = newClass(Car, { constructor: function() { document.writeln("Вызван конструктор Zaporojets()."); this.constructor.prototype.constructor.call(this); }, crack: function() { document.writeln("Вызван Zaporojets.crack()"); }, drive: function() { document.writeln("Вызван Zaporojets.drive()"); return this.constructor.prototype.drive.call(this); } }); document.writeln("Программа запущена."); // Создаем объект производного "класса". var vehicle = new Zaporojets(); vehicle.drive(); // вызывается функция базового объекта // Создаем еще один объект того же класса. var vehicle = new Zaporojets(); vehicle.crack(); // функция производного объекта </script></pre> |
Результат работы данного примера кардинально отличается от того, что было приведено в начале наблы.
Листинг 13 |
Программа запущена. Вызван конструктор Zaporojets(). Вызван конструктор Car(). Вызван Zaporojets.drive() Вызван Car.drive() Вызван конструктор Zaporojets(). Вызван конструктор Car(). Вызван Zaporojets.crack() |
Как видите, все работает так, как и ожидает программист на «класс-ориентированном» языке: конструктор
Листинг 14 |
// Вызов конструктора базового объекта. this.constructor.prototype.constructor.call(this); // Вызов переопределенного метода базового объекта. this.constructor.prototype.drive.call(this); // У стандартного метода call() можно указывать // дополнительные аргументы (после this), которые // будут переданы функции-члену объекта. |
Библиотека
Листинг 15 |
// // Create proper-derivable "class". // // Version: 1.2 // function newClass(parent, prop) { // Dynamically create class constructor. var clazz = function() { // Stupid JS need exactly one "operator new" calling for parent // constructor just after class definition. if (clazz.preparing) return delete(clazz.preparing); // Call custom constructor. if (clazz.constr) { this.constructor = clazz; // we need it! clazz.constr.apply(this, arguments); } } clazz.prototype = {}; // no prototype by default if (parent) { parent.preparing = true; clazz.prototype = new parent; clazz.prototype.constructor = parent; clazz.constr = parent; // BY DEFAULT - parent constructor } if (prop) { var cname = "constructor"; for (var k in prop) { if (k != cname) clazz.prototype[k] = prop[k]; } if (prop[cname] && prop[cname] != Object) clazz.constr = prop[cname]; } return clazz; } |
![]() |
|
Дмитрий Котеров |
2 декабря 2004 г.
©1999-2018
|
|
Вернуться к оглавлению |