Статья состоит из объяснения прототипов в 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 спроектирован, как чисто прототипно-ориентированный язык, поэтому традиционная классовая модель для него чужда и избыточна.
Что думаете вы…
ссылка на оригинал статьи http://habrahabr.ru/post/183450/
Добавить комментарий