EXTENDS 挑战:预处理器函数宏和类类 oop

发布于 2024-09-11 07:07:41 字数 3430 浏览 1 评论 0原文

背景

我一直在使用 C 预处理器来管理和“编译”具有多个文件和构建目标的半大型 JavaScript 项目。这样可以从 javascript 中完全访问 C 预处理器指令,例如 #include#define#ifdef 等。下面是一个示例构建脚本,以便您可以测试示例代码:

#!/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 条件内,这样就不会干扰该宏调用之后的大括号。


类似类的 OOP

让我们从 NAMESPACE 宏和一个晦涩但有用的 js 模式开始...

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

new function(){ ... } 做了一些巧妙的事情。它调用匿名函数作为构造函数,因此不需要在末尾添加额外的 () 来调用它,并且在其中 this 引用正在创建的对象由构造函数,换句话说,由命名空间本身。这也允许我们在命名空间内嵌套命名空间。

这是我的全套类类 OOP 宏:

#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(){}}())

预先感谢您的任何帮助、建议或热烈讨论。 :)

Background

I've been using the C preprocessor to manage and "compile" semi-large javascript projects with multiple files and build targets. This gives full access to C preprocessor directives like #include, #define, #ifdef, etc. from within javascript. Here's a sample build script so you can test the example code:

#!/bin/bash
export OPTS="-DDEBUG_MODE=1 -Isrc"
for FILE in `find src/ | egrep '\.js?

Make a src and a build directory, and put the .js files in src.


Convenience Macros

Originally, I just wanted the preprocessor stuff for #include and maybe a few #ifdefs, but I got to thinking, wouldn't it be nice to have some convenience macros too? Experimentation ensued.

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

Cool, so now I can write something like this:

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

And it will expand to:

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

How about 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) }

Notice how we sneak v=o[k] inside the if condition so it doesn't disturb the curly braces that should follow the invocation of this macro.


Class-like OOP

Let's start with a NAMESPACE macro and an obscure but useful js pattern...

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

new function(){ ... } does some neat stuff. It calls an anonymous function as a constructor, so it doesn't need an extra () at the end to call it, and within it this refers to the object being created by the constructor, in other words, the namespace itself. This also allows us to nest namespaces within namespaces.

Here is my full set of class-like OOP macros:

#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

As you can see, these macros define many things both in the Variable Object (for convenience) and in this (from necessity). Here's some example code:

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);
    }
  }
}

What about EXTENDS?

So this brings me to the question... how can we implement EXTENDS as a macro to wrap the usual "clone the prototype, copy constructor properties" js prototype inheritance? I haven't found a way to do it outside of requiring the EXTENDS to appear after the class definition, which is silly. This experiment needs EXTENDS or it's useless. Feel free to change the other macros as long as they give the same results.

Edit - These might come in handy for EXTENDS; listing them here for completeness.

#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(){}}())

Thanks in advance for any help, advice, or lively discussion. :)

` do echo "Processing $FILE" cat $FILE \ | sed 's/^\s*\/\/#/#/' \ | cpp $OPTS \ | sed 's/^[#:<].*// ; /^$/d' \ > build/`basename $FILE`; done

Make a src and a build directory, and put the .js files in src.


Convenience Macros

Originally, I just wanted the preprocessor stuff for #include and maybe a few #ifdefs, but I got to thinking, wouldn't it be nice to have some convenience macros too? Experimentation ensued.

Cool, so now I can write something like this:

And it will expand to:

How about foreach?

Notice how we sneak v=o[k] inside the if condition so it doesn't disturb the curly braces that should follow the invocation of this macro.


Class-like OOP

Let's start with a NAMESPACE macro and an obscure but useful js pattern...

new function(){ ... } does some neat stuff. It calls an anonymous function as a constructor, so it doesn't need an extra () at the end to call it, and within it this refers to the object being created by the constructor, in other words, the namespace itself. This also allows us to nest namespaces within namespaces.

Here is my full set of class-like OOP macros:

As you can see, these macros define many things both in the Variable Object (for convenience) and in this (from necessity). Here's some example code:


What about EXTENDS?

So this brings me to the question... how can we implement EXTENDS as a macro to wrap the usual "clone the prototype, copy constructor properties" js prototype inheritance? I haven't found a way to do it outside of requiring the EXTENDS to appear after the class definition, which is silly. This experiment needs EXTENDS or it's useless. Feel free to change the other macros as long as they give the same results.

Edit - These might come in handy for EXTENDS; listing them here for completeness.

Thanks in advance for any help, advice, or lively discussion. :)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

甜扑 2024-09-18 07:07:41

我想我刚刚完成了自己的挑战。我已经为所声明的类的超类的 CLASS 声明宏添加了第二个(可选)参数。

我最初的实现在构造函数周围创建了很多内联垃圾,因此我决定将一些便利函数包装在宏帮助器对象中以避免冗余。

以下是类类 OOP 宏的当前化身:

// 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 中实现类类 OOP 和代码重用的有用方法吗?如果我错过了什么,请告诉我。

I think I just completed my own challenge. I've added a second (optional) argument to the CLASS declaration macro for the superclass of the class being declared.

My original implementation created a lot of inline junk around the constructor, so I decided to wrap some convenience functions up in a macro helper object to avoid redundancy.

Here are the current incarnations of my class-like OOP macros:

// 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); 
  };

})

... here's a test namespace ...

//#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 = [];
      }
    }

  }
}

... and here's the output ...

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 = [];
      }
    }
  }
}

Inheritance, internal classes, and nested namespaces seem to work fine. What do you think, is this a useful approach to class-like OOP and code reuse in js? Let me know if I've missed anything.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文