Работа с объектами в JavaScript: теория и практика

    В этой статье я хочу по возможности полно и последовательно рассказать о том, что такое объект в JavaScript, каковы его возможности, какие взаимоотношения могут строиться между объектами и какие способы «родного» наследования из этого вытекают, как это все влияет на производительность и что вообще со всем этим делать :)

    В статье НЕ будет ни слова про: эмуляцию традиционной класс-объектной парадигмы, синтаксический сахар, обертки и фреймворки.

    Сложность материала будет нарастать от начала к концу статьи, так что для профи первые части могут показаться скучными и банальными, но дальше будет намного интереснее :)


    Объекты в JavaScript


    Во многих статьях встречается фраза «В JavaScript — всё объект». Технически это не совсем верно, однако производит должное впечатление на новичков :)

    Действительно, многое в языке является объектом, и даже то, что объектом не является, может обладать некоторыми его возможностями.

    Важно понимать, что слово «объект» употребляется здесь не в смысле «объект некоторого класса». Объект в JavaScript — это в первую очередь просто коллекция свойств (если кому проще, может называть это ассоциативным массивом или списком), состоящая из пар ключ-значение. Причем ключом может быть только строка (даже у элементов массива), а вот значением — любой тип данных из перечисленных ниже.


    Итак, в JavaScript есть 6 базовых типов данных — это Undefined (обозначающий отсутствие значения), Null, Boolean (булев тип), String (строка), Number (число) и Object (объект).
    При этом первые 5 являются примитивными типами данных, а Object — нет. Кроме того, условно можно считать, что у типа Object есть «подтипы»: массив (Array), функция (Function), регулярное выражение (RegExp) и другие.
    Это несколько упрощенное описание, но на практике обычно достаточное.

    Кроме того, примитивные типы String, Number и Boolean определенным образом связаны с не-примитивными «подтипами» Object: String, Number и Boolean соответственно.
    Это означает, что строку 'Hello, world', например, можно создать и как примитивное значение, и как объект типа String.
    Если вкратце, то это сделано для того, чтобы программист мог и в работе с примитивными значениями использовать методы и свойства, как будто это объекты. А подробнее об этом можно будет прочитать в соответствующем разделе данной статьи.

    Работа по ссылке


    Ссылка — это средство доступа к объекту под различными именами. Работа с любыми объектами ведется исключительно по ссылке.
    Продемонстрируем это на примере:

    test=function() {alert('Hello!')} //Создадим функцию {alert('Hello!')} (а функция, как мы помним, является полноправным объектом) и сделаем переменную test ссылкой на нее
    test_link=test; //test_link теперь тоже ссылается на нашу функцию
    test(); //Hello!
    test_link(); //Hello!

    * This source code was highlighted with Source Code Highlighter.

    Как мы видим, и первая ссылка, и вторая дают один и тот же результат.
    Необходимо осознать, что у нас нет никакой функции с именем test, и что переменная test не является какой-то «главной» или «основной» ссылкой, а «test_link» — второстепенной.

    Наша функция, как и любой другой объект — просто область в памяти, и все ссылки на эту область абсолютно равнозначны. Более того, объект может вообще не иметь ссылок — в таком случае он называется анонимным, и может быть использован только непосредственно сразу после создания (например, передан в функцию), иначе доступ к нему получить будет невозможно и в скором времени он будет уничтожен сборщиком мусора (garbage collection), который и занимается тем, что удаляет объекты без ссылок.

    Посмотрим, почему так важно это понимать:
    test={prop: 'sometext'} //Создаем объект со свойством prop
    test_link=test; //Создаем еще одну ссылку на этот объект

    alert(test.prop); //sometext
    alert(test_link.prop); //sometext

    //Изменяем свойство объекта
    test_link.prop='newtext';

    alert(test.prop); //newtext
    alert(test_link.prop); //newtext
    /*Можно было бы сказать, что свойство изменилось и там и тут - но это не так.
    Объект-то один. Так что свойство изменилось в нем один раз, а ссылки просто продолжают указывать туда, куда и указывают. */

    //Добавляем новое свойство и удаляем старое
    test.new_prop='hello';
    delete test.prop;

    alert(test_link.prop); //undefined - такого свойства больше нет
    alert(test_link.new_prop); //hello - что и следовало ожидать

    //Удаляем ссылку
    delete test;
    alert(test.new_prop);
    /*В этом месте скрипт выкинет ошибку, потому что test уже не существует, и test.new_prop не существует тем более */
    alert(test_link.new_prop); //hello
    /* а вот тут все в порядке, ведь мы удалили не сам объект, а лишь ссылку на него. Теперь на наш объект указывает единственная ссылка test_link */

    //Создаем новый объект
    test=test_link; //Сперва снова создадим ссылку test
    test_link={prop: 'sometext'} //А вот и новый объект

    alert(test_link.prop); //sometext
    alert(test.prop); //undefined
    /* Cоздание нового объекта разрывает ссылочную связь, и теперь test и test_link указывают на разные объекты.
    Фактически, это равносильно удалению ссылки test_link и созданию ее заново, но уже указывающей на другой объект */
    alert(test.new_prop); //hello - теперь test содержит ссылку на наш самый первый объект

    * This source code was highlighted with Source Code Highlighter.

    Такое поведение объектов часто вызывает массу вопросов у начинающих разработчиков, так что надеюсь данный текст внесет некоторую ясность. Если же мы хотим создать действительно новую, независимую копию объекта, а не ссылку — то единственный способ сделать это — создать новый объект и скопировать туда требуемые свойства.

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

    Примитивные значения


    Как я упоминал выше, типы данных String и Number могут быть как объектами, так и примитивными значениями.
    obj=new String('hello'); //Создаем строку как объект
    simple='hello'; //Создаем примитивное значение

    alert(obj); //hello
    alert(simple); //hello - пока все предсказуемо

    alert(obj.length); //6 - у объекта типа String есть свойство length, хранящее длину строки
    alert(simple.length); //6
    /* Хотя simple - не объект, мы можем обращаться к тому же набору свойств, что и у объекта типа String. Это довольно удобно */

    obj.prop='text';
    simple.prop='text';

    alert(obj.prop); //text - раз obj обычный объект, то мы можем запросто придать ему еще одно свойство
    alert(simple.prop); //undefined - а вот simple не объект, и этот номер у нас не пройдет


    * This source code was highlighted with Source Code Highlighter.

    Все то же самое справедливо и для типа Number, и для Boolean (ну, кроме того, что в них нет свойства length, а есть ряд других замечательных свойств).
    Использование строк и чисел как объектов не несет в себе никакой практической пользы, т.к. примитивные значения удобнее в работе, но сохраняют при этом весь необходимый функционал. Тем не менее, для полноты картины необходимо понимать этот механизм.

    Не стоит путать использование примитивных значений с использованием литералов — например, независимо от того, создаем мы массив как «test=new Array()» или как «test=[]», в результате все равно будет один и тот же объект. Никаких примитивных значений мы не получим.

    Создание и использование объектов


    Итак, в отличии от языков, где реализована класс-объектная парадигма, нам не нужно создавать сначала класс, чтобы потом создать объект класса. Мы можем сразу создать объект, что и сделаем в следующем примере:
    test={
     simple_property: 'Hello',
     object_property: {
      user_1: 'Петя',
      user_2: 'Вася'
     },
     function_property: function(user) {
      alert(this.simple_property + ', ' + this.object_property[user]);
     }
    }

    test.function_property('user_1'); //Hello, Петя.


    * This source code was highlighted with Source Code Highlighter.

    Перед нами объект test, имеющий 3 свойства, названия которых, как я надеюсь, говорят сами за себя. Больше всего нас в нем интересует свойство function_property, содержащее функцию. Такую функцию можно назвать методом объекта.

    В нашей функции дважды используется ключевое слово this, которое является указателем (т.е. ссылкой) на объект, из которого вызывается функция. Таким образом, this.simple_property=test.simple_property='Hello', а this.object_property[user]=test.object_property[user]='Петя'.

    Необходимо четко осознавать, this всегда указывает именно на объект, из которого вызвана функция, а не на объект, к которому она принадлежит. Хотя в данном примере это один и тот же объект, это не всегда так.
    test.function_property('user_1'); //Hello, Петя.

    test2=new Object(); //Еще одна форма создания нового объекта, аналогичная test2={}

    test.function_property.call(test2, 'user_1'); //ошибка
    /* Метод call позволяет вызвать функцию от имени другого объекта. В данном случае, мы вызываем метод function_property объекта test, и его this указывает уже не на объект test, а на объект test2. А т.к. в нем нет свойства object_property, то при попытке получить this.object_property[user]скрипт выдаст ошибку */

    //попробуем исправить ситуацию
    test2.simple_property='Good day';
    test2.object_property=test.object_property; //В данном случае воспользуемся указанием объекта по ссылке, чтобы не дублировать код

    test.function_property.call(test2, 'user_1'); //Good day, Петя.


    * This source code was highlighted with Source Code Highlighter.

    Из примера также должно быть видно, что нет четких этапов создания и использования объекта. Объект может быть как угодно модифицирован в любое время — до, после и даже во время использования. Это тоже важное отличие от «традиционного» ООП.

    Конструктор


    В примере выше мы создавали 2 объекта, обладающих некой схожестью. И там и там имелись свойства simple_property и object_property. Очевидно, что при написании реального кода также нередко встает задача создания одинаковых или просто похожих объектов. И разумеется, мы не должны каждый такой объект создавать вручную.

    На помощь нам придет конструктор. Конструктор в JavaScript — это не часть класса (потому что здесь нет классов), а просто самостоятельная функция. Самая обычная функция.

    make_me=function(_name) {
    alert('меня запустили');
    this.name=_name;
    this.show_name=function() {alert(this.name);}
    }

    child=new make_me('Вася'); //меня запустили
    /* Давайте разберемся, что здесь происходит. Интерпретатор видит оператор new и проверяет, что находится справа от него. Т.к. make_me - это функция, и она может быть использована в качестве контруктора, то создается новый объект в памяти и запускается на выполнение функция make_me, причем ее this указывает как раз на этот новый объект. Далее этому объекту добавляется свойство name, которому присваивается значение из аргумента _name, и метод show_name. Также (не знаю в какой именно момент, но это и не важно) переменная child начинает указывать на наш новенький, только что рожденный объект */

    alert(child.name); //Вася
    child.show_name(); //Вася

    child2=new make_me('Петя');
    child2.show_name(); //Петя

    child2.show_name=function() {alert('Не буду говорить свое имя');} //Не забываем, что можем изменять наши объекты в любой момент
    child2.show_name(); //Не буду говорить свое имя

    child.show_name(); //Вася - дети никак не влияют друг на друга


    * This source code was highlighted with Source Code Highlighter.

    Также можно сравнить конструктора с отцом — он порождает ребенка, наделяя его определенными качествами, но сразу после создания ребенок становится полностью независим от родителя и может стать очень непохожим на своих братьев.
    Если мы вспомним про описание типов данных в начале статьи, то становится понятно, что Object и его подтипы (Function, Array и другие) — это на самом деле конструкторы, придающие создаваемому объекту возможности функции, массива и т.д.

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

    Прототип


    Как у каждого ребенка есть отец и мать (хотя бы в биологическом смысле), также они есть и у каждого объекта в JavaScript. И если отец, как мы определелись, работает конструктором, то мать — это как раз прототип. Посмотрим, как это происходит:
    make_me=function(_name) {
    alert('меня запустили');
    this.name=_name;
    this.show_name=function() {alert(this.name);}
    }
    /*
    Видя ключевое слово function, интерпретатор проверяет код справа от него, и т.к. все ок - создает новый объект в памяти, который одновременно является нашей функцией. Затем, автоматически (без участия программиста) для этой функции создается свойство prototype, ссылающееся на пустой объект. Если бы мы это делали вручную, это выглядело бы как make_me.prototype=new Object();

    Затем, данному объекту (на который указывает свойство prototype) также автоматически добавляется свойство constructor, указывающее обратно на функцию. Получается такая вот циклическая ссылка.

    Теперь этот объект, который можно описать как {constructor: ...здесь ссылка на фунцию...} - и есть прототип функции.
    */

    alert(typeof make_me.prototype); //Object - действительно, объект
    alert(typeof make_me.prototype.constructor); //Function - это наша функция
    alert(make_me.prototype.constructor === make_me); //true

    make_me.prototype.set_name=function(_name) {this.name=_name;} //Добавляем в прототип функции make_me новый метод

    child=new make_me('Вася'); //меня запустили
    /* Теперь помимо всего того, что описано в предыдущем примере, дополнительно в объекте child создается скрытое свойство [[Prototype]], которое указывает на тот же объект, что и make_me.prototype. Т.к. свойство скрыто, мы не можем ни просмотреть его значение, ни изменить его - однако оно играет важную роль в дальнейшей работе */

    alert(child.name); //Вася
    child.show_name(); //Вася

    child.set_name('Коля');
    /* Сначала, интерпретатор ищет метод set_name в объекте child. Так как его там нет, он продолжает поиск в свойстве child.[[Prototype]], находит его там и запускает. */
    child.show_name(); //Коля - теперь Васю зовут Коля :)

    make_me.prototype.show_name2=function() {alert('Привет, ' + this.name;} //Т.к. прототип - это обычный объект, мы точно также можем его менять на лету

    child2=new make_me('Петя');
    child2.show_name2(); //Привет, Петя
    child.show_name2(); //Привет, Коля - изменения в прототипе влияют не только на вновь созданные объекты, но и на все старые

    child2.show_name2=function() {alert('Не буду говорить свое имя');} //Мы по прежнему можем изменить сам объект, при этом новый метод show_name2 в данном объекте (и только в нем) как бы "затрет" старый метод из прототипа
    child2.show_name2(); //Не буду говорить свое имя - т.к. у нас теперь есть собственный метод show_name2, то он и вызывается, и поиск в прототипе не происходит

    child.show_name2(); //Привет, Коля - здесь все по прежнему

    make_me.prototype={prop: 'hello'} //Попробуем пересоздать прототип заново

    alert(child.prop); //undefined
    child.show_name2(); //Привет, Коля
    /* Если вспомнить, что такое работа по ссылке, то все понятно. Пересоздание прототипа рвет связь, и теперь свойство [[Prototype]] у объектов child и child2 указывают на один объект (который раньше был прототипом функции make_me), а свойство make_me.prototype - на другой объект, который является новым прототипом функции make_me */

    child3=new make_me('Олег');
    alert(child3.prop); //hello - что и следовало ожидать

    * This source code was highlighted with Source Code Highlighter.

    Как видно из примера, пока отец сохраняет верность матери (т.е. пока протип функции остается прежним), все дети зависят от матери и чутко реагируют на все изменения в ней. Однако, стоит только родителям развестись (конструктор меняет прототип на другой) — дети тут же разбегаются кто куда и больше связи с ними нет.

    Немного о терминологии
    До тех пор, пока первичная связь между конструктором и прототипом не разорвана, мы можем наблюдать следующую картину:
    make_me=function(_name) {
    alert('меня запустили');
    this.name=_name;
    this.show_name=function() {alert(this.name);}
    }

    make_me.prototype.set_name=function(_name) {this.name=_name;}
    child=new make_me('Вася');

    alert(typeof make_me.prototype); //object - у функции есть свойство prototype
    alert(typeof child.prototype); //undefined - у созданного объекта НЕТ свойства prototype
    alert(child.constructor.prototype === make_me.prototype); //true - зато у объекта есть свойство constructor, которое указывает на функцию-конструктор make_me, у которой, в свою очередь, есть свойство prototype

    * This source code was highlighted with Source Code Highlighter.

    Как я заметил после чтения многочисленных форумов на эту тему, основные проблемы возникают у людей, когда они путают свойство prototype у функции и скрытое свойство [[Prototype]] у объекта, созданного с помощью этой функции.
    Оба этих свойства являются ссылкой на один и тот же объект (до тех пор, пока первичная связь прототипа с конструктором не нарушена), но это тем не менее разные свойства, с разными именами, одно из них доступно для программиста, а другое нет.

    Необходимо всегда четко понимать, что если речь идет о прототипе конструктора — то это всегда свойство prototype, а если о прототипе созданного объекта — то это скрытое свойство [[Prototype]].

    Наследование


    Теперь мы знаем, что у каждого объекта есть скрытая ссылка на прототип, а каждый прототип — это обычный объект.
    Наиболее чуткие читатели уже уловили запах рекурсии :)
    Действительно, т.к. прототип — это обычный объект, то и он в свою очередь имеет ссылку на свой прототип, и так далее. Таким образом реализуется иерархия прототипов.
    bird=function() {} //Это конструктор птички
    bird.prototype.cry=function(){alert('Кри!');} //Птичка умеет кричать
    bird.prototype.fly=function(){alert('Я лечу!');} //и летать

    duck=function() {}
    duck.prototype=new bird();
    duck.prototype.cry=function(){alert('Кря-кря!');} //Утка кричит по другому
    duck.prototype.constructor=duck; //Принудительно устанавливаем свойство prototype.constructor в duck, т.к. иначе оно будет ссылаться на bird

    billy = new duck(); //Билли - это наша утка
    billy.fly(); //Я лечу! - Билли может летать, потому что он птица
    billy.cry(); //Кря-кря! - Билли кричит кря-кря, потому что он утка

    * This source code was highlighted with Source Code Highlighter.

    Так можно реализовывать иерархию любого уровня вложенности.

    Задача на звездочку


    Теперь, раз уж мы столько знаем обо всем этом, давайте попробуем разобраться, сколько всего происходит в этих трех строчках
    make_me=function() {}
    child=new make_me();
    alert(child.toString()); //выводит [object]

    * This source code was highlighted with Source Code Highlighter.

    В первой строке мы создаем новую функцию и переменную make_me, которая указывает на эту функцию. При этом создается прототип функции, make_me.prototype, в котором содержится свойство constructor, указывающее на make_me.
    Но это далеко не все :)
    Т.к. функция make_me — это тоже объект, то он в свою очередь имеет папу и маму, т.е. конструктор и прототип. Его конструктор — это родная функция языка Function(), а прототип — объект, содержащий в себе методы call, apply и т.д. — именно благодаря этому прототипу мы и можем пользоваться этими методами в любой функции. Таким образом, у функции make_me появляется свойство [[Prototype]], указывающее на Function.prototype.

    В свою очередь, прототип конструктора Function — тоже объект, конструктором которого является (сюрприз!) Object (т.е. Function.prototype.[[Prototype]].constructor===Object), а прототипом — объект, содержащий стандартные свойства и методы объекта, такие как toString, hasOwnProperty и другие (другими словами — Function.prototype.[[Prototype]]['hasOwnProperty'] — это как раз тот самый метод, которым мы можем пользоваться во всех производных объектах — причем это именно собственной метод данного объекта, а не наследованный). Вот таким вот интересным образом мы обнаруживаем, что все виды объектов являются производными от Object.

    Можем ли мы продолжить дальше? Оказывается, нет. Object.prototype именно потому и содержит базовые свойства объекта, что не имеет собственного прототипа. Object.prototype.[[Prototype]]=null; В этом месте путешествие по цепочке прототипов в поиске свойства или метода прекращается.

    Еще один интересный факт — конструктором Object является Function. Т.е. Object.[[Prototype]].constructor===Function.
    Налицо еще одна циклическая ссылка — конструктор Object это Function, а конструктор Function.prototype — это Object.

    Вернемся к нашему примеру. Как создается функция мы уже поняли, теперь перейдем ко второй строке. Там мы создаем объект child, конструктором которого является функция make_me, а прототипом — make_me.prototype.

    Ну и в третей строчке мы видим, как интепретатор поднимается по цепочке, от child к child.[[Prototype]] (он же make_me.prototype), затем к child.[[Prototype]].[[Prototype]] (он же Object.prototype), и уже там находит метод toString, который и запускает на выполнение.

    Примеси


    Может показаться, что наследование через прототипы — единственный способ, возможный в JavaScript. Это не так.
    Мы имеем дело с очень гибким языком, который предоставляет не столько правила, сколько возможности.

    Например, при желании мы можем вообще не использовать прототипы, а программировать с помощью концепции примесей. Для этого нам пригодятся наши старые добрые друзья — конструкторы.
    //Это конструктор человека
    man=function() {
    this.live=function(){alert('Я живу');} //Человек умеет жить
    this.walk=function(){alert('Я иду');} //Человек умеет ходить
    }

    //Это конструктор поэта
    poet=function(){
    this.kill=function(){alert('Поэт убил человека');} //Поэт может убить человека
    this.live=function(){alert('Я мертв');} //От этого человек умрет
    }

    vladimir=new man(); //Владимир - человек
    vladimir.live(); //Я живу - он жив
    vladimir.walk(); //Я иду - он ходит

    poet.call(vladimir); //Выполняем конструктор poet для объекта vladimir
    vladimir.kill(); //Поэт убил человека
    vladimir.live(); //Я мертв

    //А теперь фокус
    man.call(vladimir);
    vladimir.live(); //Я живу

    * This source code was highlighted with Source Code Highlighter.


    Что мы видим в данном примере? Во-первых, это возможность наследования от нескольких объектов, не находящихся в одной иерархии. В примере их 2, но может быть сколько угодно.
    Во-вторых, это отсутствие какой-либо иерархии вообще. Переопределение свойств и методов определяется исключительно поряком вызова конструкторов.
    В-третьих, это возможность еще более динамически менять объект, причем именно отдельный объект, а не всех потомков, как при изменении прототипа.

    Upd: Замыкания и приватные свойства


    Чтобы не раздувать эту и без того немаленькую статью, даю ссылку на пост Замыкания в JavaScript, где про это довольно подробно написано.

    Что теперь со всем этим делать


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

    Причем вопрос о цене довольно нетривиален, особенно если мы говорим о разработке под браузер Internet Explorer 6 и 7 версий.
    1. Память — тут все просто. Во всех браузерах наследование на прототипах отнимает в разы меньше памяти, чем при создании методов через конструкторы. Причем, чем больше методов и свойств у нас есть, тем больше разница. Однако, стоит помнить, что если у нас не тысяча одинаковых объектов а всего лишь один, то расходы памяти в любом случае будут небольшими, т.к. здесь стоит учитывать другие факторы.
    2. Процессорное время — здесь основные тонкости связанны именно с браузерами от Microsoft.
    С одной стороны, объекты, где методы и свойства создаются через конструктор — могут создаваться в разы (в некоторых случаях в десятки и сотни раз) медленнее, чем через прототип. Чем больше методов — тем медленнее. Так что если у вас в IE замирает на несколько секунд во время инициализации скрипта — есть повод копать в эту сторону.

    С другой стороны, собственные методы объекта (созданные через конструктор) могут выполняется немного быстрее, чем прототипные. В случае, если позарез необходимо ускорить именно выполнение какого-то метода в этом браузере, то нужно это учесть. Имейте ввиду, ускоряется именно вызов метода (т.е. поиск его в объекте), а не его выполнение. Так что если сам метод у вас выполняется секунду, то особого увеличения быстродействия вы не заметите.

    В других браузерах подобных проблем наблюдается, там время создания объектов и вызова их методов примерно одинаково для обоих подходов.

    P.S. Обычно в статьях подобного рода автор предлагает некую обертку, либо пытающуюся реализовать класс-объектное наследование на базе прототипного, либо просто синтаксический сахар для прототипного наследования. Я не делаю этого намеренно, т.к. считаю, что человек, понявший смысл данной статьи, способен сам для себя написать любую обертку, и еще много интересных вещей :)
    Поделиться публикацией
    Реклама помогает поддерживать и развивать наши сервисы

    Подробнее
    Реклама
    Комментарии 216
    • +4
      > языке есть 3 типа данных, которые действительно являются полноправными объектами

      а как же RegExp, Node, Arguments..?

      > ключом, как обычно, может быть только число или строка

      нет, только строка

      но в целом — хорошо. правда про замыкания — ни слова…
      • 0
        Вообще всё, что приходит снаружи (какой-нибудь XMLHttpRequest) объктом JavaScript не является… Так что кроме JS-объектов в реальных браузерах есть и «классические» объекты… С классами и прочей атрибутикой…
        • +1
          RegExp и Arguments — самые что ни на есть родные.
        • 0
          > > языке есть 3 типа данных, которые действительно являются полноправными объектами
          > а как же RegExp, Node, Arguments..?
          Имхо, всё, что имеет свойство constructor и не является скалярным значением — полноправный объект.
          Например, DOM-элемент в IE — это не-полноправный объект =)
          • 0
            И так статья получилась огромная, а замыкания — это все-таки несколько другая тема. А вы думаете, в контексте данной статьи стоило о них упомянуть?
            • +1
              ага… с их же помощью создаются приватные поля.
              • +3
                А разве приватные поля не являются тем самым притягиванием за уши класс-объектной модели?
                Понятно, что их можно сделать, и понятно, что многие привыкли их использовать в других языках, но стоит ли этим заниматься в JavaScript, где изначально все прозрачно и открыто?
                • +1
                  нет. какое отношение инкапсуляция имеет к классам? =)

                  приватные поля короче в записи и не засоряют внешний интерфейс.
                  • 0
                    Ну, тут как бы дело в том, что в класс-ориентированных языках мы определяем приватное поле (или метод) ключевым словом private, и это — родной способ языка, предназначенный именно для этого.

                    В JavaScript мы создаем локальную переменную функции, и потом используем замыкание. Причем, если мы вдруг в процессе разработки решили приватное свойство сделать публичным или наоборот, то придется сделать больше телодвижений, чем просто поменять private на public.

                    Нам придется либо менять везде prop на this.prop (или наоборот), либо, если мы изначально работали с приватным свойством через геттеры и сеттеры — у нас может получиться избыточность, когда в геттере берется свойство, которое теперь и так публично (типа this.get_private() {return this.old_private_now_public})

                    Собственно в том и вопрос — стоит ли оно того.
                    • 0
                      ну да, в яваскрипте то же самое реализуется по другому — с помощью ключевого слова var =)

                      и часто тебе приходится делать приватные свойства публичными? особенно при наличие акцессоров… ;-)
                      • 0
                        Да не то чтобы часто, но на JavaScript не один же я пишу :) Кто его знает, что там у людей происходит в процессе разработки.

                        В общем да, пожалуй стоит об этом написать, как об одной из возможностей, но даже пока не знаю, стоит ли апдейтить данную статью или заводить новую.
                        • 0
                          ну написал же про примеси, которые нужно безконца перемешивать ;-)
                          кстати, вместо перепримешивания лучше создавать лёгкие прокси-объекты: groups.google.com/group/jfix-javascript-framework/browse_thread/thread/25e9a9420e6697b6?hl=ru
                          кстати, подключайся ;-)
            • 0
              Про типы данных исправил.
              А вот с ключами немного непонятный для меня момент:
              Если мы создадим массив ar=['test', 'test2'], то получим новый объект.
              При этом ar[0]=='test', однако ar.0 — выдает ошибку.
              Но delete ar[0] удаляет элемент массива так же, как если бы это было свойство объекта, причем ar.length при этом остается равно 2.
              Как это все можно объяснить?
              • +1
                alert([777][{toString:function(){return '0'}}]

                магия приведения типов
                • 0
                  Чего-то я все равно недопонял :)
                  [777][{toString:function(){return '0'}}] по идее то же самое, что [777]['0'].
                  Причем и [777]['0'] и [777][0] дают один и тот же результат.

                  Т.е. вы хотите сказать, что у массива ключи — это строки на самом деле? И [777][0] как бы преобразуется в [777]['0'] на лету?
                  • +3
                    ага
                    for( var key in [1] ) alert( typeof key );
                    • 0
                      Ух, шайтан )
                      Интересно, зачем это так реализовано.
                      Сейчас исправлю этот момент в статье, спасибо за подсказку.
                      • +1
                        сам удивляюсь. чем больше узнаю яваскрипт, тем больше убеждаюсь, что придумал его шизофреник…
                        • 0
                          Причем брат того, кто придумал Перл…
                          • –4
                            сам удивляюсь. чем больше узнаю яваскрипт, тем больше убеждаюсь, что придумал его шизофреник…
                            Вывод неверный. JavaScript никто не придумывал! Он постепенно создавался путём добавления средств, которых больше всего не хватало программистам, пишущим интерпретатор. Причём они добавлялись туда, куда было проще всего их добавить программистам. Тот же путь — у языков Perl (ужас), PHP (ужас, ужас, ужас), etc…
                          • –3
                            Вы неправильный вопрос задаёте: не «зачем», а «почему»! Никто не придумывал JavaScript! Он делался в большой спешке и бо́льшая часть маразма в нём возникла не потому что его кто-то придумал, а потому что «так получилось»… Представьте что вам нужно сделать интерпретатор простенького скриптового язычка, у вас очень мало времени, о будущем вам думать некогда — и вы всё поймёте.

                            Ну проще просто язык реализовывать если «не заморачиваться» и всё в строки преобразовывать. Но числами индексировать хочется — так давайте их автоматом преобразовывать! Где-то сделали так, где-то иначе, в конце-концов зафиксировали то, что получилось в стандарте… и, ужаснувшись, успокоились.
                            • +1
                              > о будущем вам думать некогда

                              Если б он заглянул в будущее Нетскейп Навигатора, то быстренько бы уволился, какой там язык… ;)

                              > Но числами индексировать хочется — так давайте их автоматом преобразовывать!

                              Массивов изначально не было, зато с каждым свойством объекта дополнительно корреспондировал индекс того же объекта.
                  • +1
                    Про замыкания я вот тут относительно подробно написал: habrahabr.ru/blogs/webdev/38642/
                    • +2
                      Ну вот кстати да, одна из причин, по которой я не писал про замыкания — это как раз то, что про них и так довольно много написано. Если вы не против, я могу дать из своей статьи ссылку на вашу, чтобы не раздувать ее сверх меры.
                      • +1
                        Я только за. :)
                        А за статью спасибо, новичкам будет очень полезна.
                  • 0
                    Примеси
                    … при желании мы можем вообще не использовать прототипы
                    На выполнение конструкций вида this.property = value; требуется время. Поэтому, чем больше объектов в системе и чем сложнее функции-конструкторы этих объектов, тем медленнее будет работать наш код.
                    Лучше всё таки использовать прототипы, либо говорить про подобные «примеси» с оговоркой на скорость.
                    • +2
                      Вы статью-то читали? Там внизу все оговорки приведены.

                      Вообще статья мне понравилась: одна из немногих рассказывающая про ООП в JavaScript и не притягивающая за уши понятие «класс», которого там нет и никогда не было…
                      • 0
                        Читал и мне тоже понравилось.
                        Метод «примесей» подан как «очень положительный». Я же считаю, что его наоборот следует применять по минимуму.

                        > не притягивающая за уши понятие «класс»
                        Раз уж вы заговорили про уши:
                        bird=function() {}
                        // ...
                        duck=function() {}
                        duck.prototype=new bird();
                        Подобный код всегда вызывал у меня непонимание: с одной стороны функция-конструктор является как-бы инициализатором свойств нового объекта, а с другой — может выступать в роли конструктора прототипа другой функции. Напонимание в следующем: как, используя данный метод, передавать параметры для инициализации нового объекта? Ответа я так и не нашёл (вариант проверки параметров на undefined в функции-конструкторе не подходит, потому что код становится не красивым =)
                        • –3
                          — Доктор, когда я сгибаю руку вот так у меня стреляет вот здесь. Что мне делать
                          — Не сгибать так руку.

                          Зачем вам потребовалось смешивать в кучу разные сущности? То что JavaScript позволяет такой финт ушами не обозначает что его нужно использовать!
                          • 0
                            — Доктор, а мне удобнее всего чесать за ухом именно с согнутой рукой! Что мне делать?

                            Затем, что это интуитивно понятно и удобно. А из того, что вы так называете передачу параметров в функцию-инициализатор, совсем не обозначает, что этой замечательной возможностью пользоваться запрещено.
                            • +2
                              — А вам обязательно при этом руку между ног пропускать?

                              Что конкретно вам не нравится в примере? Вы хотите создавать не только уток, орлов и пеликанов, но и просто абстрактных птичек? Сделайте фабрику:
                              bird=function() {} //Это конструктор птички
                              bird.prototype.cry=function(){alert(this.crytext);} //Птичка умеет кричать
                              bird.prototype.fly=function(){alert('Я лечу!');} //и летать
                              make_a_bird=function(crytext) {
                                var nb=new bird();
                                nb.crytext=crytext;
                                return nb;
                              }

                              В чём проблема-то? Если вам нужна птичка - используете make_e_bird, а простой конструктор - оставьте для прототипов.
                              • –2
                                — Гы =) Доктор, ну уж вы совсем какую-то ерунду говорите.

                                Предлагаю голосование: если кто-то выступает за метод make_a_bird, то [+1] вам в комент и [-1] в мой, а если против — то наоборот.
                                • 0
                                  подсыпь кармы — рассужу =)
                                • +1
                                  > В чём проблема-то?

                                  А зачем нам «конструктор» и «сделай_на_основе_конструктора»? Ладно бы Вы штамповали каскадно объекты через фабрику, но это здесь — уже создана «порождающая сущность» (конструктор), но потом еще и «порождающая-порождающая сущность».

                                  Другой вопрос, когда конструктор обертывают — там нет никаких привязок к именам типа «bird», «make_a_bird» и т.д. + еще и связать прототипы можно внутри этой обертки.

                                  Создавать же wrapper для каждого конструктора вручную — это лишнее.
                                  • 0
                                    А зачем нам «конструктор» и «сделай_на_основе_конструктора»?
                                    А это уж у Covex'а нужно спросить. Если мы хотим по разному создавать объекты, являющиеся прототипами и не являющиеся таковыми — но нам нужны две разные функции. В большинстве случаев это действительно не нужно.

                                    Ладно бы Вы штамповали каскадно объекты через фабрику, но это здесь — уже создана «порождающая сущность» (конструктор), но потом еще и «порождающая-порождающая сущность».
                                    Эта так же раница, которая в C++ имеется между operator new класса и конструктором, а в Java — между конструктором и фабрикой. Порождать это дело руками или автоматом — дело вкуса.
                                    • +1
                                      > Порождать это дело руками или автоматом — дело вкуса.

                                      Дело генерации. Или Вы на счетах считаете, а не на компьютере?

                                      Один из лозунгов «какой-то» философии:
                                      Правило генерации: избегайте ручного ввода кода, при любом удачном случае, пишите программы, которые бы писали программы.
                                • +1
                                  Для прототипа wrapper можно создать, таким образом, и «холостые выстрелы» исчезнут. Прототип wrapper'a будет указывать на прототип родительского конструктора.

                                  function A() { alerr('A'); }
                                  A.prototype.fn = function () {};
                                  
                                  function B() {
                                    B.parent.apply(this, arguments);
                                    alert('B');
                                  }
                                  
                                  // связка прототипов для делегирования
                                  var __wrapper = function() {};
                                  __wrapper.prototype = A.prototype;
                                  B.prototype = new __wripper();
                                  B.prototype.constructor = B;
                                  B.parent = A;
                                  
                                  var obj = new B(); // alert('A'), alert('B')


                                  В то время, как, если бы не было wrapper'a — alert('A') «выстрелил бы в холостую» когда создавалась цепь наследования.
                                  • 0
                                    А смысл городить такие конструкции, если можно просто не делать в конструкторах ничего, кроме инициализации свойств дефолтными значениями?

                                    Ну и соответственно для каких-то реальных действий выделить отдельный метод init.

                                    Ведь если цель — сократить объем кода и избежать лишних сущностей, то метод с wrapper все равно не позволяет ее достигнуть.
                                    • 0
                                      > А смысл городить такие конструкции, если можно просто не делать в конструкторах ничего, кроме инициализации свойств дефолтными значениями?

                                      Ну компромисс — есть компромисс. И подобных компромиссов «а смысл?» можно тогда в любом месте напихать (тогда вообще, смысл может отпасть и достаточно будет линейного (даже не процедурного)) программирования (безо всяких классов и прототипов).

                                      К тому же, иногда, кто-то в конструкторе (или методе init (если еще и для конструктора будет враппер) — не важно) может выдать системное сообщение.

                                      > Ведь если цель — сократить объем кода и избежать лишних сущностей, то метод с wrapper все равно не позволяет ее достигнуть.

                                      Ну вот в эти моменты люди и начинают писать функции-обертки (и часто называют их «классами»), где эти манипуляции с __wrapper'ами скрыты, но обеспечивают наследование. Естественно, никто не заставляет каждый раз писать этот кусок кода — вынесете его в обертку и создавайте объекты через нее (это всего лишь улучшение code reuse).
                                      • 0
                                        Тут дело даже не в обертке.
                                        Конструктор то все равно ручками пишем. Вот и спрашивается, чем B.parent.apply(this, arguments) лучше по сравнению с this.init_A(arguments);
                                        • 0
                                          > Вот и спрашивается, чем B.parent.apply(this, arguments) лучше по сравнению с this.init_A(arguments);

                                          Или я что-то упустил, или изначально не про это спрашивалось? :) Изначально спрашивалось, как передавать параметры в конструктор и не делать никаких проверок в этом одном и том же конструкторе, который используется и для порождения и для обеспечения наследования.

                                          Касаемо Вашего текущего вопроса про «init_A»: в случае с B.parent — мы не привязываемся к именам родительских сущностей («А», «Bla», «SuperBla»). В принципе, можно даже и к «B» не привязываться, если общаться через «this.constructor.parent».
                                    • 0
                                      Прикольно!
                                      Правда сразу же напрашивается «обёртка» для всего этого =)
                                • 0
                                  pastebin.ru/300297
                                  • +1
                                    Смущают две вещи:
                                    var klass= arguments.callee; // Говорят, что классы в JS - от лукавого !
                                    // и
                                    eval( 'var Cat= ' + Animal ); // А eval тем более =)
                                    • 0
                                      если единственный аргумент — «кто-то когда-то где-то что-то сказал-то», то меня он слабо колышет.

                                      тандем из функции и её прототипа вполне можно называть классом ибо удовлетворяет всем признакам класса.

                                      как клонировать функцию без эвала?
                                      • 0
                                        > если единственный аргумент
                                        Мне самому нравится называть Animal и Cat — классами (я, типа, пошутить попытался =)

                                        > как клонировать функцию без эвала?
                                        Никак =( Мне всегда хотелось создать новый объект-функцию (но как-нибудь не через eval — это ж некрасиво =)
                                        • 0
                                          не, ну можно, конечно, написать что-то вроде:

                                          Animal= function(){
                                          var func= function(){
                                          // init
                                          }
                                          func.superconstructor= arguments.callee;
                                          return func;
                                          }()

                                          Cat= Animal.superconstructor();

                                          но это изврат ;-)
                                          • 0
                                            Как это никак нельзя клонировать функцию без eval?
                                            А binding`и уже отменили?

                                            function GBind(oContext, fFunctor) {
                                                return function () {
                                                    return fFunctor.apply(oContext, arguments);
                                                }
                                            }

                                            или что-то другое имелось под клонированием?
                                            • 0
                                              Я имел ввиду немного другое. Приведу пример:
                                              function Func() {}
                                              var Obj = new Func();
                                              alert(Func.constructor == Function);
                                              Мне же хотелось иметь объект-функцию Func2, у которой был бы другой фактический конструктор — не Function, а какая-то другая моя функция.
                                              (2All: я и сам приравниваю это своё желание к бреду — можно мне это не напоминать =)
                                              • 0
                                                arguments.callee будет всегда указывать на функтор => «клонировать» можно будет только специально подготовленные для этого функции => нет смысла использовать делегирование, если можно фабрику (см выше).
                                              • 0
                                                Хм, может как-то так?
                                                function class( methods ){
                                                return function(){
                                                if( ! this.constructor ) return null;
                                                ....
                                                }
                                                }
                                                var animal = class({...})
                                                var cat = new animal( args )
                                              • 0
                                                > тандем из функции и её прототипа вполне можно называть классом ибо удовлетворяет всем признакам класса.

                                                только ты уточняй, — «всем признакам динамического класса» (потому что те, кто не знаком с динамическими объектами и классами, могут не сразу понять). Ну и, естественно, лучше уточнять (для избежания пустых холиворов), что стандарт понятия «класс», как «отдельная сущность для порождения объектов» — не описывает.
                                          • 0
                                            > Подобный код всегда вызывал у меня непонимание: с одной стороны функция-конструктор является
                                            > как-бы инициализатором свойств нового объекта, а с другой — может выступать в роли
                                            > конструктора прототипа другой функции.

                                            Тут нет никакого противоречия. Функция в любом случае является конструктором объекта. А прототип другой функции — это тоже объект. Ничуть не хуже какого-нибудь другого.
                                            • 0
                                              Согласен со всем сказаным, но вы вырвали фразу из контекста.
                                              Непонимание не в теоретической, а в практической части — там в моём комментарии начиная со слов «Напонимание в следующем» всё написано =)
                                      • +4
                                        Да — стоит заметить что во всех современных браузерах (кроме Opera и MS IE) «скрытое» свойство [[Prototype]] является вполне «открытым», имеет название __proto__ и его можно менять!

                                        Если вы пишете программну для Intranet'а и вас недобраузеры не волнуют — это можно с успехом иногда использовать, если же вы делаете публичный web-сайт, то этот можно использовать для отладки…
                                        • –1
                                          /* независимо от того, создаем мы массив как «test=new Array()» или как «test=[]», в результате все равно будет один и тот же объект */

                                          Объект будет не совсем «один и тот же»:

                                          Array.prototype.randomize = function() {}
                                          var a1 = new Array()
                                          a1.randomize() // ok
                                          var a2 = []
                                          a2.randomize() // ошипка


                                          (Firefox 3)
                                          • +3
                                            пишешь из параллельного мира, где развитие яваскрипта пошло по совсем другому пути развития? =)
                                            • +2
                                              Тысяча чертей! Прошу прощения за дезинформацию. Не зла ради!

                                              Недавно в своём проекте поменял дефиницю с [] на Array чтобы заработало прототипирование. И думал что это помогло ;-) А сейчас пробую — всё едино.
                                            • +1
                                              Ошибка в первом примере:
                                              test=new Function (alert('Hello!')}

                                              Неправильные скобки
                                              • 0
                                                Ага, спасибо, исправил.
                                                Вроде отлавливал опечатки, но видимо не все.
                                                • +1
                                                  не забудь написать:
                                                  upd: frizz подсказывает в комментах, что я наврал со скобочками.
                                                  • 0
                                                    И превратим этот пост в пост благодарности? )
                                                    Насколько я помню даже Дональд Кнут, известный своей щедростью человек, выписывал чеки только за фактические поправки, а не за обнаружение опечаток.
                                                    • 0
                                                      я к тому, что не приятно читать сначала дезинфу и сразу за ней в скобочках исправления, исправления исправлений и тп. всё-таки статья должна оставаться статьёй, не превращаясь в дифф версий.

                                                      а если очень хочется поблагодарить — я принимаю чеки, вебмани, яндекстаньга, пластиковые карточки и даже крышки от бутылок ;-)
                                              • 0
                                                Хорошая статья. Правда, о производительности ни слова :) А прототипы ой как тормозят…
                                                • +1
                                                  Там вообще то есть о производительности.
                                                  Под заголовком «Что теперь со всем этим делать».

                                                  А можно поподробнее про торможение прототипов? Насколько я понял из тестов, которые проводил, создание объектов на прототипах наоборот происходит значительно быстрее в IE. А вот вызов методов из таких объектов — лишь немного медленнее. И опять же только в IE.
                                                  • 0
                                                    ну, я по опыту могу сказать вот что:

                                                    Вариант 1
                                                    var a = function (){
                                                        this.set = function(a){this.var = a};
                                                    }
                                                    var b = new a();
                                                    b.set(1);

                                                    Вариант 2
                                                    var a = function (){}
                                                    a.prototype.set = function(a){this.var = a};
                                                    var b = new a();
                                                    b.set(1);

                                                    Вариант 3
                                                    var b = function (){}
                                                    b.set = function(a){this.var = a};
                                                    b.set(1);

                                                    Если вариант 2 работает чуть (10-20%) медленнее, чем вариант 1 (обращение через прототип функции идет). То вариант 3 (без прототипов объектов) работает в несколько (у меня в Fx в 2 раза) быстрее.

                                                    Причем, если нам метод set не нужен снаружи, то его можно объявить только внутри области видимости функции, и будет еще быстрее.
                                                    • 0
                                                      А в каких соотношениях в ваших тестах на производительность были «Объявление объекта-функции a», «расширение объекта a функцией set» и «вызов функции set»?
                                                      • 0
                                                        я привел полные варианты кода. Их можно запустить в цикле и замерить время.
                                                        Если объекты сложнее, чем пара строк, то нужно мерить в каждом конкретном случае. Пропорции сохраняются (я больше про разницу между 1 и 3 случаями).

                                                        Это я просто к тому, что если нам нужен синглетон — то использовать прототипы совсем не нужно.
                                                        • 0
                                                          > А прототипы ой как тормозят…
                                                          >… если нам нужен синглетон
                                                          А! Теперь всё ясно =)
                                                          Для синглтона new — действительно лишняя операция + на поиск функции set в прототипе объекта тратится время.
                                                          В остальных же случаях — вариант 2 работает быстрее, чем 1 и 3. Быстрее за счёт отсутствия необходимости создавать новые объекты-функции set при создании нового объекта.
                                                          • 0
                                                            нужны конкретные случаи. Т.е. хорошо бы обзорную статью со этими самыми случаями. В том коде, который я привел, вариант 2 — самый медленный.
                                                            • +1
                                                              Имхо, на статью не тянет =( Кода гораздо больше, чем текста:
                                                              var a = function() { this.c = 0; }
                                                              a.prototype.set = function() { this.c++; }
                                                              var b = function() {
                                                               this.c = 0;
                                                               this.set = function() { this.c++; }
                                                              }
                                                              function T(T1) {
                                                               var T2 = new Date().getTime();
                                                               if (T1) alert(T2-T1);
                                                               return T2;
                                                              }

                                                              var i, T1, cnt = 100000, obj;
                                                              /* Первый тест: много созданий объекта и вызовов функции */
                                                              T1 = T();
                                                              for (i=0; i<cnt; i++) {
                                                               obj = new a();
                                                               obj.set();
                                                              }
                                                              T1 = T(T1);
                                                              for (i=0; i<cnt; i++) {
                                                               obj = new b();
                                                               obj.set();
                                                              }
                                                              /* На первый тест тратится на много меньше времени, чем на второй т.к. в первом случае на создание нового объекта set не тратится время */

                                                              /* Второй тест: одно создание объекта и много вызовов функции */
                                                              T1 = T(T1);
                                                              obj = new a();
                                                              for (i=0; i<cnt; i++)
                                                               obj.set();
                                                              T1 = T(T1);
                                                              obj = new b();
                                                              for (i=0; i<cnt; i++)
                                                               obj.set();
                                                              T1 = T(T1);
                                                              /* На первый тест тратится немного больше времени т.к. во втором случае не нужно искать функцию в прототипе объекта */

                                                              * This source code was highlighted with Source Code Highlighter.
                                                              • 0
                                                                пример не очень корректен: есть же третий случай, когда мы напрямую меняем без создания промежуточных объектов. Это будет еще быстрее.

                                                                Я про это и писал, что хорошо бы разобрать 3-5 характерных примеров использования прототипов / синглетонов. Ибо сейчас лично у меня все на уровне спинного мозга запомнилось :)
                                                                • –2
                                                                  «Прототипы тормозят» — это Вы, батенька, переоптимизировали. Прототипы не являются медленной альтернативой доступа к свойству, а вносят дополнительную, недоступную другими путями, функциональность, на что и тратится время.

                                                                  Синглтоны же вообще костыли из мира классического ооп, который что-то сильно любят притягивать за уши к JS, где средства-то другие.
                                                                  • +2
                                                                    > Синглтоны же вообще костыли из мира классического ооп, который что-то сильно любят притягивать за уши к JS, где средства-то другие.

                                                                    Что Вы имеете в виду? Можно подробней, мне интересно.
                                                                    • 0
                                                                      Понадобиться «синглтон» в js может только для создания объекта, зависимого от возможно неподгруженных сущностей. Эдак можно вообще все сложные объекты так создавать «для подстраховки». ИМХО, если такая ситуация возникла, скорее всего, нужно пересмотреть архитектуру приложения.
                                                                      Считаете иначе — приведите пример, если не трудно.
                                                                      • 0
                                                                        > Понадобиться «синглтон» в js может только для создания объекта, зависимого от возможно неподгруженных сущностей.

                                                                        Почему? Почему, если мне нужен просто единичный объект:

                                                                        var obj = {a: 10, b: function () {}, c: 20};

                                                                        > Эдак можно вообще все сложные объекты так создавать «для подстраховки».

                                                                        Не ясно.

                                                                        > ИМХО, если такая ситуация возникла, скорее всего, нужно пересмотреть архитектуру приложения.

                                                                        Почему? Почему, если мне нужен просто единичный объект:

                                                                        var obj = {a: 10, b: function () {}, c: 20};

                                                                        > Считаете иначе — приведите пример, если не трудно.

                                                                        Да можно по-разному считать и если разные мнения технологически обоснованы — можно использовать все эти мнения и подходы.

                                                                        Я хотел узнать, что там за «костыли», «уши» и «другие средства»?
                                                                        • 0
                                                                          var obj = {a: 10, b: function () {}, c: 20};
                                                                          это не синглтон в понимании клооп.
                                                                          Отсюда и непонимание про костыли и уши.
                                                                          • 0
                                                                            > это не синглтон в понимании клооп.

                                                                            Что значит «не синглтон»? Причем здесь классовое ООП (я правильно понял «клооп»?). Где четкое определение синглтона (настолько четко, что дальше ни шагу)?

                                                                            > Отсюда и непонимание про костыли и уши.

                                                                            Ну, я все-таки снова попрошу объяснить. Ну а иначе — какой смысл просто так что-то говорить?
                                                                            • 0
                                                                              Паттерн синглтон: ru.wikipedia.org/wiki/%D0%A1%D0%B8%D0%BD%D0%B3%D0%BB%D1%82%D0%BE%D0%BD
                                                                              www.rsdn.ru/article/patterns/singleton.xml

                                                                              «Применение cинглтона в js»: www.google.ru/search?q=javascript+singleton&ie=utf-8&oe=utf-8&aq=t&rls=org.mozilla:ru:official&client=firefox-a
                                                                              и другие подобные извращения — притягивание за уши клооп к js.

                                                                              Как правильно, Вы уже сами привели пример:
                                                                              var obj = {a: 10, b: function () {}, c: 20};
                                                                              Всё, уникальный объект некоторого «класса» в нужной видимости создан — обращайтесь.

                                                                              Но это не применение синглтона :)
                                                                              Применить его подобие может понадобиться в случае habrahabr.ru/blogs/javascript/48542/#comment_1259671, т.к. одна из фич паттерна — отложенное создание объекта, но по этому поводу я своё мнение уже высказал.
                                                                              • 0
                                                                                > и другие подобные извращения — притягивание за уши клооп к js.

                                                                                Конкретно, пожалуйста, конкретно. Где, в каком месте и почему что-то там является извращением? Это Ваше ИМХО? Или кто-то Вам сказал?

                                                                                По поводу «клооп». Про динамические классовые организации слышали? Сравнения с моделью JS делали? Сходство видели? А разницу? И кто там кого и куда «за уши тянет»?

                                                                                > Но это не применение синглтона :)

                                                                                Смотря, что такое синглтон. Если для использования этого термина обязательно нужна сущность «класс» (а кто Вам это сказал?) — тогда можно не употреблять этот термин вообще. Тем, не менее, можно {} назвать, например, Singleinstance (и создать ее хоть отложено, хоть переотложено). Правда, для пущей наглядности, можно обернуть в функцию со «статическим» свойством — тогда уж точно отложено можно создавать/возращать нужный объект.

                                                                                «Синглтон» — всего лишь термин, для чье-то красивой реализации одиночности объекта. И «клооп» здесь особой роли не играет. А в JS нет никаких синглтонов, но можно называть одиночные объекты «Одиночками».
                                                                                • 0
                                                                                  >Конкретно, пожалуйста, конкретно. Где, в каком месте и почему что-то там является извращением?

                                                                                  Какие проблемы решает настоящий синглтон?
                                                                                  В клооп если я хочу создать 1 объект, чтобы он был виден всем, я делаю его внешним статическим. Тогда я, во-первых, не могу подготовить что-то необходимое ДО его создания, во-вторых, рискую насоздавать несколько объектов. Могу создавать его динамически и передавать по ссылке нуждающимся, тогда я описываю лишние свойства, родитель должен быть обработан до других объектов. Выход — синглтон.

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

                                                                                  >А в JS нет никаких синглтонов, но можно называть одиночные объекты «Одиночками».

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

                                                                                  >И кто там кого и куда «за уши тянет»?

                                                                                  Всякие попытки имитировать клооп в js, принесение паттернов из клооп — притягивание того, к чему просто привыкли, в другую среду, потому, что не понимают её и не хотят обучаться. «За уши» — потому, что не нужно, не лезет и выглядит нелепо.

                                                                                  • 0
                                                                                    > В том-то и дело, что «в JS нет никаких синглтонов», потому, что эти объекты ничем не отличаются от остальных.

                                                                                    Естественно, нет. Я больше интересовался об «ушах». При этом, о «клооп» заговорили Вы. Возможно, человек под «синглтоном» имел в виду порождение объекта не от «конструктора-штамповщика». Естественно, никаких синглтонов в роли паттернов из клооп в JS нет.

                                                                                    > Всякие попытки имитировать клооп в js, принесение паттернов из клооп — притягивание того, к чему просто привыкли, в другую среду, потому, что не понимают её и не хотят обучаться. «За уши» — потому, что не нужно, не лезет и выглядит нелепо.

                                                                                    О, поверьте, я не привык (ни к той, ни к другой, ни к третьей технологии). Паттерны, которые есть в «клооп», и порожденные именно благодаря статике и классам, естественно, не нужны в динамических языках (это я уже отметил). Насчет «ушей» — я лишь хотел сказать, что динамические классовые реализации (не касаемо никаких паттернов) — мало чем отличаются от динамических прототипных.
                                                                                    • 0
                                                                                      Значит мы просто изначально друг друга не правильно поняли.

                                                                                      Синглтон, конечно, отчасти фабрика, но для меня он всё-таки конкретный паттерн именно из клооп, потому и заговорил про клооп. Тем более, на фоне общей тенденции имитировать клооп в js.
                                                      • 0
                                                        > Вариант 3… var b = function (){}

                                                        А почему именно function, а не просто var b = {};? Коль скоро, это «синглтон»? {} по действиям алгоритма быстрее, чем создание объекта function.
                                                        • 0
                                                          это просто ради чистоты эксперимента — везде в качестве объектов функции. Можно и хэш, никто не спорит — это еще быстрее :)
                                                          • 0
                                                            > везде в качестве объектов функции

                                                            да нет, в первых двух случаях, как раз-таки получаются не «объект типа функция», а «объект типа объект».
                                                  • +1
                                                    Еще можно порекомендовать «JavaScript: подробное руководство» Флэнагана.
                                                    • +1
                                                      Это та, что с бегемотом на обложке, отличная книга.
                                                  • 0
                                                    Отличная статья. Продолжайте в том же духе пожалуйста =)
                                                    • 0
                                                      obj=new String('hello'); //Создаем строку как объект
                                                      simple='hello'; //Создаем примитивное значение


                                                      А если говорить правильно, то оба обьявления ничем не отличаются, и обе строки будут совершенно одинаковыми.
                                                      • 0
                                                        Оговорка: первая конструкция работает медленнее, чем вторая
                                                        • 0
                                                          Ну я как бы не о скорости речь веду :)
                                                        • 0
                                                          Вообще-то они ни разу не одинаковые. Одна — объект, другая — строка. Одной можно добавить свойства, другой — нельзя. Это собственно и в статье написано.
                                                          • 0
                                                            Одной можно добавить свойства, другой — нельзя
                                                            Я, видимо, неправильно добавляю свойства. :)

                                                            >>> test = 'test';
                                                            "test"
                                                            >>> String.prototype.some_property = 12
                                                            12
                                                            >>> test.some_property
                                                            12
                                                            • 0
                                                              test = 'test'
                                                              String.prototype.A = 12
                                                              test.B = 45
                                                              console.log(test.A, test.B)
                                                              • 0
                                                                Свойство A присутствует, B — отсутствует.
                                                                Т.е. свойства через прототип добавить можно, а индивидуальные нельзя.
                                                                Возможно, это имел ввиду автор статьи
                                                                • 0
                                                                  Да, речь именно о собственных свойствах объекта. Кроме того, с obj=new String() можно работать по ссылке, а с примитивной строкой — нет.
                                                                • 0
                                                                  См. ниже.
                                                            • 0
                                                              alert([ Boolean(''), Boolean( new String('') ), Boolean( String( '' ) ) ])
                                                              • 0
                                                                Разобрался:

                                                                String objects are created by calling the constructor new String(). The String object wraps Javascript's string primitive data type with the methods described below. The global function String() can also be called without new in front to create a primitive string. String literals in JavaScript are primitive strings.
                                                                • 0
                                                                  wrapper'ы создаются для каждого из примитивных типов.

                                                                  alert(1.toString()); // создался wrapper, еще и наследование показано
                                                            • –1
                                                              Заметил ещё одну интересную особьенность конструкторов Яваскрипта, о которой, возможно, не всем известно.

                                                              Объект можно наследовать от «стандартных» классов, таких как DOMElement и XMLHttpRequest.
                                                              Для этого нужно вернуть из конструктора экземпляр «стандартного» объекта.

                                                              Пример 1:
                                                              var Element = function(tag, html, className, parent) {
                                                                var e = document.createElement(tag || 'div');
                                                                e.innerHTML = html;
                                                                e.className = className;
                                                                (parent || document.body).appendChild(e);
                                                                e.customMethod = function() { /*...*/ };
                                                                return e;
                                                              };
                                                              var test = new Element('span', 'Test', 'warning');
                                                              


                                                              Пример 2:
                                                              var Request = function(url, method) {
                                                                var req = new XMLHttpRequest();
                                                                req.open(method || 'GET', url, true);
                                                                /* other initialization */
                                                                req.customMethod = function() { /*...*/ };
                                                                return req;
                                                              };
                                                              var test = new Request('page.php');
                                                              


                                                              Варианты использования ограничиваются только вашим воображением.
                                                              Например, я использую класс (наследник от XMLHttpRequest) который генерирует ошибку по таймауту (т.е. если запрос не был завершён в течение 10 секунд).
                                                              • 0
                                                                Имхо, это не совсем то, что вы ожидаете: return e; и return req; как бы «замещают собой» действие оператора new. Уберите его — и всё будет точно так же.
                                                                • 0
                                                                  В примере выше:

                                                                  var test = new Request('page.php');
                                                                  test.send(); // вызов метода встроенного класса XMLHttpRequest
                                                                  test.customMethod(); // вызов своего метода
                                                                  


                                                                  Без return в конструкторе наследование от встроенного класса не получится.
                                                                  • 0
                                                                    Не. Всё будет работать без new.
                                                                    • 0
                                                                      Понял, что вы имели ввиду.
                                                                      Зато можно сделать вот так:

                                                                      var Request = function(url, method) {
                                                                        var req = new XMLHttpRequest();
                                                                        return req;
                                                                      };
                                                                      Request.prototype = {
                                                                        method1: function() {},
                                                                        method2: function() {}
                                                                      };
                                                                      var test = new Request('page.php');
                                                                      


                                                                      Т.е. кроме того, что расширять стандартный класс своими методами, можно ещё и реализовать множественное наследование.
                                                                      • 0
                                                                        Не работает этот метод
                                                                        alert(test.method1); // --> undefined
                                                                        • 0
                                                                          Ага, потому что test — это объект, созданный конструктором XMLHttpRequest, и прототип у него совершенно другой, нежели Request.prototype.

                                                                          Такая конструкция ни в каком виде не будет работать, потому что мы не можем изменить свойство [[Prototype]] у уже созданного объекта.
                                                                          • 0
                                                                            Хм, действительно, странно.
                                                                            (пошёл читать документацию и разбираться в своём коде)
                                                                • +3
                                                                  > Как я упоминал выше, типы данных String и Number могут быть как объектами, так и примитивными значениями.

                                                                  Немного неточно про типы. Встроенные типы — это Undefined, Null, Boolean, Number, String, Object.

                                                                  'текст' — примитивное значение типа String;
                                                                  new String('text') — не-примитивное значение типа Object.
                                                                  • 0
                                                                    Т.е. Undefined, Null, Boolean, Number, String — это все примитивные типы, а Object — единственный не примитивный тип, от которого производными являются всякие Array, Function, RegExp и т.д.?
                                                                    • +1
                                                                      Объектный тип один (Object Type), но объекты разные. Очень условно можно считать, что у объектного типа есть «подтип» или «тип объекта»: Array, String, Number… Если принять к сведению, что стандарт избыточно использует одни и те же имена для разных дел, то получаем:

                                                                      String — встроенный тип;
                                                                      String — конструктор, характеризующий подтип String встроенного типа Object.

                                                                      С примитивным значением встроенного типа (String, Number, Boolean) корреспондирует не-примитивное значение соответствующего подтипа (String, Number, Boolean) встроенного типа Object.

                                                                      ;)
                                                                      • 0
                                                                        Теперь более менее понятно. А Function точно стоит в одном ряду с какбы «подтипами» Array, String и т.д.?

                                                                        Просто typeof Array, new String, new RegExp и прочие всегда выдает object, а вот typeof new Function выдает function.

                                                                        Может функция это шестой, самостоятельный тип данных?
                                                                        • +2
                                                                          Согласно стандарту всего 9 типов данных, 3 из них не конечные, о них можно не вспоминать:

                                                                          A value is an entity that takes on one of nine types. There are nine types (Undefined, Null, Boolean, String, Number, Object, Reference, List, and Completion). Values of type Reference, List, and Completion are used only as intermediate results of expression evaluation and cannot be stored as properties of objects.

                                                                          Оператор typeof (и не он один) не отражает своим названием суть происходящего:

                                                                          null -> 'object', хотя это тип Null;
                                                                          function(){} -> 'function', хотя это тип Object, типа Function нет вообще;
                                                                          нет свойства вообще -> 'undefined', хотя значения нет вовсе, как и типа;
                                                                          объект хоста -> любая строка, хотя тип Object.

                                                                          Функция определенно важный объект среди прочих, отсюда и свой typeof на случай приблизительного определения, callable объект или не callable, это важно. Но если рассматривать «подтип» типа Object так, как это упорядочено в стандартe, то есть с точки зрения каждого конструктора, свойств прототипа, свойств порожденного объекта и проч., то «подтип» Function стоит в одном ряду с «подтипом» Array, String… и т.п.
                                                                          • 0
                                                                            Да что ж такое, ни на что в этом языке нельзя понадеяться )
                                                                            Пофиксил это место в статье, спасибо за полезную инфу.
                                                                            • 0
                                                                              афайк тип function в языке есть, ибо нельзя вместо функции подставить специально сформированный объект и не обрести при этом эксепшен «not a function». то, что его нет в спецификации, которую кое-как сляпали уже после формирования языка — проблемы спецификации.
                                                                              • 0
                                                                                После формирования какого языка кое-как сляпали?
                                                                                • 0
                                                                                  сабжевого
                                                                                  • 0
                                                                                    Я просто пытаюсь понять, что есть в этом 'афайк', наличие эксепшна не убедило… ;)
                                                                                    • 0
                                                                                      о да, спецификация другого языка — куда убедительнее ;-)

                                                                                      функция — это кортеж из обычного объекта и ассоциированного с ним исполняемого кода, то есть является совершенно другой структурой данных нежели просто объект и как следствие является отдельным типом. а в спецификации написано, что луна из сыра…
                                                                                      • 0
                                                                                        Понятно. Думал, вы знаете какие-то исторические факты, почему появились «проблемы спецификации», почему функции не выделены в отдельный тип.
                                                                    • 0
                                                                      Наверно из серии «хочешь понять как это работает — попробуй объяснить другому»
                                                                      Интересно, когда начнут появляться статьи о JS 1.8…
                                                                      • 0
                                                                        наверное, когда он будет доступен большинству пользователей
                                                                      • +1
                                                                        > Таким образом реализуется иерархия прототипов, или каскадное наследование.

                                                                        Делегирующее. Каскадное — это, когда точная копия создается от прототипа, и новые объекты имеют свои собственные свойства (при этом прототип уже не нужен). Поэтому, в JS — делегирующее прототипное наследование.

                                                                        > Примеси

                                                                        Используя эту терминологию, лучше уточнить, что она не «стандартная» для JavaScript.
                                                                        • 0
                                                                          Делегирующее — это общая характеристика наследования в JavaScript. Я пытался подобрать термин именно для обозначения того, что образуется иерархическое наследование, когда прототип имеет собственный прототип и так далее. Видимо, термин подобрал неудачно. Лучше, пожалуй, эту часть фразы убрать вообще.

                                                                          > Используя эту терминологию, лучше уточнить, что она не «стандартная» для JavaScript.
                                                                          А чем она нестандартная, если для нее нужны лишь родные средства языка, без оберток, и сама идеология соответствует идеологии JS (динамическое изменение объектов)?
                                                                          • 0
                                                                            > Видимо, термин подобрал неудачно

                                                                            Да, просто термин «каскадирование» уже использован для описания прототипирования (http://ru.wikipedia.org/wiki/Прототипно_программирование). В принципе, частный случай в JS есть — когда свойства создаются прямо в объекте, либо, когда объекты создаются через «фабрику». А частный — потому что цепь прототипов все-таки присутствует.

                                                                            > А чем она нестандартная, если для нее нужны лишь родные средства языка…?

                                                                            Нестандартная, имеется в виду, что такая сущность конкретно не выделяется в JavaScript. Однако, «примесь» — это более общее теоретическое понятие, и, в принципе, может быть реализовано в JavaScript.

                                                                            Главное, что понимать под ней и как будет выглядеть реализация. В Ruby, например, подмешивание не создает родные слоты в объекте — там создается хидден-класс, который вклинивается на первое место в цепи классов (т.е. свойство не найдется в самом объекте, затем будет искаться в подмешенном хидден-классе, затем в классе объекта, затем в родителе класса объекта и т.д.). Т.е. снова — делегация, а не каскадирование. С другой стороны, «примесь» можно рассматривать как расширение самого объекта, создавая родные слоты. В принципе, оба подхода можно реализовать в JS. Просто более точным было бы — «стандарт не описывает понятия примесь, но, этот теоретический механизм вполне реализуем в JS» (это не цепляние к словам, просто уточнение :)).
                                                                        • +1
                                                                          В этом коде, каждая строчка выполняет одно и то же действие: создает функцию (а функция, как мы помним, это полноправный объект) и делает переменную test ссылкой на нее. С точки зрения интерпретатора нет никакой разницы, каким именно способом мы воспользуемся — результат будет один.


                                                                          Это не соответствует действительности.
                                                                          • 0
                                                                            точнее, не «баг с замыканиями», а «особенности реализации FunctionExpression» в разных движках
                                                                            • 0
                                                                              В комментариях у меня там всё описано. В любом случае, утверждение из статьи действительности не соответствует.
                                                                              • 0
                                                                                То, что в статье под одну гребенку подогнаны и и FunctionDeclaration и FunctionExpression — это, конечно, недочет. А то, что там какая-та реализация обрабатывает «по-своему» поведение FunctionExpression — это дело десятое.
                                                                            • +1
                                                                              Да уж, сложна была судьба языка JavaScript, и пути его создателей неисповедимы )
                                                                              • 0
                                                                                Точнее, не JavaScript, а реализаций Java(ECMA)Script.
                                                                              • 0
                                                                                В JavaScript полно таких несуразностей — а всё потому что язык не был спроектирован, а был слеплен из разных кусков, которые довольно странно взаимодействуют между собой. Хуже него — только PHP, даже Perl — и тот не так ужасен.
                                                                                • 0
                                                                                  > а всё потому что язык не был спроектирован, а был слеплен из разных кусков, которые довольно странно взаимодействуют между собой. Хуже него — только PHP, даже Perl — и тот не так ужасен.

                                                                                  Ой, с удовольствием послушаю-ю :) Подождите, попкорн только возьму и поудобней усядусь =) Шутка, не обижайтесь :)

                                                                                  А, если серьезно, то — расскажите, почему Вы так думаете, откуда, на основе чего Вы пришли к таким выводам?
                                                                                  • 0
                                                                                    А, если серьезно, то — расскажите, почему Вы так думаете, откуда, на основе чего Вы пришли к таким выводам?
                                                                                    Я просто наблюдал за этими языками в то время как они создавались и за попытками сделать из дерьма конфетку. Результат… тот ещё. Самые ужасные ляпы удалось убрать (по-моему PHP4 был чуть ли не единственным языком в котором объекты передавались по значению, а не по ссылке), но чудес всё ещё хватает и там и там. Например пресловутая модель на основе протипов: штука простая и красивая но кому и зачем пришла в голову идея что прямой доступ к полу __proto__ людям не нужен? В результа вместо простых и понятных манипуляций приходится изобретать кучу странных приёмов. Но, конечно, переплюнуть PHP где до сих пор оператор == — не отношение эквивалентности, а операторы < и > — не отношения порядка весьма сложно.
                                                                                    • +1
                                                                                      приходится изобретать кучу странных приёмов
                                                                                      Так это же самое интересное! =)
                                                                                      Имхо, изобретения — это искусство, а постоянное использование «понятных манипуляций» — ремесленничество.