Прототипно-ориентированный JavaScript и совсем немножко кодинга

в 0:00, , рубрики: javascript, prototype, ооп, ооп js, Программирование, метки: , , ,

Моя попытка проникнуться прототипами в 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

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js