Моя попытка проникнуться прототипами в JavaScript. Что из этого получилось, и стоит ли развиваться в данном направлении?
Статья состоит из объяснения прототипов в JavaScript на примерах, затем я рассказываю о своей попытке углубиться и сделать набросок относительно сложного VC каркаса, в конце я задам и предложу ответить на философский вопрос: «Чистый прототипно-ориентированный или объектно-ориентированный подход с применением классов?».
Статья полна субъективными рассуждениями, не претендующими называться экспертными, но тем не менее скорей всего будут полезна тем, кто идет по пути к использованию прототипов.
Что такое прототипы на примерах
В распространенной концепции объектно-ориентированного программирования классами, мы создаем новый класс при помощи: функции-конструктора, далее описываем необходимые свойства и методы, создаем новый экземпляр при помощи ключевого слова new
.
Как мы пытаемся делать это в JavaSript:
Пример классический: у нас есть базовый класс File
, реализующий начальный набор свойств и метод File.render
для вывода содержимого файла в определенном виде.
Нам необходимо создать два класса-наследника, в которых будет переопределен метод render
и определено свойство extension
, то есть мы будем иметь еще два специальных класса для отображения PDF и TXT файлов.
var extend = function(obj, extObj) {
var k, newObj = {};
for (k in obj) {
newObj[k] = obj[k];
}
for (k in extObj) {
newObj[k] = extObj[k];
}
return newObj;
};
var File = function(name, extension) {
this.name = name;
this.extension = extension;
};
File.prototype = {
name: null,
extension: null,
render: function() {
console.log(‘Default file render: ’, this.name);
}
};
PdfFile = function(name) {
this.name = name;
};
PdfFile.prototype = extend(File.prototype, {
extension: ‘pdf’,
render: function() {
console.log(‘PDF file render: ’, this.name);
}
});
TxtFile.prototype = extend(File.prototype, {
extension: ‘txt’,
render: function() {
console.log(‘TXT file render: ’, this.name);
}
});
var txtFileInstance = new TxtFile(‘Name of TXT file’);
var pdfFileInstance = new PdfFile(‘Name of PDF file’);
А теперь взглянем как это можно сделать при помощи прототипов:
Я буду использовать объект Obj
для упрощения инициализации переменных объекта, он не обязателен.
var Obj = {
create: function(values) {
var k;
values = values || {};
for (var k in values) {
values[k] = {
value: values[k],
writable: true,
configurable: true,
enumerable: true
};
}
return Object.create(this, values);
}
};
var File = Obj.create({
name: null,
extension: null,
render: function() {
console.log(‘Default file render: ’, this.name);
}
});
var PdfFile = File.create({
name: null,
extension: ‘pdf’,
render: function() {
console.log(‘PDF file render: ’, this.name);
}
});
var TxtFile = File.create({
name: null,
extension: ‘txt’,
render: function() {
console.log(‘TXT file render: ’, this.name);
}
});
var txtFileInstance = TxtFile.create({
name: ‘Name of TXT file’
});
var pdfFileInstance = PdfFile.create({
name: ‘Name of PDF file’
});
Выглядит немного логичней и естественней, чем создание классов в первом примере. Это можно объяснить фактом, что JavaScript прототипно-ориентированный язык программирования, классы не были предусмотрены намеренно.
В стандарте ECMAScript 6 предлагают конструкцию для создания привычных классов. Я не могу определенно сказать: поможет это или испортит язык, но программистам с устоявшемся классовым
Что происходит в примерах, что общего, что различного?
Общее: в результате в обоих примерах мы получили похожие объекты с одинаковыми данными и аналогичными методами для управления этими данными.
Различное: в первом примере происходит копирование прототипа, что делает прототип «статическим». Любые изменения прототипа в процессе работы не будут отражены в наследниках.
Я полагаю, расход памяти во втором примере будет ниже. В случае использования прототипов, данные объекта-родителя не копируются — объект-наследник хранит лишь ссылку на родителя. Не существующие данные наследник будет брать у родителя.
Из субъективных наблюдений: второй пример кажется более естественным в контексте JavaScript.
Подробнее про прототипы в статье «Нужны ли в JavaScript классы?».
Набросок относительно сложного VC каркаса
После прочтенных статей и экспериментов с прототипами я решил применить на практике полученные знания.
В работе использую Senca Touch, поэтому написал каркас, имеющий схожее API.
Код приложения и живой пример доступны на JS Bin.
Структура получившегося приложения:
- Объект
X
, содержащий базовые объекты- Базовый объект
X.Object
, прототип и точка создания новых объектов - Базовый объект
X.App
- Базовый объект
X.View
- Базовый объект
X.Controller
- Базовый объект
- Объекты приложения
listView
,listController
,app
Приложение представляет из себя простой список UL
и кнопка включающая и выключающая отображение списка.
Объект X.Object
имеет метод X.Object.create
для создания новых объектов-наследников. Другими словами — любой объект приложения является потомком X.Object
.
Объект X.View
реализует обертку для создания DOM элемента, содержащего вложенные объекты X.View
. Метод X.View.render
вызывается рекурсивно у всех вложенных элементов и возвращает корневой DOM элемент.
Объект X.Controller
используется для добавления слушателей к элементам, содержит методы для управления доверенной контроллеру View. Содержит метод X.Controller.render
, который производит рендер View и добавление слушателей, как результат возвращает DOM элемент.
Объект X.App
используется для хранения контейнера в который должны добавляться результат X.Controller.render
, а так же список контроллеров, используемых в приложении. Метод X.App.run
обходит все контроллеры приложения, запуская X.Controller.render
, результат добавляется в контейнер приложения.
Приложение выполняет достаточно простую функцию и в текущем состоянии оно не доказывает преимуществ прототипно-ориентированного подхода, но зато оно позволяет взглянуть на использование прототипов на практике.
В комментариях можно оставить ссылки на проекты, использующие хорошо организованный код с применением прототипно-ориентированного подхода. Буду признателен.
Время подвести черту и задать вопрос
Я считаю, что использование прототипов заставляет программиста взглянуть на организацию кода с другой стороны. Несомненным преимуществом является тот факт, что JavaScript спроектирован, как чисто прототипно-ориентированный язык, поэтому традиционная классовая модель для него избыточна и чужда.
Что думаете вы…
Автор: jMas