Простое наследование в стиле Ruby для Javascript

http://gist.github.com/474994
  • Перевод
Сегодня наткнулся довольно занятный gist на github'е.

Вот его код:
/*
*  def.js: Простое наследование в стиле Ruby для Javascript
*
*  Copyright (c) 2010 Tobias Schneider
*  This script is freely distributable under the terms of the MIT license.
*/
(function(global) {

 // Используется, чтобы сохранить суперкласс и "плагины" для
 // дальнейшего использования
 var deferred;

 // Добавляет родителю "плагины"
 function addPlugins(plugins) {
  augment(this.prototype, plugins);
  return this;
 }

 // Копирует аттрибуты и методы объекта "source" в объект "destination"
 function augment(destination, source) {
  for (var key in source) {
   destination[key] = source[key];
  }
 }

 // Пустой класс, необходим для наследования
 function Subclass() { }

 function def(klassName, context) {
  context || (context = global);
  
  // Создаем класс в указаном контексте (по умолчанию global)
  var Klass =
  context[klassName] = function Klass() {
   // Если вызывается как конструктор
   if (this != context) {    
    // Если в классе есть "init" метод, то он может вернуть
    // другой класс/объект
    return this.init && this.init.apply(this, arguments);
   }
   // Вызывается как функция
   // defer setup of superclass and plugins
   // Сохраним в "deferred" класс и "плагины", которые могли быть
   // переданы первым аргументом
   deferred._super = Klass;
   deferred._plugins = arguments[0] || { };
  };

  // Добавляем метод, чтобы запускать его в контексте
  Klass.addPlugins = addPlugins;

  // Будет вызываться эта функция,
  // если класс создается без наследования
  deferred = function(plugins) {
   return Klass.addPlugins(plugins);
  };

  // Благодаря функции valueOf
  // будет осуществляться наследование
  deferred.valueOf = function() {
   // Возьмем класс, который мы сохранили в "deferred"
   var Superclass = deferred._super;
   
   // Если класса нет - значит мы должны
   // вернуть сам класс, чтобы вести себя
   // как нормальная valueOf функция
   if (!Superclass) return Klass;

   // Создаем клон супер класса с пустым конструктором
   Subclass.prototype = Superclass.prototype;
   // Создаем объект
   Klass.prototype = new Subclass;

   // Добавляем superclass классу
   Klass.superclass = Superclass;
   Klass.prototype.constructor = Klass;
   
   // Добавляем "плагины", сохраненые в deferred
   return Klass.addPlugins(deferred._plugins);
  };
  
  // Возвращаем deferred -
  // функцию, принимающую атрибуты и методы, а затем создающую класс
  return deferred;
 }

 // Выносим def из замыкания
 global.def = def;
})(this);




Так же, приводится пример использования:
// Пример

def ('Person') ({
 'init': function(name) {
  this.name = name;
 },
 'speak': function(text) {
  alert(text || 'Здравствуй, меня зовут ' + this.name);
 }
});

def ('Ninja') << Person ({
 'ask': function() {
  this.speak('Ты веришь, что здесь моя скорость и сила зависят от моих мускулов?');
 }
});

var ninjy = new Ninja('Морфеус');
ninjy.speak();
ninjy.ask();



Не похоже на javascript'овый синтаксис, верно?

Как же это работает:


Немного о порядке выполнения:
var a = {valueOf: function(){alert(2)}}
var b = function(){alert(1);}
a << b();


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

Таким образом, когда вы пишите:
def('a') ({a:1});
def('b') << a ({b:1});

.

Сначала вызовется функция a, созданная в первой строке, с аргументами {b:1}. В это время, в deferred запишется класс, которым нужно дополнить, и набор аттрибутов/методов дополняемого класса.
Затем выполнится def('b').valueOf, возьмется класс и «плагины» из deferred, создастся класс на основе a, и ему запишутся атрибуты/методы.

Все довольно просто и лаконично!
Спасибо jdalton за ссылку на этот гист!

P.S.
Переведенный и дополненный вариант
Поделиться публикацией
Похожие публикации
Ой, у вас баннер убежал!

Ну, и что?
Реклама
Комментарии 23
  • +20
    В Javascript и так простое, удобное и функциональное наследование.
    Но, похоже, желающие из элегантного функционала сделать электровеник «в стиле языка N» не переведутся никогда.
    • +1
      и этот N один и тот же :)
      • +1
        Согласен, что не нужно переносить модель ООП других языков на довольно красивую идею в JS. Но всё же этот скрипт довольно интересен как эксперимент и понимание работы JS.
      • 0
        магия :)
        • +3
          если бы этот дсл упрощал работу… а так, поменяли шило на мыло…
          • +1
            В сущности, да — «шило на мыло» — не важно, написать в обертке "_extends" или "<". Здесь лишь можно отметить идейную вариацию с задействованием valueOf. А по поводу упрощенного «DSL» — да, мне больше Coffeescript нравится, хотя я его и не использовал.

            P.S.: автору статьи: кстати, идея и изначальная реализация принадлежит Tobias Schneider (и у него заведен репозиторий для этого проекта — github.com/tobeytailor/def.js). И, хоть это и написано в копирайте скрипта, большинство, почему-то, упоминают John Dalton.
            • 0
              Дело в том, что John Dalton переписал полностью gist Tobey, оставив от него только идею. А на github Tobey залил уже модифицированную John'ом версию.
              • 0
                Да, я в курсе, но тем не менее.

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


                «Только идею»? ;) Идея — первична и ядро всех новаторских вещей. Реализация — дело десятое. Удобная реализация — это хорошо.
          • +4
            Прикольно конечно в образовательных целях, но JS сам по себе и без того классная штука.
            • +1
              прожженного рубиста сразу видно по слову «klass»
              • 0
                Просто class — зарезервированное слово, поэтому не только рубисты в реализациях наследования его используют
            • 0
              Осилил. Жесть какая.

              Логика немного смахивает на работу обфускаторов. Типа:
              javascript:alert(({a:1,b:2,toString:function(){return 'Привет, '}}) + [«запутывать!»,«любитель „].reverse().join(''))

              Я про подмену стандартного метода valueOf и тайного знания о том, как он дружит с операцией сдвига “<<» + транспорт через deferred. Браво!

              Это меня вначале зацепило — «Как же им удалось переопределить оператор << ?» — подумал я. Оказалось — никак :)

              Логика красива, синтакс красив, применение — ну не знаю, не уверен, что кому-то понадобится.
              • 0
                Кстати, мысля в том же русле, можно вместо '<<' заюзать, скажем, '+':

                // line 60: deferred.valueOf = function() {
                // replace with:
                deferred.toString = function() {



                // line 79: return Klass.addPlugins(deferred._plugins);
                // replace with:
                return Klass.addPlugins(deferred._plugins) && 'yes, i am ';


                // И теперь вместо def('a') << b() пишем def('a') + b()
                def ('Ninja') + Person ({ //…
                • 0
                  Вообще, Рубевый «сахар» наследования — это < одна угловая скобка ;)

                  То же (+ добавлен this.base() через caller): github.com/DmitrySoshnikov/def.js Но, т.к. caller — deprecated и бросает исключение в strict mode, то можно переделать на wrapping функциями.
                  • 0
                    а что собственно мешает использовать одинарную скобку вместо двойной и в яваскрипте? ;-)
                    • 0
                      Ничего не мешает, так же valueOf вызовется. В моей версии — одинарная в примере.
                      • 0
                        у меня тут мысль возникла… а можно ли сделать так, чтобы вызывался valueOf, но возвращался сам объект?

                        например, +XXX вернёт число, а хотелось бы, чтобы вернуло само XXX
                        • 0
                          кроме очевидного XXX() разумеется
                          • 0
                            Нет, нельзя. valueOf должен вернуть primitive value, а это строка или число
                            • 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, объект
                              • 0
                                угу, жаль, ни одна конструкция языка не приводит к их вызову без попытки приведения к примитиву =(
                            • 0
                              Хотя, Chrome выпендривается с одной <. Вернул <<.

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

                      Самое читаемое