• Javascript наследование для чайников
    0
    Понятно.
  • Javascript наследование для чайников
    0
    Ну а все-таки (без «паразитов» ;)). В чем смысл?

    P.S.: я достаточно глубоко знаю обе парадигмы.
  • Javascript наследование для чайников
    0
    > Да ладно?

    Ага. Конкретика имеет меньшее значение, чем одинаковые методики разрешения имет свойств/методов и динамика в целом. Естественно, относительно конкретики, есть отличия. Но основа та же делегация.

    Другой пример, как уже было отмечено — CoffeeScript. На своем уровне абстракции и в своей семантике он классовый (точнее, классово-прототипный). Но как и в Python, классы там всего лишь сахар над «делегацией».

    P.S.: и Ruby такой же. И Lua.
  • Javascript наследование для чайников
    0
    А смысл? Если удобней просто this.super.
  • Javascript наследование для чайников
    0
    > Ошибка тут будет в том, чтобы считать Python классовым языком — скорее, он прототипный

    Абсолютно верно, Python — прототипный язык с классам в виде «сахара». Такие же классы в CoffeeScript. Такие же планируются (и сейчас активно обсуждаются в es-discuss) в следующей версии JS.
  • Javascript наследование для чайников
    0
    > ВСЕГДА

    Не всегда ;) gist.github.com/1330574 — можно вполне себе нормально использовать this.super, без хардкода имен классов.

    Существуют и другие методики, e.g.: github.com/DmitrySoshnikov/def.js/blob/master/def.js#L80

    Помимо этого, удобного this.super можно добиться через wrapper'ы.
  • ES5 Harmony Proxy — меняем семантику JavaScript внутри самого JavaScript
    0
    Да, похожий сахар будет. И называется это rest и spread:

    function foo(...arguments) {
      console.log(arguments);
    }
    
    foo(1, 2, 3); // [1, 2, 3]

    И обратно (spread):

    function foo(a, b, c) {
      console.log(a, b, c);
    }
    
    var args = [1, 2, 3];
    
    foo(...args); // 1, 2, 3

    Слайд 23 — здесь.
  • ES5 Harmony Proxy — меняем семантику JavaScript внутри самого JavaScript
    0
    Основной протест был в поддержке не только «функций-вызовов», но и функций, как объектов. I.e.: не только так: foo.bar(), но и foo.bar.apply(...). Записи должны (?) быть семантически эквивалентны. Достичь этого можно лишь всегда возвращая функцию-активатор. Но я тоже ратовал за isCall флаг.
  • Замыкания и объекты JavaScript. Переизобретаем интерпретатор
    +11
    Хорошая статья, поздравляю (Хабр снова начинает становится «тортом»! ;))

    Ниже пара дополнений.

    P.S.:

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


    Кстати, у GCC есть расширение — он все-таки позволяет создавать вложенные функции и их даже можно вернуть наружу (т.е. upward funarg). Достигается это за счет техники трамплинов (trampoline).

    Также, если нет upward funarg'a (иными словами, если вложенная функция используется только локально внутри родительской функции, «не убегая» наружу вверх), то техника лямбда-поднятия (lambda lifting) также решает эту проблему. Именно она используется в Паскеле для вложенных функций.

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

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


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

    Некоторые прототипные языки, например Io, используют так же делегирующее наследование, но метод, который осуществляет «сцепление» предка с потомком назван почему-то clone ;)

    каждый конструктор «висит» над своим объектом


    В общем случае, порожденный объект не зависит от своего конструктора.

    Пара конструктор+прототип (в этом примере: B()+B.prototype) играет ту же роль, что класс в классическом ООП


    Собственно, это и есть «класс» в JavaScript (только без сахара), поскольку реализует классовый code reuse. Как эта пара превращается в класс при добавлении сахара можно хорошо видеть, например, на классах CoffeeScript. Или Python. Да-да, Питон — это такой же делегирующий «прототипный» язык, как и JavaScript, только в нем добавлен сахар. Именно это я отмечал, говоря, что разницы «прототип vs. класс» недостаточно, всегда нужно задействовать все сопутствующие механизмы (первоклассные сущности, динамика и т.д. — здесь сводная таблицы — "классификация классов").

    Этот пример с Python'ом достаточно интересен для анализа. Как только появляется «сахар» — всё, язык сразу же классовый и сразу же понятный. Но стоит убрать из Питона сахар — мы получим тот же JavaScript в этом аспекте. Таким образом, «сахар решает». А «класс» — это не какое-то ключевое слово class в языке, и даже не возможность создавать классы с помощью этого слова. Класс — это возможность программировать в классифицирующем стиле. А уж как это делается — с ключевым ли словом class или ключевым словом function — это по большому счету дело десятое.

    наследование сделано по-старинке, через new


    Такой же сахар над делегацией для классов (как в Python, Ruby или CoffeeScript), кстати, довольно активно обсуждался в es-discuss (возможно мы даже увидим классы в JS — такие же как в Coffee). Так что, «по-старинке» скорей всего здесь некорректно. Разница ведь в данном случае не в «старинках», а в методах code reuse'a. Если нам нужен классовый реюз (т.е. (1) возможность генерировать объекты по шаблону и (2) наследовать строгую иерархию), то мы используем классовый реюз. Если нет — пожалуйста, прототипный (т.е. (1) не нужно генерировать много однотипных объектов и (2) «наследую от кого хочу»). Это два разных код реюза и оба применимы. Здесь на Хабре был подробный тред в комментах на эту тему.

    Ссылка на родительский объект (__parent__ в листингах 6 и 7) во многих реализациях называется __proto__


    Не совсем удачно выбранное псевдо-имя, т.к. __parent__ — это реальное (хоть и нестандартное) свойство как раз, чтобы связываться объекты активации в scope chain. Больше подошло бы __delegate__ — оно отражает суть механизма и показывает, что по идее, может быть несколько прототипов (делегатов), если это свойство, например, будет списком. Так, к примеру, Io и Self поддерживают множественное наследование, как раз расширения список делегатов. Если, вдруг, станут интересны эксперименты, для JS тоже такое реализуется на прокси-объектах.

    В случае вызова функции не как метода объекта, this по-умолчанию указывает на глобальный объект


    Да, там есть сложные заморочки, когда, и вроде кажется, что функция вызвана как метод, но все равно this становится глобальным (или undefined в strict mode):

    var o = {m: function() {this;}};
    
    o.m(); // o
    (o.m)(); // o
    
    (o.m = o.m)(); // global
    (o.m || o.n)(); // global
    


    В целом же, еще раз — статья отличная ;)
  • Топ-11 самых частых ошибок в JavaScript
    +2
    > Дуглас Крокфорд — тут вобще не в тему

    Ну, Крокфорд (официально) является членом TC-39. На практике «его» идеи тоже отражаются в ES (например, Object.create, JSON.parse, etc).

    Насчет Yahoo, он мне сам сказал, что уже давно особо ничем там не занимается ("...nothing. Really."), ** пинает и разъезжает по конференциям :D
  • ES5 Harmony Proxy — меняем семантику JavaScript внутри самого JavaScript
    0
    Предлагалось, обсуждалось — не будет принято, т.к. backward compatibilities рулят развитием JS и основной «лейтмотив» — «не ломать WEB» (т.е. то, что уже написано). Некоторые вещи все равно будут необратимо сломаны, но это уже в зависимости от «migration tax», т.е. что достойно, чтобы поломать, а что не так и важно (как например отрицательные индексы, без которых, в принципе, можно прожить; но с ними слаще, да ;)).
  • ES5 Harmony Proxy — меняем семантику JavaScript внутри самого JavaScript
    0
    > a[1]

    a[-1]
  • ES5 Harmony Proxy — меняем семантику JavaScript внутри самого JavaScript
    0
    > но имхо уже через-чур глядеть в код и видеть отрицательный индекс массива (которого никогда не могло быть) и задавать себе вопрос «wtf?!»

    Да ну, всего лишь сахар для более удобного обращения к индексам с конца: a[1] вместо a[a.length - 1].

    И такая парадигма используется в разных языках — те же Ruby, Python. Хотите используйте (с сахаром), хотите не используйте (без сахара, с шумом).
  • ES5 Harmony Proxy — меняем семантику JavaScript внутри самого JavaScript
    0
    Именно поэтому я и создал свою удобную обертку для работы с объектами, где все эти хуки можно навешивать простыми присваиванием в рантайме. См. выше п. 2 «Метапрограммирование на уровне объекта». Такая модель отражает, например, подход Python (откуда ES много чего позаимствовал).

    Основная задача (по мнению TC-39), чтобы foo.get = 10 не означало, что ты «испортил» мета-get-хук. С другой стороны, почему «испортил», если ты сам за свои объектов и следишь — так и появляются обертки типа приведенных выше.

    P.S.: кстати, уже ES5 подход с их статическими методами, типа Object.getPrototypeOf, Object.keys и прочее тоже именно из-за разделения мета- и прикладного уровня. Мне в свое время (год назад) этот подход тоже не понравился, но что сделано — то сделано, и с точки зрения разделения мета-уровня и «чтобы не испортить» это осмысленное решение и в целом правильное. Хотя, повторю, синтаксически получается много «шума», отступов и т.д.

    P.S.[2]: ты еще не видел как будут value-proxy себя вести (это те, что для перегрузки операторов). Опять же — в Python это все на __magic__ методах, в ES куча синтаксического шума и телодвижений для этого. Причины — вновь разделение мета-уровня и оптимизация по памяти.
  • ES5 Harmony Proxy — меняем семантику JavaScript внутри самого JavaScript
    +8
    Молодец, что переводишь статьи / создаешь русскоязычные версии. Чтобы русское JS-сообщество не отставало — это полезное и хорошее дело.

    По поводу прокси, от себя могу добавить несколько примеров, когда я экспериментировал с ними в самых первых имплементациях SpiderMonkey:

    Из интересного:

    1. Массивы с отрицательными индексами — как в Python:

    var a = Array.new(1, 2, 3);
    
    console.log(a[-1]); // 3
    console.log(a[-2]); // 2
    
    a[-1] = 10;
    console.log(a); // 1,2,10
    console.log(a[-1]); // 10
    

    2. Мета-программирование на уровне объекта — фишки с __count__, __noSuchProperty__, __delete__, __get__, __set__ и т.д. Это только ради академического интереса (вообще, прокси и создавались, чтобы разделить мета- и прикладной уровни).

    Можно менять трэп обычным присваиванием функции:

    var foo = Object.new({
      __get__: function (name) {
        console.log('__get__:' + name);
      }
    });
    
    foo.bar; // __get__: bar
    foo.baz; // __get__: baz
    
    foo.__get__ = function (name) {
      console.log("New get: " + name);
    };
    
    foo.bar; // New get: bar

    3. Сахар для разделенного мета-уровня

    var foo = Object.new({
      // normal object level
      data: {
        x: {
          value: 10,
          writable: true
        }
      },
      // meta-level
      meta: {
        noSuchProperty: function fooNoSuchProperty(name) {
          console.log("noSuchProperty: " + name);
        },
        noSuchMethod: function fooNoSuchMethod(name, args) {
          console.log("noSuchMethod: " + name + ", args: " + args);
        }
      },
      // a prototype of an object
      proto: Object.prototype
    });
    
    foo.bar; // noSuchProperty "bar"
    foo.bar(1, 2, 3); // noSuchProperty: "bar" -> noSuchMethod: "bar", args: 1, 2, 3
    
    // normal-level doesn't disturb meta-level
    foo.noSuchProperty = 10; // assign to normal-level, but not to meta-level
    // still noSuchProperty of the meta-level is called
    foo.baz; // noSuchProperty: "baz"
    
    // the same with noSuchMethod
    foo.noSuchMethod = 20; // assign to normal-level, but not to meta-level
    // still the meta-level noSuchMethod is activated
    foo.baz(10, 20, 30); // noSuchMethod: "baz", args: 10, 20, 30

    4. Оптимизированный __noSuchMethod__ — функция-активатор реюзается.

    5. Делегирующие примеси (как в Ruby) или множественное наследование. Используется именно реюз и делегация, а не просто копирование в собственные свойства.

    И т.д. — больше можно найти здесь.

    P.S.: был длинный тред с дебатами, где мы обсуждали, нужно ли добавить флаг isCall в get трэп — чтобы различать выражение вызова: foo.bar от foo.bar(), но пока решили не делать (что затрудняет имплементацию __noSuchMethod__ — нужна функция-активатор, которая является значением любого свойства).
  • Магия JavaScript: arguments
    +3
    Описание семантики объекта arguments в ES5/non-strict ES5 JS кодом: gist.github.com/539974

    В Хроме помимо упоминавшегося бага, был еще баг с удалением индексов arguments.

    Так же, учтите, что в strict-ES5 аксессор для индексов arguments больше не создается (т.е. обычные статические копии формальных параметров).

    И, касательно Harmony (aka ES6 или ES.next) arguments вообще будет удален и заменен на rest — полноценный массив.
  • Проверка на отрицательный ноль
    +1
    некий Аллен Вирфс-Брок

    Так, к слову, Allen Wirfs-Brock — один из участников TC-39 (technical committee 39) — группа людей занимающаяся стандартом ECMA-262.

    А вообще, приведенный код в этом топике, это не его версия, а Dave Herman'a (он тоже из TC-39). Версия Allen'a в его блоге описана.
  • Обёртки для создания классов: зло или добро?
    0
    Всё, я понял, где у нас «нестыковка» (ну, образно — на самом деле, и ты, и я правы).

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

    Я же, действительно, размышляю об общих структурах и закономерностях, которые прослеживаются во всех языках. Я действительно размываю эти рамки, потому что вижу в них только конкретику отдельно взятой реализации; реализации общих теоретических схем, ещё раз повторю, разработанных идеологами безотносительно каких-то там ECMAScript'ом, Jav(a), C, CoffeeScript'ов, Python'ов и т.д.

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

    показать его особенности, его своеобразие, его красоту

    Воот. Вот именно здесь, я понимаю, «немного печально», «что мой язык кем-то вдруг размывается и смешивается с »немоими" языками".

    Да, всё верно. Стояла бы задача «выделить терминологические и идеологически различия и рассказать о них новичкам» — вперёд, никто бы не стал ничего «размывать».

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

    Поэтому да, если хочешь показать именно обособленности новичкам — конечно удобно выделять их. Если мы беседуем в рамках теории языков программирования, их семантики и дизайна — есть «second-class static classes» и есть «first-class dynamic classes» (с возможными вариациями, например, «first-class static classes» — которые «замороженные»), и каждая технология, стратегия может быть использована в отдельно взятом конкретном языке.

    «классы в языке — это возможность генерировать и классифицировать»… ну, тогда везде они… классы эти.

    Да. Как бы утрированно не звучало — да, именно. Если нужно классифицировать. Повторю, если не нужно — можно использовать бесклассовый (прототипный) реюз и генерацию.

    P.S.:

    всё-таки продолжаю бороться со сложностью — классов в js нет ;-)

    Ну, либо так — тоже избавление от сложности. Только с альтернативной точки обзора ;)
  • Обёртки для создания классов: зло или добро?
    0
    В языке нет «second-class static classes» (по твоему определению — «ноль жёстко заданных связей и поведения»). Естественно, и очевидно, что таких классов нет и не было (ну а как? — везде динамика и всё «first-class» — это «физически» невозможно). Здесь всё верно.

    А вот, ещё раз, «first-class dynamic classes» — есть и всегда были. Только без сахара.

    Здесь, либо мы тогда Python и CoffeeScript должны называть только делегирующе-прототипными, либо, всё-таки (и правильно) — допускать, что в таком же (делегирующе-прототипном) языке мы можем выделить понятие «класса» как (повторю) «возможность генерировать и классифицировать».

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

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

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

    Надоело каждый раз вручную ставить прототип (и все остальные приблуды, типа return'a и т.д.)? — всё, сделали «за кадром» это действие.

    Т.е., видишь, да — самые обыкновенные практические задачи-причины.

    В итоге, можно остановиться на каком-то определённом этапе. А те, кто увидят ещё какую-то сложность, могут дальше от неё избавиться. Захотелось обособить функцию классифицирующей генерации от обычной — всё, заменили слово «function» на «class» в грамматике. Но раскладывается-то («десахарится», desugars) оно внутри всё равно на эту же самую «function» — jashkenas.github.com/coffee-script/#classes

    И т.д., и т.п.

    Не захотели делать mutable объекты — пожалуйста, заморозь (freeze) их. Кстати, если мы избавимся от динамики и изменяемости — мы подпадём под твоё определение «класса» с «ноль жёстко заданных связей и поведения»? Кстати, цитата Brendan'a, вырванная из контекста, тоже говорит лишь об изменяемости, т.е. о «second-class static classes», которых, естественно, не было в ES3. Но в ES5 с Object.freeze — пожалуйста, убери изменяемость, если хочется.

    После заморозки — не захотели делать всё это first-class, пожалуйста, сделай second-class (не будут у тебя классы объектами, а просто — «статические лепёшки» в коде, которые могут генерировать и классифицировать объекты — точно так же, как могли до этого — обычные «first-class dynamic functions»).

    Т.е. разницы нет как классифицировать — с ключевым ли словом «function» или с ключевым словом «class». Главное — это сами причины и нужды и, как следствие, методика, стилистика. А синтаксис — это уже даже не десятое, а сотое дело.

    P.S.: в самом «низу», на уровне ячеек памяти, в абстракции, она тоже классифицирована (типизирована). Как, думаешь, различается указатель на int или указательно на float. Или, что, например, этот блок памяти может хранить int — ведь ячейки «все равны»? Точно так же — по классификационному тегу. Есть ячейки данных, и есть специальная ячейка, которая хранит тип/класс того, что в неё кладут — для проверки.

    P.S.[2]: Ещё раз — разница между «классовое» и «прототипное» только идеологическаяклассифицированный реюз и генерация и неклассифицированный (хаотичный, прототипный) реюз и генерация. Технически же, это может быть одна и та же система.
  • Обёртки для создания классов: зло или добро?
    0
    … связка «конструктор + протоип» с классифицирующим стилем программирования.
  • Обёртки для создания классов: зло или добро?
    +1
    > Стиль программирования решает.

    Однозначно.

    > ноль жёстко заданных связей и поведения

    Воот. Во всяком случая, я уже вижу, что ты подразумеваешь под словом «класс». И это именно то, о чём я упоминал выше — «second-class static classes».

    Однако, ты (да и Brendan) с какой-то стати отмёл «first-class dynamic classes».

    Я поясню терминологию, чтобы не было путаницы (не для тебя, а для тех, кто возможно не в курсе).

    — первоклассная (first-class) сущность — такая, которая может быть использована в качестве обычных данных (создана литерально в рантайме, передана в качестве аргумента функции, возвращена из функции и т.д.);

    — второклассная (second-class) сущность — соответственно, та, которая не может быть использована в качестве, описанном выше.

    Дальше. Динамически изменяемая (dynamic mutable) сущность — понятно, в рантайме расширяй объект сколько хошь. Static (или даже static immutable) — соответственно, наоборот.

    Так вот эти понятия с понятием классификации по большому счету вообще никак не связаны. По «менее большому счету» — мы можем лишь принять second-class static систему с возможностью генерировать объекты и классифицировать их. Конечно же, никакие фичи делегации, динамики и прочего из JS здесь не будут действовать.

    Но если мы возьмем делегирующую first-class dynamic систему с «сахаром», то мы тоже получим понятие класса. При этом мы свободно сможем дописывать в рантайме новые слоты, добавлять новые методы, менять класс/прототип объекта динамически, передавать класс в качестве аргумента и т.д. dmitrysoshnikov.com/ecmascript/ru-chapter-7-1-oop-general-theory/#dinamicheskaya-klassovaya-organizatsiya

    Так вот, почему, если Python (или CoffeeScript) делегирующие языки, их называют классовыми? Потому что в них всего лишь по-дефолту, «из коробки» уже написан этот сахар, эта обёртка с использованием ключевого слова class. Но внутри-то они — абсолютно те же, что и JS.

    Еще раз, все знают, что JS — прототипный язык (ладно, я тоже, допустим, знаю). Но при этом в нём, как и в любом делегирующем языке, можно программировать в классовом стиле. И да, именно стиль решает. А точнее не стиль, а понятие «возможность генерировать и классифицировать» — вот, что называется в абстракции «класс». А конкретные реализации — это уже дело десятое. web.media.mit.edu/~lieber/Lieberary/OOP/Delegation/Delegation.html
  • Обёртки для создания классов: зло или добро?
    +2
    Язык программирования — для программиста. Необходимость в языке программирования и программировании в целом — это, повторю, борьба со сложностью. Чем более высоко абстрактен язык, тем эта борьба становится легче — путём человек-приближенных, высоко-абстрактных понятий ты описываешь ту или иную систему.

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

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

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

    В некоторых языках (Java, C++, др.) класс напрямую связан с понятием типа. Но в общем же случае, для того, чтобы иметь возможность классифицировать, нужен лишь классификационный тег (classification tag). Иногда называют тегом типизации (type tag) или, опять совсем просто — типом. Это некоторое свойство (или набор свойств в общем случае), которое позволяет однозначно определить объект твоей классификации от объекта с таким же набором свойств.

    Как я отметил выше, первая главная операция в ООП — это возможность порождать (construct) объекты. Если мы примем, что объект — это просто «набор свойств и поведений», то синтаксически мы можем прям так эти свойства со значениями и описывать:

    var point = {x: 1, y: 2};


    Если нам нужно три «точки», то:

    var a = {x: 1, y: 2};
    var b = {x: 3, y: 4};
    var c = {x: 5, y: 6};


    Но очевидно, что повторное использование кода (code reuse) методом «copy-paste» — это примитивная методика программирования (что если нам понадобится 100, 1000 точек?). Поэтому нам нужен некий удобный сахар для генерации объектов со схожей структурой. И самый простой способ сделать это — это использовать обычную функцию (да-да, точно так же, как мы всегда делаем, когда код повторяется больше двух раз — так называемое «правило двух» — «если код повторяется больше двух раз, вынеси (инкапсулируй) его в функцию».

    function makePoint(x, y) {
      return {
        x: x,
        y: y
      };
    }
    
    var a = makePoint(1, 2);
    var b = makePoint(3, 4);
    var c = makePoint(5, 6);


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

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

    Но. Чтобы классифицировать твои объекты, недостаточно их просто генерировать. Нужен классифицирующий тег. Т.е. у собаки тоже 4 конечности, но она отличается от человека.

    Например, это может быть имя функции, которая создала объект. А лучше — ссылка на неё. Можно поместить этот тег прямо в самом объекте, но очевидно, что можно выделить вспомогательный спец. объект и задействовать делегацию — так, тег будет храниться в единственном экземпляре и будет доступен всем нашим объектам. Назвать его можно прямо и в лоб — constructor (вспомогательный объект при этом назовем prototype):

    makePoint.prototype = {
      constrcutor: makePoint
    };


    И модифицированный способ создания:

    function makePoint(x, y) {
      return {
        __proto__: makePoint.prototype,
        x: x,
        y: y
      };
    }


    Повторим, посредством делегации мы добьемся следующего эффекта:

    a.constructor // a.constructor -> not found, a.__proto__.constructor -> found == makePoint.


    Разработав всё это (нашу фабрику «makePoint» по генерации объектов-точек), мы придём к выводу, что некоторые вещи можно оптимизировать, убрать сложность (мы ж со сложностью боремся!):

    1. уберём (да, для сахара) этот префикс «make», заменим его ключевым словом «new»;

    2. сделаем return опциональным (правда, на кой ляд он нам в общем случае? — пусть система сама знает, что надо вернуть объект);

    3. будем создавать вспомогательный объект автоматически за кадром и устанавливать __proto__ тоже «по-тихому» (ты хочешь повторять это ручками каждый раз? — я нет);

    4. введём какое-нибудь специальное слово (например, this), чтобы ссылаться на созданный объект и не писать каждый раз эти скобочки { и }. Хотя, тут двояко, иной раз со скобками удобней.

    5. что-нибудь ещё придумаем…

    В итоге, мы получим систему конструкторов в JS:

    function Point() { // no "make"
      this.x = x;
      this.y = y;
    }
    
    var a = new Point(1, 2);
    var b = new Point(3, 4);
    var c = new Point(5, 6);
    
    a.constructor // Point


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

    Технические хаки (типа «я щас подменю свойство constructor» и т.д.) — меня, как программиста, вааапще не касаются. Удобно мне так классифицировать объекты — я классифицирую. Не буду я ничего ломать, подменять constructor, использовать поломанный [[Class]] в каком-нибудь движке и т.д.; я буду просто программировать в классифицирующем стиле. Если мне надо.

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

    В итоге, в JS (как и в любом другом языке с делегацией) можно программировать как с классами, так и без них. Но в классовой системе нельзя программировать в бесклассовом режиме.
  • Обёртки для создания классов: зло или добро?
    +2
    Нет такого словосочетания «proper classes». Тот, кто написал это в ES3 спецификации имел в виду «second-class (static) classes». «Классы» в JS ничем не отличаются от классов в Python. И как они устроены изнутри, можно хорошо видеть на примере сахара CoffeeScript.

    Ещё раз — возможность классифицировать — вот что такое «классы». И у JS всегда была (и есть) эта возможность. Здесь можно реюзать код «хаотично» (прототипно) и систематизировано (классово). Всё остальное — это уже дополнительный идеологических сахар.

    Основное в ООП это:

    1. возможность генерировать объекты (т.е. всего лишь комплексные структуры с некоторым набором полей) — constructors.

    2. осуществлять выборку значений этих полей — selectors.

    Всё. Остальное: реюз, классификация, наследование, приватные свойства (для разделения уровня абстракций, но никак не для параноидального сокрытия) — это всё дополнительные сущности.

    Главное — это общие законы и методики. ECMAScript — это всего лишь конкретика, которая этим же (используемым в других языка) законам «подчиняется». Т.е. теоретические абстракции и идеология разработаны давно (и задолго до ES) — и все языки в той или иной мере просто воплощают эти абстрактные сущности для достижения главной цели программирования — борьбой со сложностью.

    Так что, в JS есть классы и всегда были. Но, да, без сахара ;) Хотя, конечно, эти размышления не для новичков.

    Но вообще, по моему мнению, изначальные преподы JS тоже допустили большую ошибку. Они начали превозносить JS в ранг чего-то особенного, что доступно только избранным и «никак не доступно сходу тупому Си-программеру, который привык мыслить в парадигме классов». В итоге, понятие «класса» было несправедливо отнесено к ненужному. На самом же деле, как я отметил, технически и большой разницы-то может и не быть. Да и в самих прототипах ничего сложного нет и не было. А идеологическая — да — возможность генерировать неклассифицированные объекты.

    Я ж говорю, сам ES использует понятие класса для классификации объектов — свойство [[Class]], так почему же его бояться и относить к ненужному? Повторю, оно не нужно, если я хочу просто зареюзать код от другого объекта. А когда мне нужно классифицировать объекты — зачем мне отказываться от классов?

    P.S.: меня интересует знание вопроса из самой глубины, а не просто цитаты конкретных людей из TC-39, которые так же корректируют спецификации, ошибаются и т.д.
  • Обёртки для создания классов: зло или добро?
    +4
    В ECMAScript нет (пока) сахара для классов. Но возможность классифицировать (т.е. способоность создавать объекты по заданному шаблону и с нужным классификационным тегом) там была всегда.

    Основная разница классового и прототипного ООП (кто там жаловался на слово «идеология»? ;) именно идеологическая.

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

    С точки зрения цепи наследования большой разницы нет:

    Прототипное: own -> proto -> proto -> proto -> null
    Классовое (абстрактно): own -> class -> superclass -> superclass -> null.


    При этом, в прототипном (делегирующем) программировании, цепь наследования так же линейна.

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

    Наличие сущности „класс“ в языке не отменяет возможности классифицировать объекты. С этой точки зрения, например классы Python'a — это всего лишь синтаксический сахар того же делегирующего наследования, которое используется в ECMAScript. Для этого класс, естественно, должен быть сам первоклассным объектом. Здесь классы делятся на:

    — Первоклассные динамические (first-class dynamic classes); примеры: Python, Ruby, CoffeeScript, ECMAScript (без сахара);

    — Второклассные статические (second-class static classes); примеры: C++, Java.

    Так вот, JS всегда имел „first-class dynamic classes“ в виде пары „конструктор + прототип“. Т.е. конструктор умеет генерировать объекты и задавать им классифицирующий тег (например, свойство constructor). При этом, как классифицировать объект — с сахаром ли (ключевое слово class) или без него — это уже второстепенный вопрос.

    Самое интересное, что ECMAScript сам использует понятие „класса“ для классификации своих объектов. И, не удивительно, использует классифицирующий тег с таким же именем — [[Class]].

    var a = {};
    var b = [];
    
    // both are objects
    alert([typeof a, typeof b]); // "object", "object"
    
    // but have different classification tags
    var getClass = Object.prototype.toString;
    
    
    alert([getClass.call(a), getClass.call(b)]); // "[object Object]", "[object Array]"


    Поэтому, в JS есть (и всегда была) возможность классифицированного и неклассифицированного (прототипного) программирования. И каждое можно/нужно использовать, исходя из случая. А вот в классовой системе уже нельзя (в общем случае) программировать прототипно.

    Если нужно просто повторно зареюзать код от другого объекта — зачем мне класс — используйте Object.create. Если я решил, что у меня должно быть несколько объектов одной классификации (например, компонент „кнопка“ в UI системе) — с какой стати мне отказываться от классов?

    P.S.: недавно я отвечал в подобной теме:

    www.mail-archive.com/jsmentors@googlegroups.com/msg00513.html
    www.mail-archive.com/jsmentors@googlegroups.com/msg00503.html (примерное начало темы).

    P.S.[2]:

    — Есть ли в JS классы?

    — Сахара нет, но классы (т.е. возможность классифицировать) — есть и всегда была. А сахар допиливается обёртками (так же, как и Python, так же, как и в CoffeeScript).

    P.S.[3]: jashkenas.github.com/coffee-script/#classes
  • Простое наследование в стиле Ruby для Javascript
    0
    Хотя, Chrome выпендривается с одной <. Вернул <<.
  • Простое наследование в стиле Ruby для Javascript
    0
    например, +XXX вернёт число

    В общем случае, это ToNumber (вызывается плюсом) вернёт число; valueOf может вернуть объект (так, например, возвращает стандартный Object.prototype.valueOf — результатом будет this-value).

    var o = {};
    o.valueOf() === o; // true

    Ты можешь переопределить valueOf любого объекта и вернуть всё, что надо. Только если оба — и toString, и valueOf будут возвращать не примитив, а объект, тогда может быть TypeError в некоторых операциях (например, в конвертации ToNumber). При этом, они (до ошибки) всё равно будут вызываться.

    function foo() {}
    
    //console.log(foo.valueOf() === foo); // true
    
    foo.valueOf = function () {
      console.log("foo.valueOf is called");
      return {};
    };
    
    foo.toString = function () {
      console.log("foo.toString is called");
      return {};
    };
    
    +foo;
    
    -->
    
    "foo.valueOf is called"
    "foo.toString is called"
    
    TypeError "can't convert foo to number"

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

    foo.valueOf(); // OK, объект
    foo.toString(); // OK, объект
  • Простое наследование в стиле Ruby для Javascript
    0
    Ничего не мешает, так же valueOf вызовется. В моей версии — одинарная в примере.
  • Простое наследование в стиле Ruby для Javascript
    0
    Да, я в курсе, но тем не менее.

    оставив от него только идею


    «Только идею»? ;) Идея — первична и ядро всех новаторских вещей. Реализация — дело десятое. Удобная реализация — это хорошо.
  • Простое наследование в стиле Ruby для Javascript
    +1
    В сущности, да — «шило на мыло» — не важно, написать в обертке "_extends" или "<". Здесь лишь можно отметить идейную вариацию с задействованием valueOf. А по поводу упрощенного «DSL» — да, мне больше Coffeescript нравится, хотя я его и не использовал.

    P.S.: автору статьи: кстати, идея и изначальная реализация принадлежит Tobias Schneider (и у него заведен репозиторий для этого проекта — github.com/tobeytailor/def.js). И, хоть это и написано в копирайте скрипта, большинство, почему-то, упоминают John Dalton.
  • Простое наследование в стиле Ruby для Javascript
    0
    Вообще, Рубевый «сахар» наследования — это < одна угловая скобка ;)

    То же (+ добавлен this.base() через caller): github.com/DmitrySoshnikov/def.js Но, т.к. caller — deprecated и бросает исключение в strict mode, то можно переделать на wrapping функциями.
  • Строительные блоки Ruby
    +1
    def a
      yield
    end
    a{ return  0 } # => LocalJumpError: unexpected return

    Это относится к ключевому слову return. Оно, ведь, не просто опционально (как пишут некоторые статьи); в определённых местах эта опциональность имеет смысл:

    def a
      yield 10
    end
    a {|n| n + 10 } # 20
  • Строительные блоки Ruby
    +1
    > а заложено в язык

    Точнее — в идеологию. И эта идеология присуща многим языкам. Просто большинство кроме статично-классового ООП и не видали ничего больше. Естественно, легче назвать это «религией» и «откреститься» от этого.
  • Строительные блоки Ruby
    +2
    Причём здесь Ваше отношение к альтернативной идеологии ООП и «религия»? Python использует делегирующую модель наследования с объектами первого класса. Абсолютно та же картина в Javascript.

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

    В Ruby тоже можно свободно получить доступ (если того захочет сам программист) к инкапсулированным данным.

    Найдите время, прочитайте полностью: dmitrysoshnikov.com/ecmascript/ru-chapter-7-1-oop-general-theory/
  • Маленький тест на знание JS
    0
    100.toString.length — синтаксическая ошибка, должно быть 100..toString.length: первая точка определяется, как отделение дробной части, вторая — уже доступ к свойству.
  • Маленький тест на знание JS
    0
    Хотя, Zakas опубликовал пост с ответами на мой quiz: www.nczonline.net/blog/2010/02/23/answering-soshnikovs-quiz/ (хотя, я вижу две ошибки в объяснении, одна незначительная (на #3), другая — очень (на #6)).
  • Маленький тест на знание JS
    0
    Zakas-у, вероятно, просто сообщили об этом. В любом случае, повторю, мне эта позиция («этот красивый комментарий публикую сейчас, а этот нет, а этого — вообще не публикую») не близка. Удалять спам и троллинг — это, конечно, совершенно, другое, хотя, я с этим не сталкивался в своих статьях. А Ваш ответ звучит так, как будто, Zakas «сделал одолжение и соизволил», что ещё больше является забавным пафосом, который меня всегда «умиляет» в людях.

    Насчёт последнего комментария, этот человек (Asen Bozhilov) тоже говорил, что Zakas модерирует полезные, но не/мало/лицеприятные комментарии: groups.google.ru/group/comp.lang.javascript/browse_thread/thread/2b9dcc0b88ff8ffa?hl=en#9255f32d6271e3bd
  • Маленький тест на знание JS
    0
    Мой коммент был 5-ый, перед комментом kangax-a, который уже опубликован ;) Ладно, проехали, мне от этого ни тепло, ни холодно. Это PR и не больше. Вернее, боязнь анти-PR-а.
  • Маленький тест на знание JS
    0
    В принципе, здесь поля для беседы не было, я просто отметил, что тест — слабее, тех которые он упоминает в описании теста, в любом случае, поблагодарил за работу. Возможно, он счел это не нужным, ни о чем. Мне без разницы. Я ни один (ни один.) коммент никогда не скрывал. Объективная оценка — это главное. Я не опускал тест (а он подумал, скорей всего, именно это), ни говорил, что он плохой, что он тупой, что он не нужный, еще что-то. Я просто отметил, что он по уровню проверки знаний — слабее, чем те, которые он сам же и упоминает.

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

    > Я его ловил, указывал на ошибки, премодерация меня не удавила

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

    Ладно, хрен ты с ним.
  • Маленький тест на знание JS
    0
    Да, и мне, собственно, по барабану, прошел мой коммент модерацию или нет, я правил не нарушал, не троллил, не был rude (как у него написано в Note: к комментам), говорил по делу. Но понял, что для этого человека в первую очередь важен PR, а не реальное положение дел, «наука» (ECMAscript) и объективные оценки.
  • Маленький тест на знание JS
    0
    > Ваш коммент не прошел модерацию? Интересно, что же Вы написали.

    Примерно (с переводом): «Объективно, этот quiz легче, чем те, которые упоминаются (в целом, эти quiz-ы не требуют каких-то глубоких анализов); уровень сложности — 3, у kangax-a был на 4, но в любом случае — спасибо.»