简体   繁体   English

是否可以使用 haxe 宏来检测对象何时变脏(任何属性已更改)

[英]Could haxe macro be used to detect when object is dirty (any property has been changed)

Let say we have an object:假设我们有一个对象:

@: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) {
     ... 
   }


}

Could we write a macro checkDirty so that any change to field/properties would set property dirty to true .我们可以写一个宏checkDirty以便对字段/属性的任何更改都会将属性dirty设置为true Macro would generate dirty field as Bool and clearDirty function to set it to false .宏将生成dirty字段作为BoolclearDirty函数以将其设置为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

Just to note - whenever you consider proxying access to an object, in my experience, there are always hidden costs / added complexity.请注意 - 每当您考虑代理访问对象时,根据我的经验,总是存在隐藏成本/增加的复杂性。 :) :)

That said, you have a few approaches:也就是说,您有几种方法:

First, if you want it to be pure Haxe, then either a macro or an abstract can get the job done.首先,如果您希望它是纯 Haxe,那么宏或摘要都可以完成工作。 Either way, you're effectively transforming every property access into a function call that sets the value and also sets dirty .无论哪种方式,您都有效地将每个属性访问转换为一个函数调用,该函数调用设置值并设置dirty

For example, an abstract using the @:resolve getter and setter can be found in the NME source code , replicated here for convenience:例如,可以在NME 源代码中找到使用@: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);
   }
}

This may be an older syntax, I'm not sure... also look at the operator overloading examples on the Haxe manual:这可能是一个较旧的语法,我不确定......还可以查看 Haxe 手册上的运算符重载示例

  @: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);

Second, I'd just point out that if the underlying language / runtime supports some kind of Proxy object (eg JavaScript Proxy ), and macro / abstract isn't working as expected, then you could build your functionality on top of that.其次,我只想指出,如果底层语言/运行时支持某种 Proxy 对象(例如JavaScript Proxy ),并且宏/抽象没有按预期工作,那么您可以在此基础上构建您的功能。

I wrote a post ( archive ) about doing this kind of thing (except for emitting events) before - you can use a @:build macro to modify class members, be it appending an extra assignment into setter or replacing the field with a property.我之前写一篇关于做这种事情(除了发出事件)的帖子存档) - 您可以使用@:build宏来修改类成员,无论是将额外的赋值附加到 setter 还是用属性替换字段。

So a modified version might look like so:因此,修改后的版本可能如下所示:

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

which, if used as其中,如果用作

@: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;
    }
}

Would emit the following JS:将发出以下 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;
    }
};

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM