Модераторы: Sardar
  

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> Работа с классами, наследование, traits, разбираем prototype.js > base.js 
:(
    Опции темы
Sardar
Дата 30.7.2006, 20:40 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Бегун
****


Профиль
Группа: Модератор
Сообщений: 6986
Регистрация: 19.4.2002
Где: Нидерланды, Groni ngen

Репутация: нет
Всего: 317



Одной из основных и, зачастую, "трудно воспринимаемых фишек" библиотеки prototype.js является поддержка "классов". Рассмотрим код:

Код
//prototype.js > base.js
var Class = {
  create: function() {
    return function() { 
      this.initialize.apply(this, arguments);
    }
  }
}

//ваша библиотека
var Test = Class.create();
Test.prototype = { //удобное и наглядное определение класса
    /** конструктор */
    initialize: function(arg1, argN) {
        alert(arg1 + ", "+argN);
        this.value = arg1;
    }, 
    
    /** публичный метод */
    method: function(arg) {
        alert(this.value+", "+arg);
        this.value = arg;
    }
}

var a = new Test("Vingrad", "test"); //alert( Vingrad, test ) 
a.method("ViJio"); //alert( Vingrad, ViJio )


Как это использовать думаю сразу стало ясно. Рассмотрим, что же происходит в деталях.

Class является простым литеральным объектом, все литеральные объекты - это Object. У этого объекта определено поле create, в которое положили функцию, задача которой создать closure, который в свою очередь вызывает функцию initialize на объекте, являющимся текущим контекстом вызова.

(* далее по тексту функции и замыкания (closure, анонимные функции) будут разделяться по смыслу, хотя в JavaScript фактически разницы между ними нет - вернее, привычных всем функций нет. *)

Не думаю, что всё стало ясно, потому разберём по шагам smileClass.create() создаёт анонимную функцию:

Код
function() { 
      this.initialize.apply(this, arguments);
}

//убеждаемся
var Test = Class.create();
alert(Test);

Класс Test на самом деле не является идентификатором типа, это просто переменная, хранящая в себе анонимную функцию:
Код
//типов в JS нет, есть функции и их прототипы
var NonTest = Test; //обычно присваивание значения
var b = new NonTest("hello", "world"); // alert( hello, world )
b.method("ura!"); // alert( hello, ura! )
alert(b instanceof Test); //true
alert(a instanceof NonTest); //true
alert(a.method === b.method); //true, === - оператор идентичности

Впрочем, от этого ни холодно, ни жарко - типов в JS нет, следовательно, в "приведении к типу" нет необходимости, следовательно, не нужны свои идентификаторы типов.

Class.create() при каждом вызове возвращает новую анонимную функцию со своим чистым, как слеза, прототипом. Этот прототип можно расширить привычным Test.prototype.fieldName = ... или полностью заменить новым объектом, как это мы сделали раньше. В обоих случая необходимо определение поля initialize, вызываемое конструктором; в нём происходит инициализация объекта. Таким образом, получаем приятный глазу способ "определения класса".

Код
//расширяем прототип, не заменяем его, как в примере ранее
var TestExtended = Class.create();
TestExtended.prototype.initialize = function(arg1, argN) {
    alert(arg1 + ", "+argN);
    this.value = arg1;
}
TestExtended.prototype.method = function(arg) {
    alert(this.value+", "+arg);
    this.value = arg;
}

var c = new TestExtended("keep", "constructor"); //alert( keep, constructor )
c.method("ura"); // alert( keep, ura )
alert(c instanceof TestExtended); //true
alert(c instanceof Object); //true
alert(c instanceof Test); //false - структурного сравнения в JS нет


Минусы подхода с заменой прототипа новым объектом в том, что поле constructor новых объектов, порождённых от "класса", будут указывать не на "класс", а на конструктор объекта, используемого как прототип. Все литеральные объекты (определяются { }) порождаются конструктором Object, следовательно:
Код
alert(a.constructor); //function Object() { [native code] }
var a1 = new a.constructor("Vingrad2", "test2"); //ничего, это new Object()
alert(a1 instanceof Test); //false - опаньки =)

alert(c.constructor); //function() {this.initialize.apply(this, arguments);}
var c1 = new c.constructor("keep2", "constructor2"); //alert( keep2, constructor2 )
alert(c1 instanceof TestExtended); //true - что и ожидалось


Поле constructor у объекта позволяет создать ещё один такой же объект, не зная имени функции конструктора, хотя на практике это практически никогда не используеться.

Итак, вернёмся к самому первому листингу и на словах опишем выполнение кода:
  • создаётся анонимная функция, способная вызвать функцию из поля initialize, которое должно уже существовать, следовательно, должно быть определено в прототипе анонимной функции.
  • прототип анонимной функции заменяется новым литеральным объектом с двумя полями: initialize и method, в полях определения функций (closure).
  • при создании нового объекта конструктором вызывается initialize, передавая ему все аргументы. Вызов Function.apply в данном случае нужен только, чтобы передать аргументы, контекст (this) в любом случае указывает на создаваемый объект.
  • всё, что определено как this.field, будет видно всем функциям, вызванным на создаваемом объекте.
Все "методы и поля" (не забываем, что в JS это одно и тоже, называемое slot) определённые в присваиваемом к прототипу объекте, являются общими для всех объектов, порождённых от "класса". Это в корне отличается от привычного многим:

Код
function Test(arg) {
  this.field = arg;
  var private_field = 90;
  this.method = function() {
    alert(private_field + ", " + this.field);
  }
}
/* в поле this.method будет лежать новый для каждого объекта closure,
 т.е. obj1.method == obj2.method - будет возвращать false. В памяти оба метода будут
 разными объектами, впрочем, зачем память в скриптах-то экономить =) */


Этот подход позволяет объявить "приватные поля класса", не видимые никем, кроме функций определённых в конструкторе. Не буду убеждать, что приватные поля чужды гибкому JS, но всё же, если они вам понадобятся, то определяйте их и методы, которые их пользуют, в конструкторе класса, т.е. в initialize


Вопрос: нужна ли нам эта поддержка в ViJio?
Если нужна, то необходимо выработать стиль написания кода, который позволял безболезненно соединять код написанный методом выше или по старинке определяя конструктор и прототип "самому". Физически разницы почти нет, есть только различия в синтаксисе.  

Это сообщение отредактировал(а) Sardar - 31.7.2006, 15:15


--------------------
 Опыт - сын ошибок трудных  © А. С. Пушкин
 Процесс написания своего велосипеда повышает профессиональный уровень программиста. © Opik
 Оценить мои качества можно тут.
PM   Вверх
Sardar
Дата 31.7.2006, 02:20 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Бегун
****


Профиль
Группа: Модератор
Сообщений: 6986
Регистрация: 19.4.2002
Где: Нидерланды, Groni ngen

Репутация: нет
Всего: 317



Теперь подумаем о наследовании.

В языках с "жёсткими типами", таких как Java/C#, многие привыкли видеть некий базовый класс, описывающий основную функциональность, а детали оставляющий на потомков. Либо просто новый класс, расширяющий функциональность уже существующего класса. В любом, случае сохранется:
  • равенство is a класса потомка к базовому классу
  • наследуется часть функционала, часть заменяется, а часть навязана базовым классом (абстрактные методы)
  • логическая структуризация: "это абстрактный класс Simulator, он виден всем, все с ним работают; а это скрытый от всех (не публичный) herschel.hifi.tools.simcontrol.sockipml.SocketSimulatorDriver extends Simulator, который реализует связь с симулятором посредством сетевого коннекта (сокета))". (* Пример взят из проекта "контроллер сети симуляторов", моя текущая работа *)
Это всё хорошо, но бывают случаи когда требуется наследовать просто некий функционал, например функционал списка или любого другого контейнера. Аггрегация (класс содержит членом требуемый объект и его функционал) не всегда является решением, например когда требуется 3-5 малых методов у нескольких классов (в результате они будут иметь разный смысл, но единый код), по дизайну имеющих разные родительские классы. Множественное наследование - это зло, т.к. несёт помимо функционала "левые равенства is a", которые по смыслу никак не подходят.

Не будем углубляться в метапрограммирование и конецепцию traits, просто представим, что можно импортировать некий метод или функцию в класс, тем самым расширив функционал объекта. При этом никаких связей и отношений is a к другим классам, из которых импортируем функционал, не определяются, мы просто даём некоторому объекту пользоваться функцией/методом. Из этого следует:
  • есть некие "классы", которые не обязательно могут порождать объекты, в идеале не могут, т.к. не являются типами вообще.
  • из этих "классов" можно импортировать функционал без каких либо отношений по типу
  • импортированный функционал ведёт себя точно так же, как если бы этот функционал был copy/paste в целевой "класс", но импортирование может происходить в момент работы.
  • импортированный функционал не ограничивает логику целевого класса, можно импортировать (a + b) в два класса, где у одного a и b - это числа, а у другого - высокоуровневые объекты с перегруженными операциями.
Как видим, можно описывать сложные алгоритмы, в то время как типы операндов и вообще их наличие до конца не известны. Теперь упростим это всё до JS:
  • все объекты key => value - пары (slot, далее по тексту поле) + список связей с конструктором, атрибутами полей и прочей инфой.
  • значение поля может быть что угодно; если функция, то поле называют методом. значение поля может поменяться в любой момент.
  • объект может иметь поля как собственные (this), так и общие между всеми из прототипа
  • импортирование фукции - это присваивание конкретному полю функцию как значение.
  • перегрузки операторов нет, поэтому мощь traits сильно ограничивается
Код
 //некий функционал
function sum() {return this.a + this.b;}
function del() {return this.a / this.b;}

//"класс", фактически без функционала
function Test(a, b) {
   this.a = a;
   this.b = b;
}

var test = new Test(48, 4); //объекты
var test2 = new Test(15, 3); //на текущий момент "пустышки"
test.getSum = sum; //импортируем в sum только в объект test
test.constructor.prototype.getDel = del; //импортируем del во все объекты, порождённые от Test

alert(test.getSum()); //52
alert(test2.getSum? test2.getSum(): "not defined"); //not defined
alert(test.getDel()); //12
alert(test2.getDel()); //5

alert(sum()); //NaN, вызов; эквивалентент window.sum() или global.sum() по стандарту ECMA
var a=8, b= 2; //определили реальные переменные для window/global
alert(del()); //4


Как видим, удобно иметь одну функцию для передвижения объектов по экрану (drag&drop например), независимо от того, что это: таблица, слой или слово из текста. Главное, это возможность придать объектам новые способности временно по задаче, а не жёстко кодировать их посредством Movable, Clickable и прочих интерфейсов.

В идеале целевой набор функционала (далее по тексту traits) должен сам "обновлять" требуемый объект. К примеру, имеем список сотрудников в таблице, к каждому имеем анотации отображаемые в боксе внизу экрана. Одним кликом этот бокс можно превратить в плавающее окошко (на малых экранах увлечить по вертикали отображаемую область), причём ни один из компонентов на странице не будет "знать" об этих трансформациях бокса:

Код
//вёрстка бокса уже есть, инициализируем
var annotations = new Console("annotations_console"); //позволяет писать/затирать инфу в div id="annotations_console"
annotations = new PeopleBox(annotations); //форматирует консоль, позволяет работать с понятиями
//"график активности", "план отпуска" и т.д.

/*! все компоненты работают боксом annotations_console не зная что это и как отображается !*/
//... допустим, юзер кликнул "Pop out" - выбросить бокс на новое окно
WindowMananger.popup("annotations_console"); //и бокс стал окном, пока в нём не кликнут "Pop in"
//причём окно может быть реальным окном или плавающим слоем, по вкусу.


Таким образом можно как угодно изменять объект, не очерчивая заранее какие-либо ограничения. Естественно, другие компоненты должны предпологать, что их целевой объект может внезапно измениться. Так, в примере размеры слоя должны при каждой отрисовке опрашивается вновь, а не записывается во внутренние переменные.

Как видим, подход очень гибкий, настолько, как указатель void* в C. Отсюда множество споров, безопаснен ли такой подход и не будет ли он убивать большие проекты. Отметим что на JS вы вряд ли будете писать более 50кб кода, следовательно, больших проектов не будет, и подход очень удобен... в умелых руках smile

Перейдём к prototype.js:
Код
Object.extend = function(destination, source) {
  for (property in source) {
    destination[property] = source[property];
  }
  return destination;
}

Метод в прямом смысле копирует всё из одного объекта в другой. Если целевым объектом будет некий объект, то расширим только его, если же прототип конструктора, то расширим все объекты, порождённые от этого конструктора.

(* Object не является "родительским" для HTML и прочих элементов, поэтому расширять Object.prototype не имеет большого смысла. В Мозилле и Опере есть W3C'сшные HTMLElement и прочие интерфейсы, с помощью которых можно расширять DOM, но ИЕ их пока не поддерживает *)

Просто и красиво, если бы не одно но: разные браузеры по-разному "читают объект" конструкцией for.. in (пинать стандарт ECMA-262), например мозилла листает все поля, в том числе и стандартные toString, унаследованные от Object, в то время как ИЕ только определённые пользователем явно. Предопределённые "методы" являются нативными (native, зашитый в движок, поддерживаемый бинарными либами браузера), переопределение их может приниматься браузером как:
  • глупость - браузер будет игнорировать, ИЕ
  • обычное присваивание, замечено в мозилле
  • ошибка - если поле не имеет определённых внутренних getter/setter, кидает исключение ИЕ
В JS нет итераторов (пинать ECMA), если цикл for...in оборвался исключением, то вернуться и продолжить можно, только отлавливая исключение в for...in блоке:
Код
Object.extend = function(destination, source) {
  var er=[]; //список пропертей, кидающих исключение
  for (property in source) {
      try{
          destination[property] = source[property];
      } catch(e) { er.push(property+"="+(e.description? e.description: e.toString())); };
  }
  //при первом вызове можно отослать эту *важную инфу* аяксом на сервер, после игнорировать.
  if(er.length > 0) reportBrokenProperties(destination, source, er);
  return destination;
}


Другой способ обойти конфликты имён с нативными - использовать префикс в именах, я часто пользую тильду: '~'.
  

Это сообщение отредактировал(а) 12345c - 31.7.2006, 14:48
PM   Вверх
Sardar
Дата 31.7.2006, 02:39 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Бегун
****


Профиль
Группа: Модератор
Сообщений: 6986
Регистрация: 19.4.2002
Где: Нидерланды, Groni ngen

Репутация: нет
Всего: 317



Код в целом был краток, описание многословным, считаю первое плюсом JS, второе можете скинуть на мою болтливость (пытался сделать короче...   smile ).  В base.js находиться много интересного, но рассмотренно оно будет в отдельном топе. В частности разберём и дополним работу с функциями, ибо функции это одна из основ JS, владение ими и вообще умение строить целые выражения есть та самая необходимая основа, от которой будем отталкиваться строя более функциональный код.

Топ открыт для коментариев  smile    


--------------------
 Опыт - сын ошибок трудных  © А. С. Пушкин
 Процесс написания своего велосипеда повышает профессиональный уровень программиста. © Opik
 Оценить мои качества можно тут.
PM   Вверх
vasac
Дата 31.7.2006, 10:53 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1060
Регистрация: 4.5.2006

Репутация: нет
Всего: 36



Осмелюсь высказать свое скромное ИМХО.

JS является слаботипизированным языком с объектной моделью, основанной на прототипах. И в этом кроются основные проблемы для начинающих js-программистов, так как большинство из них приходит к js после изучения жесткотипизированных языков.

И тут нужно определиться для чего нужен данный фреймворк:
— Для удобства начинающих. Тогда можно вводит подобия классов и подгонять js под то чем он не является в принципе. При этом вышеозначенные начинающие смогут на нем создавать относительно сложные вещи даже не понимая самой сути js.
— Либо же он предназначен для создания действительно сложных вещей с использованием всех возможностей js. Но для того чтобы использовать все эти немалые возможности нужно использовать чистый js в том виде в котором он есть, даже если это может кому-то казаться непривычным. 
PM WWW   Вверх
Sardar
Дата 31.7.2006, 11:31 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Бегун
****


Профиль
Группа: Модератор
Сообщений: 6986
Регистрация: 19.4.2002
Где: Нидерланды, Groni ngen

Репутация: нет
Всего: 317



vasac, скорее второе. Писать Java подобное:
Код
function Point(ax, y) {
   this.x = x;
   this.y = y;
   this.getX = function(){ return x; }
   this.getY = function(){ return y; }
   this.setX = function(x){ this.x = x; }
   this.setY = function(y){ this.y = y; }
}

и прочие громоздкие вещи мы не будем. Цель была пояснить что есть Class.create() из prototype.js, как им пользоваться и главное - нужно ли это нам.

Синтаксис удобен, хотя как я говорил раньше из-за проблем с конструктором лучше:

Код
var Test = Class.create();
Object.extend(Test.prototype, {
   initialize: function(... конструктор
   , field_or_method: ...
}

//или упростить, импортируем функционал Number и что нибудь своё
var Test = Class.create().import(Number.prototype).import({
    initialize: ....
});


Синтаксис становиться несколько более привычным для пришедших из class-based ООП языков. Только синтаксис, методология JavaScript будет навязываться что бы научить писать эффективно.  


--------------------
 Опыт - сын ошибок трудных  © А. С. Пушкин
 Процесс написания своего велосипеда повышает профессиональный уровень программиста. © Opik
 Оценить мои качества можно тут.
PM   Вверх
cruelangel
Дата 12.9.2007, 19:26 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 319
Регистрация: 12.9.2007

Репутация: нет
Всего: 8



как я неоднократно отмечал классы совершенно не нужны ни яваскрипту, ни ООП вообще smile

PM MAIL   Вверх
  
Ответ в темуСоздание новой темы Создание опроса
0 Пользователей читают эту тему (0 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | ViJio - фреймворк для JS | Следующая тема »


 




[ Время генерации скрипта: 0.1318 ]   [ Использовано запросов: 22 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.