如何解决是否可以使用 haxe 宏来检测对象何时变脏任何属性已更改
假设我们有一个对象:
@: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
,以便对字段/属性的任何更改都会将属性 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
宏来修改类成员,无论是附加一个额外分配到 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;
}
};
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。