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

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> Обьекты, прототипы, эффективные интерфейсы, вникаем в тонкости JavaScript 
:(
    Опции темы
Sardar
Дата 30.4.2005, 23:30 (ссылка) |    (голосов:6) Загрузка ... Загрузка ... Быстрая цитата Цитата


Бегун
****


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

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



Предисловие.

Началось всё с этого топа. А что такое прототипы? Многие пытаються увидеть в JS обьектно ориентированный язык, но на самом деле это не так. JavaScript это обьектно протитипный язык програмирования, имеющий свои особенности мышления. Конечно можно за уши притянуть ООП подход к JS, но мы увидим что подобные громоздкие обороты лишни и к счастью не используються. Просто представь себе кучу классов, типов в JS, не динамичное поведение обьектов, жёстко прописанные интерфейсы и прочее...

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

JS простой язык, идеально подходящий для манипулирования существующими обьектами. Такими обьектами могут быть элементы страницы, GUI(XUL) и т.п.

Смысл этого текста научиться думать в обьектно прототипном стиле. Инфы много, я буду часто повторяться дабы вложить и закрепить инфу. Главы буду разбивать по постам. Любое утверждение будет подтверждаться примером.

В этом топе запрещаеться писать отзывы и т.п. Создам для этого одтельный топ smile
Там же можно направлять меня в те темы, которые не ясны, я их буду чётче/шире раскрывать.

nJoy smile


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


Бегун
****


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

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



Начнём с основ, постепенно углублясь в детали.

Всё есть обьекты.
В JS всё есть обьекты. Примитивные типы тоже существуют, для улучшения производительности и не только, но они при случае конвертируются в обьекты. Узнать тип переменной можно ключевым словом typeof.
Код
//метод toString описан в интерфейсе Object, есть у всех обьектов 
var a=new Object();
alert(a.toString()+",  "+(typeof(a)));

var b=34; //примитивный тип число
alert("dec: "+b.toString()+", hex: "+b.toString(16)+", bin: "+b.toString(2)); //dec: 34, hex: 22, bin: 10010
alert(typeof(b));

//функции это тоже обьекты, нет не указатели и т.п. а настоящие обьекты
function test1(arg){alert("test1: "+arg);} //обычное определение
test2=function(arg){alert("test2: "+arg);}; //то же самое, но это уже выражение
test3=new Function("arg", "alert('test3: '+arg);"); //не поддерживается Оперой 6. Позволяет задавать тело функции как строку
alert(typeof(test1));

alert(test1); test1("argument"); //не явный вызов toString
alert(test2.valueOf()); test2("argument"); //явный вызов valueOf, дающий исходник функции
alert(test3.toString()); test3("argument"); //то же самое

//раз функция это обьект, то создадим ей свой метод :)
test1.callMe=function(arg){
  this(arg); //мы же функция =)
};
test1.callMe("проба"); //вызов функции test1: "test1: проба"

Усвоив что всё есть обьекты, мы придём к первому правилу: все обьекты имеют динамически изменяемый интерфейс(читай ниже)
Исключение: у стандартных встроенных в браузер обьектов нельзя подменить методы, т.е. у подобных обьектов(массивы, HTML элементы и т.п) есть свойствo "только для чтения", которое мы, как программисты использовать в наших обьектах не можем. Это всё нативные методы/свойства, выполняющие работу на "низком" уровне.

На заметку: у обьектов есть только свойства smile Методы это тоже свойства обьектов(переменные), в которые записаны обьекты функции. Мы можем менять/добавлять метод как и любое другое свойство. Ссылка this будет указывать на обьект. При вызове функции не в контексте обьекта ссылка this указывает на обьект window. Другими словами все глобальные переменные и функции есть свойства обьекта window(хотя это не логично, хотели сделать обьект global, но у ИЕ заржавело...)

На заметку: во многих языках есть понятие полей обьекта, при изменении которого вызывается функция обработчик(т.н. get/set функции). Изначально нетскейповцы придумали в стандартном интерфейсе Object(а следовательно у всех) методы watch/unwatch, следящие за обращениями к свойству обьекта и вызывающие функции обработчики. Под ИЕ это дело не пошло smile
Мозилла "не оффициально" поддерживает __defineGetter__|__defineSetter__. Пример можно увидеть здесь
ИМXО хорошая и удобная вещь для создания свойств только для чтения. К сожалению сейчас пока нет универсального единого способа сделать свойства обьекта "только для чтения".

Впервые достала фича форума, если время между постами меньше 5 минут, посты сливаються. Мешает...


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


Бегун
****


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

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



Эффективный интерфейс обьекта.
Любой обьект имеет определённый интерфейс - список всех свойств обьекта(выше мы обусловились что методы это свойства/поля обьекта smile ). Интерфейс обьекта может менятся во время выполнения. Мы также уточнили иключение для нативных обьектов из этотого утверждения(зачем и как нам переопределять window.open? smile ).

Теперь поговорим от том как же формируется интерфейс обьекта.
Рекомендуется выспаться, пробежать пару км. для бодрости, выпить кофе smile


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

Исключение: ссылка this и arguments являются личными для каждой функции и не наследуются по контексту.

Код
//в a положим функцию
var a=null;
function test() { //эта функция может быть вызвана как конструктор или просто как функция. резличии увидим ниже.
  this.field="vingrad"; //this может указывать на только что созданный обьект или на глобальный(window)
  var lnk=this; //копия ссылкия на this
  var parg=arguments; //копия копия ссылки на arguments
  
  a=function() { //определяем функцию в контексте test
/* вывод алертов буду приводить в двух вариантов:
*    первый - вызов test как конструктора,
*    второй - вызов test как просто функциия 
*/
    alert(this.field); //undefined, vingrad
    alert(lnk.field); //vingrad, vingrad
    alert(arguments.length); //0, 0
    alert(parg.length); //2, 2
  }
}

var b=new test("dummy", "arg"); //вызов как конструктора с 2 аргументами
//test("dummy", "arg"); //вызов как функция с 2 аргументами
a(); //вызываем созданную функцию

Замечаем что переменные lnkи parg также доступны и ниже по контексту в новой функции. Также видим что this и arguments у каждой функции свои, для имортирования их ниже по контексту, нужно создать на них ссылки с другими именами.

При вызове test как конструктора, в this храниться ссылка на только что созданный обьект. При вызове a() мы вызываем функцию в глобальном контексте, т.е. this в a указывает на window. Поэтому alert(this.field) выдаст undefined.

При вызове test как простую функцию this указывет на window, т.е. мы создаём новую переменную в глобальном контексте с именем field. Как и ранее a вызывается в глобальном контексте, потому field нам доступна.

Важно: из теста видно что мы наследуем контекст, т.е. все переменные с их текущими значениями. При следующем вызове эти переменные поменяют свои значения, но тем не менее в созданной функции они продолжают содержать "старые" значения. На самом деле при каждом вызове test создаётся новая функция a! Которая и содержит актуальные значения. Убедитсься мы в этом можем следующим кодом:
Код
//автоматом создалась а в глобальном
var b=new test("dummy", "arg");
var c=a; //сохранили её в другой переменной
b=new test("dummy", "arg", "arg3"); //автоматом создалась вторая а в глобальном
a(); //смотрим на послендний алерт, количество аргументов: 3
c(); //количество аргументов: 2

alert(c==a); //естественно это две разных функции

Может показаться не эффективным вот так транжирить память, но на самом деле всё работает правильно при правильном проектировании программы smile
На заметку: на самом деле в хорошем интерпретаторе(а плохие быстро умирают smile ) код функции не дублируется, просто разным "копиям" присваивается ссылка на свой контекст, хранящий все "унаследованные" переменные.
Механизм похож на вложенные анонимные классы в Java.

Теперь более реальные примеры, создание "делегатов", например для вставки их как параметры у window.setTimeout. Напомню что set(Timout|Interval) работает только с обьектом функцией. Делегат в нашем случае это функция содержащая сылку на обьект и метод, который вызывается при её вызове.

Код
<div id="dbg"></div><!-- здесь будет нашь вывод -->
<script type="text/javascript">
<!--
function msg(str) {
  document.getElementById("dbg").innerHTML+="<b>"+str+"</b><br />";
}
//литеральный обьект, удобен, ничем не отличается от new Object ;-)
var a={
  test: function() { msg("Object a"); } //в каком контексте эта функция? ;-)
};
//этот синтаксис наверное более привычен...
var b=new Object();
b.test=b_toString;
function b_toString() { msg("Object b");}

//функция возвращает функцию, вызывающую метод обьекта по имени
function createStrDelegate(obj, methodname) {
  return function() { obj[methodname](); } //замечаем что значения аргументов сохраняються индивидуально в контексте каждой новой возвращаемой функции
}
//это не столько вызов метода обьекта, сколько вызов функции в некотором контексте =)
//другими словами можем исполнить любую функцию как метод обьекта. жаль что работает сие с версии JS 1.5
function createApplyDelegate(obj, method) {
  return function() { method.apply(obj, arguments);}
}
//для подтверждения слов выше. попытаемся исполнить в контексте обьекта
function viewToStringMethod() {
  msg("<pre>"+this.test+"</pre>");
}

window.setTimeout(createStrDelegate(a, "test"), 1000);
window.setTimeout(createApplyDelegate(b, b.test), 2000);
window.setTimeout(createApplyDelegate(b, viewToStringMethod), 3000);

//не забываем что в JS нет "тупых" sleep функций, замораживающих тред.
//всё должно работать динамично, не подвисая. другими словами setTimeout
//не остановит исполнение, а запустит новый тред(вернее приостоновит исполнение текущего,
//впрочем это зависит от реализации...), в котором исполнится функция.
msg("Begin"); //поэтому Begin появиться первым
//-->
</script>

Как видим сохранение контекстов в создаваемых функциях очень удобно, когда нужно определять каких то уникальных обработчиков для обьектов. Например для создания обработчиков событий для элементов страницы.

И так в этой точке должно быть ясно что:
  • функции это обьекты
  • функция может быть созданна простым выражением(выражение в синтаксическом понятии expression)
  • как и любое выражение функция может быть создана в любой другой функции(в любом месте)
  • при создании функция наследует текущий контекст "родительской" функции. Все переменные кроме this и arguments наследуються(для ясности: этот механизм работает когда ты создаёшь функции в глобальном(window) контексте и видишь все глобальные переменные smile )
  • каждый раз когда мы определяем функцию, мы создаём новый обьект-функцию. Обычно ссылка на старую функцию перезаписывается, если ссылок больше не остаётся, то обьект удаляется сборщиком мусора.



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


Бегун
****


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

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



Задание интерфейса обьекта в функции(конструкторе).
Теперь мы знаем что есть обьекты и что есть функции и контексты. Рассмотрим такой код:
Код
/* при вызове как конструктор создаётся новый обьект и присываивается ссылке this.
* при вызове как простой метод в this храниться ссылка на window(функция является методом обьекта window, и исполняется по умолчанию в его контексте)
* затем функция инициализирует свойства
*/
function test(arg1, arg2) {
  this.field=arg1; //создали новое свойство у обьекта
  var context_field=arg2; //создали переменную в контексте конструктора
  
//важно: создаётся функция! в контексте конструктора, записываем её в свойство method
  this.method=function() {
     alert("from method(this.field): "+this.field);  //увидим vingrad
     alert("from method(field): "+(typeof(field)=="undefined"? "undefined": field)); //увидим undefined! почему? читай ниже
     alert("from method(context_field): "+context_field); //унаследовали из контекста, JavaScript
  }
}

var a=new test("vingrad", "Java Script"); //создали новый обьект
alert("from global(object.field): "+a.field); //можем обращаться к field, т.к. this(в конструкторе) и a указывают на один и тот же обьект
alert("from global(context_field): "+a.context_field); //undefined, т.к. переменная является локальной в конструкторе, т.е. переменной контекста
a.method();


Если в этот момент уже потерял способность думать, прими душь, потом продолжим smile

Представим что мы интерпретатор, по шагам выполним код(опустим определение функции test):
  • создать новый пустой обьект, слинковать его инфу(instanceof, constructor, prototype) с функцией test. Отдать пустой обьект в конструктор функцию
  • функция создаёт новое поле в обьекте(как если бы мы сами писали a.field="что угодно").
  • создать локальную переменную конструктора context_field, присвоить ей значение.
  • создать новую функцию, записать в её свойствах ссылку на текущий контекст функции test.
  • создать новое поле method в обьекте, присвоить только что созданную функцию.
Как видим свойства обьекта(установленные через this и prototype(читай дальше)) это одно - они являються настоящими полями обьекта, а локальные переменные конструктора это совсем другое. Если в конструкторе создана хотя бы одна функция, то контекст со всеми локальными и "унаследованными" выше переменными сохнраняется в ней(для неё), иначе он после вызова разрушаеться(таким образом любой вызов функции это сначала создание контекста, выполнение, если ссылок на него кроме текущей функции более нет, то контекст уничтожается. Кстати понятия static переменных не существует).

Отсюда понятно почему мы извне не можем обратиться к локальным переменным конструктора, а все созданные внутри него функции доступ до них имеют(имеют ссылку на его контекст, бывший текущим в момент их создания).
Важно: повторю все созданные внутри конструктора функции это самостоятельные обьекты, т.е каждый новый обьект от test имеет свои собственные функции-методы, которые содержат одинаковый код. Это в корне отличается от других ООП языков, где обьекты являються экземплярами класса и все имеют один и тот же код в методах(код храниться в классе). В JS каждый обьект с методом созданным как показано выше получает свой собственный обьект-метод(код храниться в обьекте).

Что бы было еще более ясно о локальных переменных конструктора(на них мы потом будет делать "приватные" поля обьекта), дополним предидущий код:
Код
//определение функции test
var a=new test("vingrad", "Java Script"); //создали обьект, конструктор уже отработал

a.method2=function() { //создали новуу функцию, но не в контексте конструктора!
//Теперь мы "наследуем" все переменные переменные из текущего(глобального) контекста, в котором context_field не определён
  alert("from method2(context_field): "+(typeof(context_field)=="undefined"? "undefined": context_field)); //undefined
}
a.method();
a.method2();

Как видим созданный method2 уже не имеет доступа к локальным переменным конструктора.

Для справки: все созданные свойства обьекта являются его личными(сохраняются в его памяти, другой обьект может их не иметь). Метод method2 мы добавили уже после того как конструктор отработал, это тоже личное свойство обьекта. Нет никакой разницы между определением свойства в конструкторе и вне конструктора, используеться один и тот же механизм динамически создаваемых свойств обьектов.
Различии появяться когда мы приступим к прототипам smile

Предлагаю сделать паузу smile smile


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


I ♥ <script>
****


Профиль
Группа: Модератор
Сообщений: 6418
Регистрация: 2.8.2004
Где: spb

Репутация: 55
Всего: 137



Обсуждение ведем здесь


PM MAIL WWW ICQ Skype   Вверх
Се ля ви
Дата 16.5.2009, 13:36 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Java/SOAрхитектор
****


Профиль
Группа: Модератор
Сообщений: 2016
Регистрация: 5.6.2004
Где: place without tim e and space

Репутация: 5
Всего: 127



Цитата(Sardar @  30.4.2005,  23:40 Найти цитируемый пост)
К сожалению сейчас пока нет универсального единого способа сделать свойства обьекта "только для чтения".

Напрямую - нет, но как насчёт методов get/set и инкапсуляции на основе замыканий?
Представляем свойство в виде двух методов - getFieldName и setFieldName - и делаем это так:
Код
var a = new function() {
    var x = 5;
    this.getX = function(){
        return x;
    }
    //А метода 'setX' не определяем ...
}

alert('X = ' + a.getX());
В Java-программировании, например, так принято делать. Сдесь за невозможностью реализации напрямую - как ты говоришь, можно использовать такой вот приёмчег... smile


--------------------
  )
 (
[_])
проф. блог

Кролики думали, что занимаются любовью, а на самом деле их просто разводили...
PM MAIL WWW Skype GTalk   Вверх
  
Ответ в темуСоздание новой темы Создание опроса
Форум для вопросов, которые имеются в справочниках, но их поиск вызвал затруднения, или для разработчика требуется совет или просьба отыскать ошибку. Напоминаем: 1) чётко формулируйте вопрос, 2) приведите пример того, что уже сделано, 3) укажите явно, нужен работающий пример или подсказка о том, где найти информацию.
 
1 Пользователей читают эту тему (1 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | JavaScript: Общие вопросы | Следующая тема »


 




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


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

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