使用Object.defineProperty在属性之间创建双向绑定的可能方法是什么?

时间:2014-01-05 07:09:01

标签: javascript

说,我有一个实现3D对象的对象类型,它具有四元数和3轴旋转属性:

Object.defineProperty(MyObject3D.prototype, 'rotation', {
    ...
});

Object.defineProperty(MyObject3D.prototype, 'quaternion', {
    ...
});

我想确保每次修改每个属性时,会自动重新计算另一个属性。显然,如果我同时设置这些属性的set方法,我将遇到无限循环。

到目前为止,我能看到的唯一方法是实际使用另一组类似于旋转和四元数的属性(可能是不可枚举的,用于更清洁的对象内省):

Object.defineProperty(MyObject3D.prototype, '_rotation', {
    enumerable: false
});

Object.defineProperty(MyObject3D.prototype, '_quaternion', {
    enumerable: false
});

Object.defineProperty(MyObject3D.prototype, 'rotation', {
    get: function() {
        return this._rotation;
    },
    set: function(r) {
        this._rotation = r;
        this._quaternion = rotation2quaternion(r);
    }
});

Object.defineProperty(MyObject3D.prototype, 'quaternion', {
    get: function() {
        return this._rotation;
    },
    set: function(q) {
        this._quaternion = q;
        this._rotation= quaternion2rotation(q);
    }
});

你能想出更好的方法吗?也许我错过了一些Object.defineProperty的功能,可以让它更干净,更短?

1 个答案:

答案 0 :(得分:2)

不是简单地设置rotationquaternion访问器属性,而是让它们成为数据属性然后定义setRotationsetQuaternion方法来改变它们不是更简单?例如:

function MyObject3D() {
    ...
}

MyObject3D.prototype.setRotation = function (r) {
    this.quaternion = rotation2quaternion(r);
    this.rotation = r
    return this;
};

MyObject3D.prototype.setQuaternion = function (q) {
    this.rotation = quaternion2rotation(q);
    this.quaternion = q;
    return this;
};

现在您可以正常访问.rotation.quaternion。但是,要设置它们,请改用.setRotation.setQuaternion。是的,它不安全,因为用户可以手动设置.rotation.quaternion。但它有几个优点:

  1. 它适用于不支持defineProperty
  2. 的浏览器
  3. 它允许您链接操作,因为它返回this
  4. 很容易理解这些功能的作用。

  5. 解决此问题的另一种方法是使用不可变对象。这是你在Haskell中做的方式。例如:

    function MyObject3D(rotation, quaternion) {
        this.quaternion = quaternion;
        this.rotation = rotation;
        Object.freeze(this);
    }
    
    MyObject3D.prototype.putRotation = function (r) {
        return new MyObject3D(r, rotation2quaternion(r));
    };
    
    MyObject3D.prototype.putQuaternion = function (q) {
        return new MyObject3D(quaternion2rotation(q), q);
    };
    

    如果您的构造函数中有一些初始化逻辑,那么您可以将其移动到智能构造函数中:

    function createMyObject3D(r, q) {
        // some initialization logic
        return new MyObject3D(r, q);
    }
    

    因此,当您想要创建一个新对象时,使用createMyObject3D,当您想要改变对象时,使用new MyObject3D传递新值。以下方法的优点是:

    1. 您的代码变为引用透明。因此,它可以进行等式推理。
    2. 它仍然允许您链接操作,因为它返回一个全新的对象。
    3. 没有人可以篡改您对象的属性。
    4. 很容易理解这些功能的作用。
    5. 唯一的缺点是它取决于旧版浏览器中可能不存在的Object.freeze。然而,冻结对象是一个可选步骤,可以省略。