6

我有一个 Knockout 扩展,knockout-secure-binding,我们遇到了一个问题

特别是在使用 时Object.defineProperty,就像knockout-es5所做的那样,当在 上触发更改事件时不会调用value绑定的函数。updateinput

我的单元测试说明了这种特殊性。这有效:

it("reads an input `value` binding", function () {
    var input = document.createElement("input"),
        evt = new CustomEvent("change"),
        context = { vobs: ko.observable() };
    input.setAttribute("data-sbind", "value: vobs")
    ko.applyBindings(context, input)
    input.value = '273-9164'
    input.dispatchEvent(evt)
    assert.equal(context.vobs(), '273-9164')
})

这(作为淘汰赛 es5 定义属性的方式)不起作用:

it("reads an input `value` binding for a defineProperty", function () {
    // see https://github.com/brianmhunt/knockout-secure-binding/issues/23
    var input = document.createElement("input"),
        evt = new CustomEvent("change"),
        obs = ko.observable(),
        context = { };
    Object.defineProperty(context, 'pobs', {
        configurable: true,
        enumerable: true,
        get: obs,
        set: obs
    });
    input.setAttribute("data-sbind", "value: pobs")
    ko.applyBindings(context, input)
    input.value = '273-9164'
    input.dispatchEvent(evt)
    assert.equal(context.pobs, '273-9164')
})

如前所述,在后一种情况下,当value.update被调用时不会被input.dispatchEvent调用。

自定义绑定正在返回它自己的valueAccessor,所以我希望问题与此有关。让我感到特别奇怪的是,它可以与对象属性一起使用,但不能defineProperty

4

1 回答 1

6

Knockout 在处理它们之前重写绑定表达式,以支持“双向绑定以包含一个写入函数,该函数允许处理程序更新值,即使它不是可观察的。” 这部分使Object.defineProperty定义的属性在绑定中起作用。

这是在ko.expressionRewriting.preProcessBindings方法(source)中实现的

此方法转换以下绑定表达式:

data-bind="value: pobs, checked: vobs"

到以下:

"'value':function(){return pobs },'checked':function(){return vobs },'_ko_property_writers':function(){return {'value':function(_z){pobs=_z},'checked':function(_z){vobs=_z}} }"

请注意生成的_ko_property_writers其中包含用于设置不可观察属性的代码。

这是关于这个神奇属性的代码注释:

// For those developers who rely on _ko_property_writers in their custom bindings, we expose _twoWayBindings as an
// undocumented feature that makes it relatively easy to upgrade to KO 3.0. However, this is still not an official
// public API, and we reserve the right to remove it at any time if we create a real public property writers API.

因此,您只需要在convert_to_accessors函数中重现相同的逻辑:您需要在result名为的对象上创建一个新属性,该属性"_ko_property_writers"返回适当的编写器函数:

Parser.prototype.convert_to_accessors = function (result) {
    var propertyWriters = {};
    ko.utils.objectForEach(result, function (name, value) {
      if (value instanceof Identifier || value instanceof Expression) {
        result[name] = function expidAccessor() {
          // expression or identifier accessir
          return value.get_value();
        };
        if (ko.expressionRewriting.twoWayBindings[name]) {
          var token = value.token;
          var context = value.parser.context.$data;
          propertyWriters[name] = function(_z) {
              context[token] = _z;
            };
        }
      } else if (typeof(value) != 'function') {
        result[name] = function constAccessor() {
          return value;
        };
      }
    });
    if (Object.keys(propertyWriters).length > 0)
        result["_ko_property_writers"] = function () {
           return propertyWriters;
        }
    return result;
};

免责声明:这不是生产就绪的实现!它只是显示了需要做什么的想法。尽管它使您的两个示例测试都进行了,但它可能会破坏插件的其他部分。您还应该特别注意正确的上下文处理,因为使用value.parser.context.$data有点 hacky。

于 2014-02-09T20:39:38.150 回答