是否可以使用 haxe 宏来检测对象何时变脏(任何 属性 已更改)
Could haxe macro be used to detect when object is dirty (any property has been changed)
假设我们有一个对象:
@:checkDirty
class Test {
var a:Int;
var b(default, default):String;
var c(get, set):Array<Int>;
public function new() {
...
}
public function get_c() {
...
}
public function set_c(n) {
...
}
}
我们可以编写一个宏 checkDirty
,以便对 field/properties 的任何更改都会将 属性 dirty
设置为 true
。宏将生成 dirty
字段作为 Bool
和 clearDirty
函数将其设置为 false
.
var test = new Test();
trace(test.dirty); // false
test.a = 12;
trace(test.dirty); // true
test.clearDirty();
trace(test.dirty); //false
test.b = "test"
trace(test.dirty); //true
test.clearDirty();
test.c = [1,2,3];
trace(test.dirty); //true
请注意 - 每当您考虑代理访问对象时,根据我的经验,总会有隐藏的成本/增加的复杂性。 :)
也就是说,您有几种方法:
首先,如果你想让它成为纯粹的Haxe,那么无论是宏还是抽象都可以完成工作。无论哪种方式,您都有效地将每个 属性 访问转换为设置值并设置 dirty
.
的函数调用
例如,可以在 NME source code 中找到使用 @:resolve
getter 和 setter 的摘要,为方便起见,在此处复制:
@:forward(decode,toString)
abstract URLVariables(URLVariablesBase)
{
public function new(?inEncoded:String)
{
this = new URLVariablesBase(inEncoded);
}
@:resolve
public function set(name:String, value:String) : String
{
return this.set(name,value);
}
@:resolve
public function get(name:String):String
{
return this.get(name);
}
}
这可能是一个较旧的语法,我不确定...另请参阅 Haxe 手册中的 operator overloading examples:
@:op(a.b) public function fieldRead(name:String)
return this.indexOf(name);
@:op(a.b) public function fieldWrite(name:String, value:String)
return this.split(name).join(value);
其次,我只想指出,如果底层语言/运行时支持某种代理对象(例如 JavaScript Proxy),并且宏/抽象没有按预期工作,那么您可以构建最重要的是你的功能。
我写过 a post (archive) 关于做这种事情(除了发出事件)之前 - 你可以使用 @:build
宏来修改 class 成员,无论是附加一个额外分配到 setter 或用 属性.
替换该字段
因此修改后的版本可能如下所示:
class Macro {
public static macro function build():Array<Field> {
var fields = Context.getBuildFields();
for (field in fields.copy()) { // (copy fields so that we don't go over freshly added ones)
switch (field.kind) {
case FVar(fieldType, fieldExpr), FProp("default", "default", fieldType, fieldExpr):
var fieldName = field.name;
if (fieldName == "dirty") continue;
var setterName = "set_" + fieldName;
var tmp_class = macro class {
public var $fieldName(default, set):$fieldType = $fieldExpr;
public function $setterName(v:$fieldType):$fieldType {
$i{fieldName} = v;
this.dirty = true;
return v;
}
};
for (mcf in tmp_class.fields) fields.push(mcf);
fields.remove(field);
case FProp(_, "set", t, e):
var setter = Lambda.find(fields, (f) -> f.name == "set_" + field.name);
if (setter == null) continue;
switch (setter.kind) {
case FFun(f):
f.expr = macro { dirty = true; ${f.expr}; };
default:
}
default:
}
}
if (Lambda.find(fields, (f) -> f.name == "dirty") == null) fields.push((macro class {
public var dirty:Bool = false;
}).fields[0]);
return fields;
}
}
如果用作
@:build(Macro.build())
@:keep class Some {
public function new() {}
public var one:Int;
public var two(default, set):String;
function set_two(v:String):String {
two = v;
return v;
}
}
会发出以下 JS:
var Some = function() {
this.dirty = false;
};
Some.prototype = {
set_two: function(v) {
this.dirty = true;
this.two = v;
return v;
}
,set_one: function(v) {
this.one = v;
this.dirty = true;
return v;
}
};
假设我们有一个对象:
@:checkDirty
class Test {
var a:Int;
var b(default, default):String;
var c(get, set):Array<Int>;
public function new() {
...
}
public function get_c() {
...
}
public function set_c(n) {
...
}
}
我们可以编写一个宏 checkDirty
,以便对 field/properties 的任何更改都会将 属性 dirty
设置为 true
。宏将生成 dirty
字段作为 Bool
和 clearDirty
函数将其设置为 false
.
var test = new Test();
trace(test.dirty); // false
test.a = 12;
trace(test.dirty); // true
test.clearDirty();
trace(test.dirty); //false
test.b = "test"
trace(test.dirty); //true
test.clearDirty();
test.c = [1,2,3];
trace(test.dirty); //true
请注意 - 每当您考虑代理访问对象时,根据我的经验,总会有隐藏的成本/增加的复杂性。 :)
也就是说,您有几种方法:
首先,如果你想让它成为纯粹的Haxe,那么无论是宏还是抽象都可以完成工作。无论哪种方式,您都有效地将每个 属性 访问转换为设置值并设置 dirty
.
例如,可以在 NME source code 中找到使用 @:resolve
getter 和 setter 的摘要,为方便起见,在此处复制:
@:forward(decode,toString)
abstract URLVariables(URLVariablesBase)
{
public function new(?inEncoded:String)
{
this = new URLVariablesBase(inEncoded);
}
@:resolve
public function set(name:String, value:String) : String
{
return this.set(name,value);
}
@:resolve
public function get(name:String):String
{
return this.get(name);
}
}
这可能是一个较旧的语法,我不确定...另请参阅 Haxe 手册中的 operator overloading examples:
@:op(a.b) public function fieldRead(name:String)
return this.indexOf(name);
@:op(a.b) public function fieldWrite(name:String, value:String)
return this.split(name).join(value);
其次,我只想指出,如果底层语言/运行时支持某种代理对象(例如 JavaScript Proxy),并且宏/抽象没有按预期工作,那么您可以构建最重要的是你的功能。
我写过 a post (archive) 关于做这种事情(除了发出事件)之前 - 你可以使用 @:build
宏来修改 class 成员,无论是附加一个额外分配到 setter 或用 属性.
因此修改后的版本可能如下所示:
class Macro {
public static macro function build():Array<Field> {
var fields = Context.getBuildFields();
for (field in fields.copy()) { // (copy fields so that we don't go over freshly added ones)
switch (field.kind) {
case FVar(fieldType, fieldExpr), FProp("default", "default", fieldType, fieldExpr):
var fieldName = field.name;
if (fieldName == "dirty") continue;
var setterName = "set_" + fieldName;
var tmp_class = macro class {
public var $fieldName(default, set):$fieldType = $fieldExpr;
public function $setterName(v:$fieldType):$fieldType {
$i{fieldName} = v;
this.dirty = true;
return v;
}
};
for (mcf in tmp_class.fields) fields.push(mcf);
fields.remove(field);
case FProp(_, "set", t, e):
var setter = Lambda.find(fields, (f) -> f.name == "set_" + field.name);
if (setter == null) continue;
switch (setter.kind) {
case FFun(f):
f.expr = macro { dirty = true; ${f.expr}; };
default:
}
default:
}
}
if (Lambda.find(fields, (f) -> f.name == "dirty") == null) fields.push((macro class {
public var dirty:Bool = false;
}).fields[0]);
return fields;
}
}
如果用作
@:build(Macro.build())
@:keep class Some {
public function new() {}
public var one:Int;
public var two(default, set):String;
function set_two(v:String):String {
two = v;
return v;
}
}
会发出以下 JS:
var Some = function() {
this.dirty = false;
};
Some.prototype = {
set_two: function(v) {
this.dirty = true;
this.two = v;
return v;
}
,set_one: function(v) {
this.one = v;
this.dirty = true;
return v;
}
};