如何在JavaScript中实现DOM数据绑定

请把这个问题视为严格的教育。 我仍然有兴趣听到新的答案和想法来实现这一点

TL;博士

我将如何实现与JavaScript的双向数据绑定?

数据绑定到DOM

通过数据绑定到DOM我的意思是,例如,具有一个JavaScript对象a与属性b 。 然后有一个<input> DOM元素(例如),当DOM元素发生变化时, a变化,反之亦然(即我的意思是双向数据绑定)。

这里是AngularJS的一个图表,它看起来像这样:

双向数据绑定

所以基本上我有JavaScript类似于:

var a = {b:3};

然后输入(或其他形式)元素,如:

<input type='text' value=''>

我希望输入的值是ab的值(例如),并且当输入文本改变时,我想要ab也改变。 当JavaScript中的ab更改时,输入发生更改。

问题

在普通的JavaScript中完成这些操作的基本技巧是什么?

具体来说,我想提出一个很好的答案:

  • 如何绑定对象的工作?
  • 如何在表单中倾听更改可能会奏效?
  • 是否有可能以简单的方式只在模板级别修改HTML? 我不想跟踪HTML文档本身的绑定,只能在JavaScript中进行绑定(使用DOM事件,并且JavaScript保持对使用的DOM元素的引用)。
  • 我试过了什么?

    我是胡须的忠实粉丝,所以我尝试将它用于模板。 然而,我试图执行数据绑定本身时遇到了问题,因为Mustache将HTML处理为字符串,所以在得到结果后,我没有参考viewmodel中对象的位置。 我唯一能想到的解决方法是用属性修改HTML字符串(或创建的DOM树)本身。 我不介意使用不同的模板引擎。

    基本上,我有一种强烈的感觉,我正在使问题复杂化,并有一个简单的解决方案。

    注意:请不要提供使用外部库的答案,尤其是那些有数千行代码的答案。 我用过(并且喜欢!)AngularJS和KnockoutJS。 我真的不想以'使用框架x'的形式得到答案。 最好是,我想要一个未知的读者不知道如何使用许多框架来掌握如何实现双向数据绑定。 我不期望得到一个完整的答案,但是有一个想法。


  • 如何绑定对象的工作?
  • 如何在表单中倾听更改可能会奏效?
  • 更新这两个对象的抽象

    我想还有其他的技术,但最终我会有一个对象来保存对相关DOM元素的引用,并提供一个接口来协调它自己的数据及其相关元素的更新。

    .addEventListener()为此提供了一个非常好的界面。 你可以给它一个实现eventListener接口的对象,并且它将调用它的处理器作为this值。

    这使您可以自动访问元素及其相关数据。

    定义你的对象

    原型继承是实现这一点的好方法,当然不是必需的。 首先,您将创建一个构造函数来接收元素和一些初始数据。

    function MyCtor(element, data) {
        this.data = data;
        this.element = element;
        element.value = data;
        element.addEventListener("change", this, false);
    }
    

    所以这里的构造函数将元素和数据存储在新对象的属性中。 它还将change事件绑定到给定element 。 有趣的是,它传递的是新对象而不是函数作为第二个参数。 但是,这本身并不行。

    实现eventListener接口

    为了使这个工作,你的对象需要实现eventListener接口。 完成这一切所需的只是为对象提供一个handleEvent()方法。

    这就是继承进来的地方。

    MyCtor.prototype.handleEvent = function(event) {
        switch (event.type) {
            case "change": this.change(this.element.value);
        }
    };
    
    MyCtor.prototype.change = function(value) {
        this.data = value;
        this.element.value = value;
    };
    

    有很多不同的方法可以构造它,但对于协调更新的示例,我决定让change()方法只接受一个值,并让handleEvent传递该值而不是事件对象。 这样change()可以在没有事件的情况下被调用。

    所以,现在,当change事件发生时,它将更新元素和.data属性。 当您在JavaScript程序中调用.change()时也会发生这种情况。

    使用代码

    现在您只需创建新对象,并让它执行更新。 JS代码中的更新将显示在输入中,并且输入上的更改事件对JS代码将可见。

    var obj = new MyCtor(document.getElementById("foo"), "20");
    
    // simulate some JS based changes.
    var i = 0;
    setInterval(function() {
        obj.change(parseInt(obj.element.value) + ++i);
    }, 3000);
    

    DEMO: http : //jsfiddle.net/RkTMD/


    所以,我决定把我自己的解决方案放在锅里。 这是一个工作小提琴 。 请注意,这只能在非常新的浏览器上运行。

    它使用什么

    这个实现非常现代 - 它需要一个(非常)现代的浏览器和用户两项新技术:

  • MutationObserver用来检测dom中的变化(事件监听器也被使用)
  • Object.observe检测对象中的变化并通知dom。 危险,因为这个答案已经被写成Oo已经被ECMAScript TC讨论和决定,所以考虑一个polyfill。
  • 怎么运行的

  • 在元素上,放一个domAttribute:objAttribute映射 - 例如bind='textContent:name'
  • 在dataBind函数中读取它。 观察对元素和对象的更改。
  • 发生更改时 - 更新相关元素。
  • 解决方案

    这里是dataBind函数,注意它只是20行代码,可能更短:

    function dataBind(domElement, obj) {    
        var bind = domElement.getAttribute("bind").split(":");
        var domAttr = bind[0].trim(); // the attribute on the DOM element
        var itemAttr = bind[1].trim(); // the attribute the object
    
        // when the object changes - update the DOM
        Object.observe(obj, function (change) {
            domElement[domAttr] = obj[itemAttr]; 
        });
        // when the dom changes - update the object
        new MutationObserver(updateObj).observe(domElement, { 
            attributes: true,
            childList: true,
            characterData: true
        });
        domElement.addEventListener("keyup", updateObj);
        domElement.addEventListener("click",updateObj);
        function updateObj(){
            obj[itemAttr] = domElement[domAttr];   
        }
        // start the cycle by taking the attribute from the object and updating it.
        domElement[domAttr] = obj[itemAttr]; 
    }
    

    这里有一些用法:

    HTML:

    <div id='projection' bind='textContent:name'></div>
    <input type='text' id='textView' bind='value:name' />
    

    JavaScript的:

    var obj = {
        name: "Benjamin"
    };
    var el = document.getElementById("textView");
    dataBind(el, obj);
    var field = document.getElementById("projection");
    dataBind(field,obj);
    

    这是一个工作小提琴 。 请注意,这个解决方案非常通用。 Object.observe和突变观察者匀场可用。


    我想添加到我的preposter。 我建议一种稍微不同的方法,它可以让你在不使用方法的情况下为对象分配一个新的值。 必须指出的是,这并不是特别受老式浏览器支持的,IE9仍然需要使用不同的接口。

    最值得注意的是我的方法不利用事件。

    吸气剂和固化剂

    我的建议使用了getters和setter的相对年轻功能,特别是setter。 一般来说,增变器允许我们“定制”某些属性如何赋值并检索的行为。

    我将在这里使用的一个实现是Object.defineProperty方法。 它适用于FireFox,GoogleChrome和 - 我认为 - IE9。 没有测试过其他浏览器,但是因为这只是理论上的...

    无论如何,它接受三个参数。 第一个参数是您希望为其定义新属性的对象,第二个参数是类似于新属性名称的字符串,最后一个是提供有关新属性行为信息的“描述符对象”。

    两个特别有趣的描述符是getset 。 一个例子如下所示。 请注意,使用这两个禁止使用其他4个描述符。

    function MyCtor( bindTo ) {
        // I'll omit parameter validation here.
    
        Object.defineProperty(this, 'value', {
            enumerable: true,
            get : function ( ) {
                return bindTo.value;
            },
            set : function ( val ) {
                bindTo.value = val;
            }
        });
    }
    

    现在利用这个变得稍微不同:

    var obj = new MyCtor(document.getElementById('foo')),
        i = 0;
    setInterval(function() {
        obj.value += ++i;
    }, 3000);
    

    我想强调的是,这只适用于现代浏览器。

    工作小提琴:http://jsfiddle.net/Derija93/RkTMD/1/

    链接地址: http://www.djcxy.com/p/35357.html

    上一篇: How to Implement DOM Data Binding in JavaScript

    下一篇: How to handle initializing and rendering subviews in Backbone.js?