EXTENDS 挑战:预处理器函数宏和类 oop
背景
我一直在使用 C 预处理器来管理和编译"具有多个文件和构建目标的半大型 javascript 项目.这使您可以从 javascript 中完全访问 C 预处理器指令,如 #include
、#define
、#ifdef
等.这是一个示例构建脚本,您可以测试示例代码:
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?$'`
do
echo "Processing $FILE"
cat $FILE
| sed 's/^s*//#/#/'
| cpp $OPTS
| sed 's/^[#:<].*// ; /^$/d'
> build/`basename $FILE`;
done
制作一个src
和一个build
目录,并将.js文件放到src
中.
Make a src
and a build
directory, and put the .js files in src
.
便利宏
最初,我只是想要 #include
的预处理器,也许还有一些 #ifdef
,但我开始想,拥有它不是很好吗也有一些方便的宏?实验随之而来.
Originally, I just wanted the preprocessor stuff for #include
and maybe a few #ifdef
s, 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]);
}
它会扩展为:
for (var prop in location) if (location.hasOwnProperty(prop)) {
console.log(prop + " : " location[prop]);
}
foreach 怎么样?
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) }
请注意我们如何将 v=o[k]
隐藏在 if
条件中,这样它就不会干扰应该遵循该宏的调用的花括号.
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.
类OOP
让我们从一个 NAMESPACE 宏和一个不起眼但有用的 js 模式开始...
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(){ ... }
做了一些简洁的事情.它调用一个匿名函数作为构造函数,所以它不需要在末尾额外的 ()
来调用它,其中 this
指的是正在创建的对象由构造函数,换句话说,命名空间本身.这也允许我们在命名空间中嵌套命名空间.
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.
这是我的全套类 OOP 宏:
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
如您所见,这些宏在 Variable Object
(方便起见)和 this
(必要时)中定义了许多东西.下面是一些示例代码:
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);
}
}
}
<小时>
EXTENDS 呢?
所以这给我带来了一个问题......我们如何将 EXTENDS 实现为宏来包装通常的克隆原型,复制构造函数属性"js原型继承?除了要求EXTENDS出现在类定义之后之外,我还没有找到一种方法,这很愚蠢.这个实验需要 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.
编辑 - 这些对于 EXTENDS 可能会派上用场;为了完整起见,在此处列出它们.
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. :)
推荐答案
我想我刚刚完成了自己的挑战.我在 CLASS 声明宏中添加了第二个(可选)参数,用于声明的类的超类.
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.
这是我的类 OOP 宏的当前化身:
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 = [];
}
}
}
}
...这是输出...
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 和代码重用的有用方法吗?如果我遗漏了什么,请告诉我.
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.
相关文章