Фон
Я использовал препроцессор 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 в качестве макроса, чтобы обернуть обычное наследование прототипа js "клонирование прототипа, копирование свойств конструктора"? Я не нашел способа сделать это, кроме требования, чтобы EXTENDS появлялись после определения класса, что глупо. Этот эксперимент требует 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(){}}())
Заранее спасибо за любую помощь, совет или живое обсуждение. :)