РАСШИРЯЕТСЯ задача: макросы функции препроцессора и классоподобные операции - PullRequest
7 голосов
/ 25 июля 2010

Фон

Я использую препроцессор C для управления и «компиляции» полу-больших проектов javascript с несколькими файлами и целями сборки.Это дает полный доступ к директивам препроцессора C, таким как #include, #define, #ifdef и т. Д. Изнутри javascript.Вот пример сценария сборки, поэтому вы можете проверить пример кода:

#!/bin/bash
export OPTS="-DDEBUG_MODE=1 -Isrc"
for FILE in `find src/ | egrep '\.js?$'`
do
  echo "Processing $FILE"
  cat $FILE  \
  | sed 's/^\s*\/\/#/#/'  \
  | cpp $OPTS  \
  | sed 's/^[#:<].*// ; /^$/d'  \
  > build/`basename $FILE`;
done

Создайте каталог src и build и поместите файлы .js в src.


Удобные макросы

Изначально я просто хотел получить препроцессор для #include и, возможно, несколько #ifdef с, но я подумал, не так липриятно иметь несколько удобных макросов?Последовали эксперименты.

#define EACH(o,k)     for (var k in o) if (o.hasOwnProperty(k))

Круто, так что теперь я могу написать что-то вроде этого:

EACH (location, prop) {
  console.log(prop + " : " location[prop]);
}

И это расширится до:

for (var prop in location) if (location.hasOwnProperty(prop)) {
  console.log(prop + " : " location[prop]);
}

Как насчет foreach?

#define FOREACH(o,k,v)   var k,v; for(k in o) if (v=o[k], o.hasOwnProperty(k))
// ...
FOREACH (location, prop, val) { console.log(prop + " : " + val) }

Обратите внимание, как мы крадемся v=o[k] внутри условия if, чтобы оно не нарушало фигурные скобки, которые должны следовать за вызовом этого макроса.


Классоподобный ООП

Давайте начнем с макроса NAMESPACE и неясного, но полезного шаблона js ...

#define NAMESPACE(ns)    var ns = this.ns = new function()

new function(){ ... } делает некоторые полезные вещи.Он вызывает анонимную функцию в качестве конструктора, поэтому для его вызова не требуется дополнительных () в конце, а внутри него this относится к объекту, создаваемому конструктором, другими словами, к самому пространству имен,Это также позволяет нам вкладывать пространства имен в пространства имен.

Вот мой полный набор классоподобных макросов ООП:

#define NAMESPACE(ns) var ns=this.ns=new function()

#define CLASS(c)      var c=this;new function()

#define CTOR(c)       (c=c.c=this.constructor=$$ctor).prototype=this;\
                      function $$ctor

#define PUBLIC(fn)    this.fn=fn;function fn
#define PRIVATE(fn)   function fn
#define STATIC(fn)    $$ctor.fn=fn;function fn

Как видите, эти макросы определяют многие вещи как вVariable Object (для удобства) и this (по необходимости).Вот пример кода:

NAMESPACE (Store) {

  CLASS (Cashier) {

    var nextId = 1000;

    this.fullName = "floater";

    CTOR (Cashier) (fullName) {
      if (fullName) this.fullName = fullName;
      this.id = ++nextId;
      this.transactions = 0;
    }

    PUBLIC (sell) (item, customer) {
      this.transactions += 1;
      customer.inventory.push(item);
    }

    STATIC (hire) (count) {
      var newCashiers = [];
      for (var i=count; i--;) {
        newCashiers.push(new Cashier());
      }
      return newCashiers;
    }
  }

  CLASS (Customer) {

    CTOR (Customer) (name) {
      this.name = name;
      this.inventory = [];
      this.transactions = 0;
    }

    PUBLIC (buy) (item, cashier) {
      cashier.sell(this, item);
    }
  }
}

А как насчет EXTENDS?

Так что это подводит меня к вопросу ... как мы можем реализовать EXTENDS какмакрос для переноса обычного «клонирования прототипа, копирования свойств конструктора» js наследование прототипа?Я не нашел способа сделать это, кроме как требовать, чтобы EXTENDS появлялся после определения класса, что глупо.Этот эксперимент требует расширения или бесполезен.Не стесняйтесь изменять другие макросы, если они дают одинаковые результаты.

Редактировать - они могут пригодиться для EXTENDS;перечисляя их здесь для полноты.

#define EACH(o,k)   for(var k in o)if(o.hasOwnProperty(k))
#define MERGE(d,s)  EACH(s,$$i)d[$$i]=s[$$i]
#define CLONE(o)    (function(){$$C.prototype=o;return new $$C;function $$C(){}}())

Заранее благодарим за любую помощь, совет или живое обсуждение.:)

1 Ответ

2 голосов
/ 26 июля 2010

Я думаю, что только что выполнил свой собственный вызов. Я добавил второй (необязательный) аргумент в макрос объявления CLASS для суперкласса объявленного класса.

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

Вот текущие воплощения моих классоподобных макросов ООП:

// class-like oo

#ifndef BASE
  #define BASE  $$_
#endif

#define COLLAPSE(code)      code

#define NAMESPACE(ns)       var ns=BASE._ns(this).ns=new function()

#define CLASS(c,__ARGS...)  var c=[BASE._class(this),[__ARGS][0]]; \
                            new function()

#define CTOR(c)             BASE._extend($$_##c,c[1],this); \
                            c=c[0].c=$$_##c; function $$_##c

#define PUBLIC(fn)          BASE._public(this).fn=fn;function fn

#define PRIVATE(fn)         function fn

#define STATIC(fn)          BASE._static(this).fn=fn;function fn

// macro helper object

COLLAPSE(var BASE=new function(){

  function Clone(){};

  function clone (obj) {
    Clone.prototype=obj; return new Clone;
  };

  function merge (sub, sup) { 
    for (var p in sup) if (sup.hasOwnProperty(p)) sub[p]=sup[p]; 
  };

  this._extend = function (sub, sup, decl) {
    if (sup) {
      merge(sub, sup);
      sub.prototype=clone(sup.prototype);
      sub.prototype.constructor=sub;
    };
    if (decl) {
      merge(sub.prototype, decl);
      decl._static=sub;
      decl._public=sub.prototype;
    };
  };

  this._static=this._ns=this._class=function (obj) {
    return (obj._static || obj); 
  };

  this._public=function (obj) {
    return (obj._public || obj); 
  };

})

... вот тестовое пространство имен ...

//#include "macros.js"

NAMESPACE (Store) {

  CLASS (Cashier) {

    var nextId = 1000;

    this.fullName = "floater";

    CTOR (Cashier) (fullName) {
      if (fullName) this.fullName = fullName;
      this.id = ++nextId;
      this.transactions = 0;
    }

    PUBLIC (sell) (item, customer) {
      this.transactions += 1;
      customer.inventory.push(item);
    }

    STATIC (hire) (count) {
      var newCashiers = [];
      for (var i=count; i--;) {
        newCashiers.push(new Cashier());
      }
      return newCashiers;
    }
  }

  // Customer extends Cashier, just so we can test inheritance

  CLASS (Customer, Cashier) {

    CTOR (Customer) (name) {
      this.name = name;
      this.inventory = [];
      this.transactions = 0;
    }

    PUBLIC (buy) (item, cashier) {
      cashier.sell(this, item);
    }

    CLASS (Cart) {

      CTOR (Cart) (customer) {
        this.customer = customer;
        this.items = [];
      }
    }

  }
}

... и вот вывод ...

var $$_=new function(){ function Clone(){}; function clone (obj) { Clone.prototype=obj; return new Clone; }; function merge (sub, sup) { for (var p in sup) if (sup.hasOwnProperty(p)) sub[p]=sup[p]; }; this._extend = function (sub, sup, decl) { if (sup) { merge(sub, sup); sub.prototype=clone(sup.prototype); sub.prototype.constructor=sub; }; if (decl) { merge(sub.prototype, decl); decl._static=sub; decl._public=sub.prototype; }; }; this._static=this._ns=this._class=function (obj) { return (obj._static || obj); }; this._public=function (obj) { return (obj._public || obj); }; }
var Store=$$_._ns(this).Store=new function() {
  var Cashier=[$$_._class(this),[][0]]; new function() {
    var nextId = 1000;
    this.fullName = "floater";
    $$_._extend($$_Cashier,Cashier[1],this); Cashier=Cashier[0].Cashier=$$_Cashier; function $$_Cashier (fullName) {
      if (fullName) this.fullName = fullName;
      this.id = ++nextId;
      this.transactions = 0;
    }
    $$_._public(this).sell=sell;function sell (item, customer) {
      this.transactions += 1;
      customer.inventory.push(item);
    }
    $$_._static(this).hire=hire;function hire (count) {
      var newCashiers = [];
      for (var i=count; i--;) {
        newCashiers.push(new Cashier());
      }
      return newCashiers;
    }
  }
  var Customer=[$$_._class(this),[Cashier][0]]; new function() {
    $$_._extend($$_Customer,Customer[1],this); Customer=Customer[0].Customer=$$_Customer; function $$_Customer (name) {
      this.name = name;
      this.inventory = [];
      this.transactions = 0;
    }
    $$_._public(this).buy=buy;function buy (item, cashier) {
      cashier.sell(this, item);
    }
    var Cart=[$$_._class(this),[][0]]; new function() {
      $$_._extend($$_Cart,Cart[1],this); Cart=Cart[0].Cart=$$_Cart; function $$_Cart (customer) {
        this.customer = customer;
        this.items = [];
      }
    }
  }
}

Наследование, внутренние классы и вложенные пространства имен работают нормально. Как вы думаете, это полезный подход к ООП, подобному классу, и повторному использованию кода в js? Дайте мне знать, если я что-то пропустил.

...