Pull to refresh

Comments 103

Я конечно извиняюсь, но зачем городить такой велосипед, не проще ли просто писать:
function MyClass() {
  ....
}

var obj = new MyClass();


А наследование делать так:

function MyFoo() {
   MyClass.apply(this, arguments);
  ....
}

inherits(MyFoo, MyClass);

var foo = new MyFoo();

когда же люди смирятся с тем, что в JS нет классов, но есть прототипы?
прошу прощения, хотел в основную коммент написать, промахнулся
Да не страшно.
Я думаю «классы» из javascript не уйдут, потому что людям так проще и понятней говорить и объясняться. Сказать «Класс» проще чем «Функция конструктор и ее прототип», но нужно полностью отдавать себе отчет в том что классы в js не тоже самое что в таких языках как C++ или Java.
Вот только то, что zxcabs написал — это не прототипное наследование JS, а грязный хак, который имеет целью сделать классы похожие на php
Поясню, на всяк случай, что эта ересь:
function MyFoo() {
   var self = this;

   MyClass.apply(this, arguments);

   this.method1 = function () {}
   this.method2 = function () {}
}


Это просто глупость. Аналог на php:
class MyFoo {
   function __construct () {
      $self = $this;

      $this->method1 = function () use($self) { };
      $this->method2 = function () use($self) { };
   }
}


В js есть только один нормальный способ создавать «классы», вот он:
function MyFoo() {
}

MyFoo.prototype = {
   method1: function () {},
   method2: function () {}
};


Все нормальные обёртки просто инкапсулируют повторящиеся названия и громоздкое наследование.
Давайте не приплетать мне то что я не говорил?
Где в моем примере это?
function MyFoo() {
   var self = this;

   MyClass.apply(this, arguments);


   //Вот это???? это не мое и придумывать того чего нет не надо.
   this.method1 = function () {}
   this.method2 = function () {}
}
Ох, прошу прощения. Я не заметил строку
inherits(MyFoo, MyClass);


Посчитал, что под наследованием вы имеете ввиду это (которое как раз и используется для «наследования» в вышеприведённом мной способе):
MyClass.apply(this, arguments);


А так да — вы правы.
А конструктор вернуть на место не забыли (при полном переопределении прототипа), последний листинг? И выше речь шла вроде бы не просто о создании классов, а о наследовании.
Там много чего не хватает) Я знаю, как правильно пользоваться прототипами ;)
Я уверен что знаете, это не был экзамен :)
Давайте уже наконец узнаем что делает магический inherits
exports.inherits = function(ctor, superCtor) {
  ctor.super_ = superCtor;
  ctor.prototype = Object.create(superCtor.prototype, {
    constructor: {
      value: ctor,
      enumerable: false,
      writable: true,
      configurable: true
    }
  });
};
Functional inheritance Крокфорда тоже ересь? А то я им часто пользуюсь. Ну то есть наследование мне наверное понадобилось от силы раз, но таким подходом, где методы определяются внутри функции пользуюсь постоянно.
У Крокфорда есть много сомнительных практик и идей. Имхо, лучше слушать других авторов.
Это мы в курсе, от того и сделана попытка реализации классов в JS.

Но спасибо за критику.
Если быстрее получается писать код с классами, то почему бы нет?
да я против ничего не имею, просто мне кажется тема избита, чтобы писать о ней в очередной раз (раз, два, три только на хабре, думаю есть ещё. Вот этот комментарий особенно красноречив)
Не стоит забывать, что над этим уже работают в комитете.
А так называемые велосипеды косвенно дают идеи для формирования будущего синтаксиса языка и многих библиотек.
Поддерживаю.
Хватит из JS делать C++! Остановитесь!
Да конечно же возможность использования наследования методом присваивания прототипу наследника родительского класса проста в использовании, но имеет небольшой ряд ограничений. При наследовании через прототипы все порожденные экземпляры будут использовать общие приватные переменные родительского класса, контекст в родительском классе будет иметь ссылку на родительский класс, который мы вызовем через superClass, что требует разработчику использовать каждый раз call, apply и т.д. для передачи родителю нужного контекста.

Так же в моей реализации есть возможность использования сеттеров/геттеров для более старых ИЕ. Понятно дело что подобное поведение не особо заслуживает на написание дополнительных библиотек. Но решение описанное в статье дает возможность окунуться в подобие Настоящих классов, где можно перехватить любой вызов методов, организовать псевдо абстракцию, приватность.

Но я конечно не буду утверждать о том что данная реализация лучшее в корне для всех. Нет, оно лишь дает возможность писать немного иначе, не так как привычно для JavaScript. И конечно же ни в коем случае не заставляю вас отказываться от более удобных для вас способов.
Нету в прототипном ООП классов. Нету и всё тут. Не надо тащить в прототипное ООП привычки из классового — всегда фигня получается.
Скажите без обид, вас подобные статьи/решения пугают? Или выводят из себя? Поймите, вас же никто не заставляет писать код иными способами, пишите так как писали и никто вас за это судить не будет. Но голословно утверждать словами «Не надо», не совсем корректно. Каждый для себя решает сам как ему готовить то или иное блюдо. Если вам статья не понравилась, проходите мимо. Если видите в статье глупость, тупость, незнание, флуд и т.д. Ставьте минус, если статья не глупа но вам она не по душе, просто читайте то что вам по душе.

Кому-то данная статья принесла пользу, уж точно не обратное. Либо пользу, либо ничего. Третьего ни как не может быть. Поэтому мысли «Не надо» оставьте при себе, или хотя бы дополняйте свои посты всем известным ИМХО.

ИМХО.
Понимаете, когда приходит человек в мир Java, например, и начинает писать что-то подобное про процедурный подход, его закидывают помидорами. Потому что Java — это классовое ООП и не надо в него сувать глобальные переменные.

А когда в мир JS приходите вы, то почему-то все рады. Но с чего вдруг? Чего полезного вы привнесли в этот мир? Эх…
Зачем изобретать велосипед? :(
Есть же уже куча реализаций — структуры в backbone, jquery widget, mootools class… И все равно многие почему-то считают, что собственный велосипед круче.
Автор прочитал Стефанова и вдохновился?
Отлично обстоит дело в coffeescript с имитацией (если можно так сказать) js классов и наследованием.
>> Автор прочитал Стефанова и вдохновился?
Нет я не читал его.

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

Плюс добавлю, что не все велосипеды хороши и удобны. Для кого-то удобен трех-колесный, для кого-то много-скоростной. Поэтому люди и пишут свои собственные что бы получить то, чего нет в других велосипедах. Плюс получают опыт и знания основ программирования, поэтому критиковать человека за то что он хочет учиться/развиваться, я считаю не разумно. ИМХО

Но все равно спасибо за критику.

В этом правда. Хочешь что то понять напиши сам. Но такие лесопеды лучше держать при себе и показывать в кругу друзей. А если решили показывать на широкой публике, то лучше 1000 раз погуглить аналоги и описать именно ту возможность которая отличает Ваш лесопед от всех остальных.
Так она есть, вроде бы)) Это немножко кроссбраузерные сеттеры/геттеры. Под ie<9. Или я ошибаюсь, и библиотек по их эмуляции — пруд пруди?
А как они реализованы?
Я столкнулся с тем, что this.parent и reset из MooTools страшно тормозит в Canvas приложениях — они забирают на себя до 25% всего времени исполнения программы, что даёт в результате существенный упадок фпс.
Ну, насколько я понял, они реализованы через vbscript-вставки… Хотя как точно, не могу вам ответить. Попробуйте написать автору)

Честно говоря, не понял, причём здесь MooTools — мы же, вроде как, о другой библиотеке речь ведём)
Мутулз для примера.
Т.е. аксессоры работают только в ие? О, о
Было бы глупо делать только для ИЕ ) Конечно же они работают во всех браузерах.
Вообще-то, в статье всё расписано)
Да, как отметил trikadin, для браузеров ИЕ ниже 9-й версии они реализованы через vbscript-вставки, но для остальных браузеров используется нативный метод Object.defineProperty. Вы можете скачать эту небольшую библиотеку и посмотреть пример того как это реализовано.
Всё, вчера посреди ночи был жутко невнимателен и почему-то неправильно понял идею. Тогда вот что скажу. Вместо того, чтобы выкладывать библиотеку, лучше бы выложили описание, как реализовать эти геттеры и сеттеры в старых ишаках. Уверен, такая тема обрела бы больше положительных отзывов в силу того, что она была бы не на тему «очередной враппер для создания классов», а на тему «интересные возможности в старых браузерах ИЕ, о которых не все знают»
Эти возможности к сожалению ограничены, и лишь подобная обертка дает возможность использовать те самые ограниченные возможности старых браузеров ИЕ, вы можете прочитать об ограничениях в одном из хабропостов
Mootools вообще нельзя использовать в современном мире, по аналогичным причинам что и Prototype.
Это из-за популярного заблуждения, что расширение встроенных прототипов — зло?
Некоторые апостолы считают, что если зло заложил сам создатель, то оно таковым не является :)
Некоторые свидетели называют злом то, что есть добро. И как всегда без вменяемой аргументации.

Я для себя определился только в одном — автор библиотеки общего назначения не должен навязывать прототипы, но использовать их в конечном проекте — это только во благо.
Некоторая аргументация была в прошлом нашем обсуждении :) повторять не стану. В общем смысле расширение базовых классов может привести к хаусу, и усложняет рефакторинг.

Согласен что в своих проектах, за которые отвечаешь только ты, можно делать что угодно. Но существуют другие типы проектов (например огромные многолетние JEE проекты), думаю если бы вам приходилось с такими работать, а тем более рефакторить, где каждый гавнюк пихал в проект первый нагугленный плагин или просто новый JS фреймворк (зачем мелочиться), то возможно вы бы изменили мнение.
Я работал с разными проектами. Иногда лучше бы они прототипы расширяли…
А я в последние годы работал в основном с большими какашками :( Поэтому недавно уволился, и теперь надеюсь получать новый позитивный опыт :)
jQuery тоже когда-то считали велосипедом, в прочем как и все что мы имеем сейчас.
В комментариях таких статей обязательно найдётся 1-3 человека, которые скажут, что «классы не нужны, есть прототипы», несмотря на то, что в 95% случаев классы строятся не на чем ином, как на прототипах — называют то же самое, но другими словами. Пора придумать стандартный дисклеймер для статей о классах.

«Мы на этот раз не будем описывать уток, поговорим исключительно о прототипах. Но иногда для краткости, если конструкция из прототипов крякает как утка, летает, как утка, и ведёт себя как утка, будем называть её уткой.»
Мне кажется такой подход избыточным.
1. Зачем вам наследование? Можете привести реальный пример? Нет, я понимаю когда оно используется на серверной стороне, но к чему городить это на клиенте. У вас так много подобных друг другу сущностей?
2. Мне всё видится намного проще:
+function()
{
    classes.Some = function(){ return this.init.apply( this, arguments ); }
    classes.Some.prototype =
    {
        init: function( options )
        {
        },

        // public
        someMethod: function(){}
    }

    function someProtectedMethod()
    {
    }
}();
В дальнейшем схему можно наворотить, например, создать классо-фабрику, которая будет организовывать 2 уровня прототипа и упростит работу с private методами (к примеру, их можно поместить в 1 объект, а затем про-bind-ить на экземпляр «класса»).
1. Зачем вам наследование? Можете привести реальный пример?

И правда зачем нам наследование, давайте все начинать с нуля… А то уж слишком мы умные и опытные стали, ведь быдь первобытным проще. САРКАЗМ

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

Я надеюсь ответил на ваш вопрос, о плюсах наследования можно почитать где угодно, и для этого не обязательно читать о JavaScript, ибо везде один и тот же принцип.
Я надеюсь ответил на ваш вопрос
Нет. Вопрос тот же — можете привести реальный пример? Всё таки JavaScript имеет своеобразное ООП, и до сих пор у меня не возникало необходимости в частом использовании прототипа дальше первого уровня (и уж тем более необходимости медленных get-ов и set-ов). В исключительной ситуации достаточно сымитировать аналог php-ых traits. Какого уровня js-приложения вы пишете?
Пишу я приложения как правило разного уровня, иногда огромные иногда мелкие. Но это ничего не меняет.

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

К примеру структура компонентов строится примерно таким образом:

1. Управляющий компонент — как правило обработка событий и общих методов.
2. Компонент канвы (форма) — расширен от управляющего, но имеет возможность зарезервировать область (не видимый прямоугольник) к примеру. На которую потом можно поместить любой компонент.
3. К примеру возьмем кнопку — расширяется от канвы, но дополняет возможность, прорисовки (заполнения области) нужным фоном/шкурой, обрабатывает события перехвата нажатия, выставление текста и т.д.
4. К примеру возьмем кнопку с картинкой — расширяется от обычной кнопки, но дополняет ее возможностью добавить рисунок (иконку) на кнопку, получается некий (BitmapButton).

И это все лишь небольшой пример того для чего нужно наследование, оно не просто упрощает жизнь программисту. Но и дает возможность создавать другие компоненты наследуя лишь те возможности которые необходимы.
Спасибо. Канва хороший пример, я бы там тоже намудрил с наследованием.
Немного не понятно, зачем делать set__? Ну, то есть я понимаю, что сеттер это такая штука, которая позволяет нам менять значения приватных свойств без написания отдельного метода для каждого и бла-бла-бла…
Но вот смотрите, вы в сеттере пишите
if (property===«text»)…
дальше будет
if (property===«color»)…
if (property===«style»)…
и т.д.
Вместо того, чтобы сделать для каждого свойства отдельный метод, вы для каждого свойства пишите отдельный if.
В чем profit? =| Кроме очевидного "мой скрипт написан с использованием ООП"?

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

В нашем случае мы лишь объявляем свойство со знака доллар, и парсер классов просто вешает на него событие перехвата. Ведь согласитесь что написать таким образом:
{
    $acsessor1: 0,
    $acsessor2: 0,
    set__: function( prop, val ) {
    },
    get__: function( prop ) {
    }
}

на мой взгляд намного читабельно и понятно, чем писать что-то вроде этого:
{
    set__acsessor1: function( val ) {
    },
    get__acsessor1: function() {
    },
    set__acsessor2: function( val ) {
    },
    get__acsessor2: function() {
    }
}

Хотя конечно оба варианта заслуживают рассмотрения, но как я упомянул чуть выше, скорость по моему мнению, все же важнее… Так как выяснить присутствие приставок set__|get__ к свойствам займет чуток больше времени выполнения, нежели простая проверка первой буквы у свойства.

Но я готов внести изменения и/или даже выпустить дополнительную ветвь данной разработки, которая будет реализовать подобные вещи иначе.
В результате при росте количества сеттеров/геттеров, все равно получаем
{
    $acsessor1: 0,
    $acsessor2: 0,
    set__: function( prop, val ) {
    
      if (prop == 'accessor1')
      {
        this.set_accessor1()
      }
      else if (prop == 'accessor2')
      {
        this.set_accessor2()
      }
    },
    get__: function( prop ) {
    
      if (prop == 'accessor1')
      {
        return this.get_accessor1()
      }
      else if (prop == 'accessor2')
      {
        return this.get_accessor2()
      }
    }
}

Что само по себе уже доставляет!

Так, что я бы все-таки подумал о какой-нибудь доктрине объявления геттеров и сеттеров. Если обратиться к опыту backbone.js, то логично было бы поступить так:
{
  _props : 
  {
    // Универсальные обработчики
    
    "set *" : function(value){},
    "get *" : function(value){},

    // Обработчики свойства name
    "set name" : function(value){},
    "get name" : function(value){},
    
    // Ну или более наглядно, но более экзотично
    "-> name" : function(value){},
    "<- name" : function(){}
  }
}
Как упомянул trikadin, можно организовать некий приватный объект который будет иметь setters/getters для нужных значений. Я считаю это разумным подходом, к примеру:

accessors: {
    accessor1: function( val, isSet ) {
        if ( !isSet ) {
            return myValue;
        }
        myValue = val;
    }
    accessor2: function( val, isSet ) {
        if ( !isSet ) {
            return myValue;
        }
        myValue = val;
    }
}
..............
{
    $accessor1: 0,
    $accessor2: 0,
    set__: function( prop, val ) {
        accessors[ prop ] && accessors[ prop ].call( this, val, true );
    },
    get__: function( prop ) {
        return accessors[ prop ] && accessors[ prop ].call( this );
    }
}
Тогда почему бы сразу не ввести в либу объект $accessors? не придётся дважды писать названия свойств, которым нужны сеттеры, не придётся придумывать велосипеды для велосипеда и так далее. ведь всё очень изящно:

classes.Class( "SuperButtonClass extends ButtonClass", {
	
	property : 'foo',
	anothProp: 'bar',
	
	$accessors: {
		accessor1: {
			get: function (currentValue) {
				return currentValue + ': getter';
			},

			set: function (newValue) {
				return 'setter: ' + newValue;
			}
		},

		accessor2: {
			get: function (currentValue) {
				return currentValue + ': second getter';
			},

			set: function (newValue) {
				return 'second setter: ' + newValue;
			}
		}
	},
	
	method: function () {
		return Math.random() * 42;
	}
});
Да вы правы, очень изящное решение… И плюс ко всему прочему, подобное решение еще ускорит работу конструктора классов.

Спасибо, реализую!
Пугающий уровень вложенности + теряется декларативность.
Хотя тут есть одна небольшая проблемка. Если родительский класс тоже реализует аксессоры, и они имеют одинаковые имена. Допустим:

classes.Class( "ButtonClass", {
    $accessors: {
        accessor1: {
            get: function (currentValue) {
                return currentValue + ': getter';
            },

            set: function (newValue) {
                return 'setter: ' + newValue;
            }
        }
    }
});

classes.Class( "SuperButtonClass extends ButtonClass", {
    
    property : 'foo',
    anothProp: 'bar',
    
    $accessors: {
        accessor1: {
            get: function (currentValue) {
                return currentValue + ': getter';
            },

            set: function (newValue) {
                return 'setter: ' + newValue;
            }
        },

        accessor2: {
            get: function (currentValue) {
                return currentValue + ': second getter';
            },

            set: function (newValue) {
                return 'second setter: ' + newValue;
            }
        }
    },
    
    method: function () {
        return Math.random() * 42;
    }
});

Как вызвать сеттер/геттер родительского класса? В моем случае это можно сделать так:
{
    $accessor1: 0,
    $accessor2: 0,
    set__: function( prop, val ) {

        this.parent.set__( prop, val );

        accessors[ prop ] && accessors[ prop ].call( this, val, true );
    },
    get__: function( prop ) {

        if ( accessors[ prop ] ) {

            return accessors[ prop ].call( this );
        }

        return this.parent.get__( prop, val );
    }
}


Хотя да, в вашем решении можно так же реализовать вызов нужного аксессора…
Все же оба варианта имеют право на жизнь. Какой именно стоит реализовать, об этом надо подумать.
В вашем случае будет зацикленность при двойном наследовании. Foo => Bar => Qux:

classes.Class( "Foo", {
	$accessor: 0,
	set__: function( prop, val ) {},
	get__: function( prop ) {
		console.log('Foo.get');
	}
});

classes.Class( "Bar extends Foo", {
	$accessor: 0,
	get__: function( prop ) {
		console.log('Bar.get');
		return this.parent.get__( prop, val );
	}
});

classes.Class( "Qux extends Bar", {
	$accessor: 0,
	get__: function( prop ) {
		console.log('Qux.get');
		return this.parent.get__( prop, val );
	}
});


Многие новички, которые создаёт обёртки для классов допускают эту ошибку.
А вы проверьте и увидите что ее не будет… Поверьте этот вопрос я решал обдуманно. Зацикливания не будет, это я вам уверенно могу сказать.
И поверьте, я далеко не новичок)
Ох. Мутулз-стайл решение, вижу)

Пришлось от него отказаться:
1. Очень осложняет дебаг — стек-трейс растёт в два раза быстрее
2. Тянет значительные ресурсы

Ещё вопрос — что будет, если ваш код будет использоваться с 'use strict'?
Ещё вопрос — что будет, если ваш код будет использоваться с 'use strict'?
Будет все хорошо, я проверял работу скрипта с установленным значением «use strict»;
вот лог что я увидел запустив ваш пример:

[10.04.2012 19:26:01] JavaScript — localhost/
console.log
Qux.get
[10.04.2012 19:26:01] JavaScript — localhost/
console.log
Bar.get
[10.04.2012 19:26:01] JavaScript — localhost/
console.log
Foo.get
В свое время jQuery так и поступил со своими виджетами, результат всем известен: появился backbone.js и теперь интерфейсами занимается он, почему?, потому что сделал создание UI декларативным. Люди хотят пользоваться сокращалками и ускорялками, даже, если они накладывают незначинельный оверлоад на систему, главное — цена конечного продукта. Ваши решения хороши и правильны, но, они не столь очевидны и просты.

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

Вопрос стоит лишь в том что вы хотите сделать, привнести «новое» или сделать «правильно». В любом случае, успехов.
Зачем городить огороды из нескольких уровней вложенности, дополнительной области видимости и кода, который для каждого объекта будет одинаков?
Верно подмечено, именно по этой причине я и решился перенять решение из PHP. Так как оно мне показалось более изящным и маложрущим ресурсы.
backbone.js, то логично было бы поступить так:
{
_props:
{
// Универсальные обработчики

«set *»: function(value){},
«get *»: function(value){},

// Обработчики свойства name
«set name»: function(value){},
«get name»: function(value){},

// Ну или более наглядно, но более экзотично
"-> name": function(value){},
"< — name": function(){}
}
}


Это не очень хорошее решение по той простой причине, что осложняется поиск при помощи IDE.
Не самый критичный момент. В случае необходимости, может добавиться в IDE.
Поверьте мне, тесная интеграция с IDE — ооочень критичный момент
Критичный для чего, для работы в IDE? Да! Для разработки же не критичный: сначала появляется новый инструмент, а затем средства обслуживания, а не наоборот.
Средства обслуживания появятся только при условии популярности инструмента.
До появления средств обслуживания инструментом придётся пользоваться без них.
В больших тяжёлых проектах без средств обслуживания слишком сложно.
До попадания в большие тяжелые проекты любой технологии нужно созреть. А если уж она, таааак хороша, что без нее никак, то…
Человек, который узнал вкус IDE с неё уже не слезет, имхо ;)
слезет, да еще как. На textmate/sublime text, или более хардкорно – на vim, или еще более хардкорно – на emacs.
Вообще истинные программисты кодируют бабочками
Я слез. Поэтому нам не найти общего языка в этом вопросе )
UPD: исправил ошибку в коде
{
  _props : 
  {
    // Универсальные обработчики-роутеры
    "set *" : function(prop, value){},
    "get *" : function(prop){},

    // Обработчики свойства name
    "set name" : function(value){},
    "get name" : function(value){},
    
    // Ну или более наглядно, но более экзотично
    "-> name" : function(value){},
    "<- name" : function(){}
  }
}
Я внес изменения в библиотеку, причем не только эти. Теперь немного иначе работает, не так как описанная выше, так же исправил кучу ошибок, добавил возможности, добавил ссылку на static.

Теперь работает иначе, примерно такой вид:
Class( "Factory", {

    // старый вариант, так же работает
    $accessor1: 0,
    $accessor2: 0,

    // сеттеры для универсальных свойств, указанных выше со знаком доллара
    __set: function( prop, value ) {
        //
    },

    // гкттер для универсальных свойств, указанных выше со знаком доллара
    __get: function( prop ) {
        //
    },


    // сеттер для свойства name
    "set name": function( value ) {
    },

    // геттер для свойства name
    "get name": function() {
    }
});

Теперь все классы по умолчанию создаются в контексте Class, то-есть наш класс Factory будет доступен через new Class.Factory()

Изменить контекст можно, так:
Class( window, "Factory", {} ); // создали в глобальном контексте

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

Но вот не знаю куда выложить новую версию, которая немного иная… Ибо статья описывает иную версию. Стоит ли менять статью!? Вот даже не знаю как быть.
Статью менять смысла нет. Нужно писать новую, когда набор изменений наберет критическую массу.

Но перед этим вам нужно решить один вопрос важный вопрос — найти свободную нишу и там обосноваться, сейчас вы позиционируетесь в месте спорного, но относительно решенного вопроса — классов в JS: сейчас им там не уютно, а в 2.0 они будут встроенными.

Давайте посмотрим по-другому — вы создали что-то другое, но при этом явно полезное и кроссбраузерное. Если перефразировать вас же, то это достаточно мощный и гибкий конструктор объектов, т.е. слово class лучше вообще убрать. Опять же создание объектов, само по себе, в JS не столь востребовано, потому что JS используется в основном для создания плагинов (которые более одного файла в 10kb занимают редко), следовательно, ваша ниша объемные/сложные приложения, где код часто повторяется, и при этом важна связанность объектов. Ага, а вот тут вы сталкиваетесь с тем что у вас есть наследование, которое js-сообщество не воспринимает, хорошо, тогда назовите это мета-наследование или гибкое прототипирование: Class.UI.button.create(), в объекте передаваемом в create можно указать что именно копировать из прототипа (например директивой meta), иначе брать всё. Можно еще добавить (ну это уже плюшка) общий (shared) объект, для соединений, конфигов и т.п.

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

Это не классовая библиотека, которую можно применить в любом проекте.)

Ну, это, как говорится, под каким соусом на это смотреть, мне вот, работая над почти сотней разнообразных «мелких» сайтов в месяц, допиливая то одно, то другое как раз не хватает целостного управления. Когда один написал, другой дополнил, третий, переписал, то что сделал первый, чтобы работало, как у второго, только чуть-чуть иначе, тяжело не то чтобы быстро подправить, а зачастую и разобраться в проекте, что вынуждает… писать свое заново! Полный безоговорочный Ы! Так, что не стоит думать, что цель создания каркаса приложения — мощность, как раз-таки цель — это гибкость! Вы правильно заметили, что каркасы приложений встречаются редко (спасибо за хороший контраргумент против вас же :). Но не потому ли они редки, что нет удобного инструмента для создания «целостных» приложений? Попытки-то есть, но это еще не то!

То что предлагает автор — это лишь начало большого пути всего языка, основа-основ — разделение на компоненты, напомню, когда-то и атом считали неделимым, дальше вы знаете что было )
Согласен с вами, все начинается с маленького)
Вы правы, это сложное и серьезное направление. Но свою библиотеку я все же планирую использовать на практике. Для начала хочу попробовать на ней написать что-то не совсем серьезное, то-есть что-то небольшое, что бы посмотреть как пойдет. А там глядишь и до чего-то серьезного дело дойдет. Но пока об этом рано говорить.
Да я понимаю что в будущем JS обзаведется нативной возможностью создания классов, не даром в браузерах слова class, private, protected и т.д. уже сегодня являются зарезервированными, хотя браузеры и не ругаются при их использовании в иных случаях.

Насчет убрать слово Class я тоже подумываю об этом, а то это слово уж слишком людей пугает, сложно понять почему, но таковы реалии. Правда пока не придумал на что заменить это слово. Но это мелочи.

Вот насчет Class.UI.button.create() я не совсем понял что вы имеете ввиду. То-есть нужен метод который будет срабатывать до создания объектов, в котором будут указываться правила?
Нет, я имею в виду, что таким образом можно организовать наглядное и простое наследование, абсолютно согласующееся с JS-way и не использующее темную силу:

// создаем класс UI, дефолтный для всех дочерних элементов интерфейса
// в описании указываем, что у него есть обязательный метод: draw
// и добавляем метод titleFromArray
Class.create('UI', { abstract : ["draw"], titleFromArray : function(arr){ } })

// создаем класс Button, который реализует метод draw и наследует метод titleFromArray от UI
Class.UI.create('button', { parent : ['titleFromArray'], draw : function(){ } })

Имена свойств abstract и parent даны для примера, и лучше их заменить чем-то более далеким от классического ООП, например, strict и inherit соответственно.
P.S. А создавать объекты нужно так же как раньше:
// Вариант с использованием контекста библиотеки
var button= new Class.UI.button()

// Ну или при создании указать контекст window и обращаться напрямую
var button2= new UI.button()
А это не будет слишком громоздким? Или я возможно не так понимаю вашу задумку. Но как я понял это если будет 20 потомков, то что бы вызвать последнего потомка, нужно прописать целую цепь:
var q  = new Class.UI.button.Lala.Trata.TirTir.Q.W.E.R.T.Y.U.I.P.A.S.D.F.G.H();
По моему это немного не красиво, да и еще и не удобно. Хотя возможно вы имеете ввиду что то иное. И я просто вас не понял.
Во-первых, для чего 20 потомков? Вы часто такое встречали? Таких программистов расстреливать надо, максимальный уровень наследования доступные здравому рассудку — 2-3 уровня.

Во-вторых, хвала js, потомка всегда можно вынести в отдельную переменную! var Button= UI.button, либо использовать для этого ваши же контексты.

В третьих даже при UI.button.dropdown список сохраняет наследственность и описательность, показывая что и где копать, да и в принципе ограничивается 3-уровнями, что приемлемо.

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

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

В-шестых, JS-way это js-way )
Да когда же вы угомонитесь? JavaScript прекрасно живет без этих ваших кривых велосипедов. Научитесь же, в конце-то концов, использовать прототипы.
Вы утверждаете что автор не использует прототипы, хотя бы не явно?
Господи, ну потрудитесь вы прочитать статью внимательно) Там главный профит в конце написан.
Прочитал. В чём профит ваших аксессоров в сравнении с простым обращением к свойствам объекта? Зачем в геттере/сетере писать километровый switch, когда можно просто написать несколько функций/методов(зависит от стиля)?

При наследовании через прототипы все порожденные экземпляры будут использовать общие приватные переменные родительского класса


Чтобы этого не было, не нужно присваивать эти самые переменные контексту родителя:
;var MyClass = (function() {
    var _privateVar1 = 0,
        _privateVar2 = 1,
        _privateVar3 = 3;
        
    var MyClass = function() {
        console.log(_privateVar1, _privateVar2, _privateVar3)
    }
    MyClass.prototype.getPrivateVar1 = function() {
        return _privateVar1
    };
    MyClass.prototype.setPrivateVar1 = function(value) {
        _privateVar1 = value
        return this
    };
    return MyClass
});


Ваш К.О.
В чём профит ваших аксессоров в сравнении с простым обращением к свойствам объекта?

В том, что можно не писать каждый раз object.setProperty(propertyName, value), или как-то так, а делать обычное присваивание свойста: object.property= value, что выглядит читабельнее и естественнее. Дальше, упрощается обращение к свойствам и запись — их не приходится делить на функции getProperty и setProperty (конечно, можно сделать одну, проверяющую количество аргументов, но это тоже так себе выход) — чтение и запись свойства производятся через обращение к самом свойству (obj.property), а все изменения внутри объекта производят аксессоры.

Зачем в геттере/сетере писать километровый switch

Вообще-то, «километровый switch» можно и не писать. Обычно сеттеров (и геттеров) — один-два на объект. Но если больше — можно сделать объект setters, в котором по имени изменяющегося св-ва будут храниться нужные функции. Тогда достаточно будет просто сделать так: setters[propertyName]().
Спасибо, К.О., все мы знаем что такое сеттеры. Только вопрос остался открытым: в чём профит от этого:

...
if ( property === "text" ) {
    // то меняем текст кнопки на новое значение
    this.node().innerHTML = value;
}
...


И чем оно отличается от этого:

..
setText: function(value) {
    // то меняем текст кнопки на новое значение
    this.node().innerHTML = value;
}
..


Вообще-то, «километровый switch» можно и не писать. Обычно сеттеров (и геттеров) — один-два на объект. Но если больше — можно сделать объект setters, в котором по имени изменяющегося св-ва будут храниться нужные функции. Тогда достаточно будет просто сделать так: setters[propertyName]().


Тут всё просто:

;var MyClass = (function() {
    var _properties = {
        'a': 1,
        'b': 2,
        'c': 'test string'
    }

    var MyClass = function() {}
    
    MyClass.prototype.property = function(name, value) {
        if (typeof name === 'undefined') throw "No name specified"
        if (_properties.hasOwnProperty(name)) {
            if (typeof value === 'undefined') {
                return _properties[name]
            } else {
                _properties[name] = value
                return this
            }
        } else throw "Property with name '" + name + "' not found";
    }
    return MyClass
}());

var inst = new MyClass()

inst.property('a') // => 1
inst.property('b') // => 2
inst.property('c') // => test string
inst.property('d') // => "Property with name 'd' not found"

inst.property('a', 10)
    .property('b', 15)
    .property('c', 'new string')
    .property('d', 'fail') // => "Property with name 'd' not found"


inst.property('a') // => 10
inst.property('b') // => 15
inst.property('c') // => new string
inst.property('d') // => "Property with name 'd' not found"

inst.property() // => "No name specified"


Хотя, конечно, это извращение чистой воды. Если есть необходимость «видеть» свойства, то нужно не тратить время на велосипед, а использовать возможности языка и к чёрту инкапсуляцию и всю эту императивщину

...
var MyClass = function() {
    this.a = 1
    this.b = 2
    this.c = 'test string'
}
...
Спасибо за интересную статью, материал для размышления и прочее. Конечно, соглашусь с большинством комментаторов, что js совершенно другой язык. Ему не нужны классы, на нем прекрасно и легко писать. Да, ему не хватает синтаксического сахарка для тех мест, которые используются чаще всего, но писать на нем, как на других языках и незачем! Напишите лучше о вставках на vb, мне кажется, это реально крутое решение, которое вами не было освещено в должном объеме.
Респект и прочие почести, пишите еще.
На хабре уже было, по крайней мере я читал год или два назад
Скажите, а какая была практическая цель написания этой библиотеки, кроме как «сделать как в PHP»?
Практической цели вовсе и не было, по большей части просто захотелось сделать возможность все же использовать возможности VB которые так никто и не пытался толком использовать. То-есть попытки разные были, но я пошел немного другим путем. Организация аксессоров в ИЕ старых версий имеет ряд ограничений, одно из них это отсутствие возможности добавления динамических свойств, то-есть VB не дает создать новое свойство если то не было объявлено явно.

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

Статья конечно же не корректно описана, мне нужно было именно на аксессорах и заострить внимание, возможно это дало бы иной результат реагирования людей на статью. Понимаю что классов в JS нет, я не подумал о том что попытка их сделать или даже обсуждать приведет людей к панике.

PS. Статьи никогда не писал, это мой первый опыт так сказать. Я старался писать ее так как обычно сам предпочитаю читать, и я никогда лично сам не реагирую на статьи паникуя и тряся всем телом, выплевывая слюни в попытке рассказать автору статьи что он не прав. Для меня любая статья, в первую очередь просто статья, а если считаю ее не нужной, просто забываю о ней.
Sign up to leave a comment.

Articles