数据绑定的秘诀是什么?

What's the secret to data-binding?

对于大多数 JS 框架和库而言,它们带来的价值通常表现为关于如何构建应用程序的新结构(Backbone、React)或有效提升能量的新想法他们提供的语言 (Angular),或者只是他们提供的方法都经过充分测试、快速且非常方便 (jQuery)。

通常他们提供的想法和方法是 JavaScript 的非常简单的用法,但背后有一个非常聪明的团队,他们找到有趣的方法来做事,你可以通过思考并得到可靠的猜测胆量如何运作。

但是,我一直无法想通双向绑定 JS 模型以查看组件的能力。这个功能的核心秘诀是什么?从用户输入更改内部变量很简单,但反过来呢?当 JS 变量发生变化时,您如何能够 "know" 以立即更新显示?肯定不能轮询,那又怎样?

每当您的 JS 运行 块被 angular 触发时,它将在该块完成执行时 运行 一个摘要循环。这基本上会检查所有可能更改的值,并且需要更新视图。

如果 angular 没有触发代码,那么它不会知道某些事情可能发生了变化,因此您的绑定可能会不同步。例如,如果你 运行 像这样

setTimeout(function() {$scope.myValue = '123'});

Angular 不会知道 myValue 已更改,它实际上不会更新视图。这就是为什么 Angular 拥有自己的服务来完成所有事情。例如$timeout$http.

如果您有一些 Angular 不知道的回调函数,那么您可以通过调用 $scope.$apply()

手动告诉它检查更改

我可以在 Backbone 中讲述它是如何完成的,它对数据绑定的看法相对较低。

它是 1. 控制属性 setter 方法的库 2. 在属性更改时调用回调函数(例如通过调度事件)以更新 UI.

基本的伪代码是这样的:

class Model:

  method set(name, value):
    if value != this.attributes[name]
      this.triggerEvent('change', name, value)
      this.attributes[name] = value

m = new Model()
someInputWidget.onEvent('userChangedInput', function(value) {
  m.set(someInputWidget.name, value)
})
m.onEvent('change', function(name, value) {
  getInputWidgetByName(name).setValue(value)
})

Backbone 不对 UI 进行任何数据绑定,但您可以参考 Backbone 的 annotated source 以了解实际的事件调度实现。

有几种方法可以做到这一点。 Object.observe 很棒,但缺乏很好的支持。您也可以轮询值,保留对象的第二个副本以进行比较。您还可以编写自己的显式 set/get 方法来像 backbone 那样更新模型。

我经常使用的一种巧妙方法是使用 getters/setters 使模型与 dom 保持同步:

//a demo "model" of data:
model = {
    name: "Fred"
};

function change(k,v){alert([k,v]);} // a stand-in change monitor for demo

// iterate model and replace values with getter/setter combos: 
Object.keys(model).forEach(function(key) {
    var val = model[key];
    delete model[key];
    Object.defineProperty(model, key, {
        get: function() {
            return val;
        },
        set: function(v) {
            val = v;
            change(key, val);
        } //call change upon setting
    });
    change(key, val); //update view "onload"
}); // alerts "Fred";

//update model (fires change() with "name" and "sally" arguments:
model.name="sally"; // alerts  "sally";

更改功能非常简单,对于您的情况,应该只查找绑定到键的元素。 这里的优点是您不需要特殊的自定义 CRUD 方法,您可以像 1999 年一样通过赋值修改对象属性。它也不会轮询,并且可以在 IE9 和任何其他 ES5 环境中一直正常工作。这是无需自定义方法即可绑定 JS>DOM (afaik) 的最简单方法。

它确实有一些限制:嵌套对象很难get/set,你不能一次做整个对象,你只能"watch"基元。数组也是一个问题:您不能真正将 expando 属性替换为 getters/setters 而没有副作用。但是,在相对平坦的 JSON 安全数据集合上,get/set 很有魅力,不需要复杂的库即可运行。

查看使用此方法的完整示例:http://pagedemos.com/xg3szbguqnwu/4