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

博士tl;

如何用JavaScript实现双向数据绑定?

到DOM的数据绑定

通过数据绑定到DOM,我的意思是,例如,拥有一个带有属性b的JavaScript对象a。然后有一个<input> DOM元素(例如),当DOM元素改变时,a也会改变,反之亦然(也就是说,我的意思是双向数据绑定)。

下面是AngularJS的一个图:

JavaScript是这样的:

var a = {b:3};

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

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

我希望输入的值是a.b的值(例如),当输入文本发生变化时,我希望a.b也发生变化。当JavaScript中的a.b发生变化时,输入也会发生变化。

这个问题

在纯JavaScript中完成这个任务的基本技术是什么?

具体来说,我想要一个好的答案参考:

对象绑定是如何工作的? 倾听形式上的变化是如何起作用的? 是否有可能以一种简单的方式只在模板级别修改HTML ?我不想在HTML文档本身中跟踪绑定,而只在JavaScript中跟踪绑定(使用DOM事件,JavaScript保持对所使用的DOM元素的引用)。

我都试过什么?

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

基本上,我有一种强烈的感觉,我把手头的问题复杂化了,有一个简单的解决办法。

注意:请不要提供使用外部库的答案,特别是那些有数千行代码的答案。我用过(而且喜欢!)AngularJS和KnockoutJS。我真的不想要“使用框架x”这种形式的答案。最理想的情况是,我希望未来不知道如何使用许多框架的读者能够自己掌握如何实现双向数据绑定。我不期待一个完整的答案,但希望能让人理解。


当前回答

所以,我决定把我自己的溶液扔进锅里。这是一个工作的小提琴。注意,这只在非常现代的浏览器上运行。

它使用什么

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

mutationobserver用于检测dom中的变化(事件监听器也被使用) 对象。观察以检测对象中的变化并通知dom。危险,由于这个答案已经被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);

这是一把能用的小提琴。注意,这个解决方案是相当通用的。对象。可以使用观察和突变观察器shimming。

其他回答

所以,我决定把我自己的溶液扔进锅里。这是一个工作的小提琴。注意,这只在非常现代的浏览器上运行。

它使用什么

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

mutationobserver用于检测dom中的变化(事件监听器也被使用) 对象。观察以检测对象中的变化并通知dom。危险,由于这个答案已经被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);

这是一把能用的小提琴。注意,这个解决方案是相当通用的。对象。可以使用观察和突变观察器shimming。

这是一个非常简单的双向数据绑定在香草javascript....

<input type="text" id="inp" onkeyup="document.getElementById('name').innerHTML=document.getElementById('inp').value;">

<div id="name">

</div>

在这个链接“Easy Two-Way Data Binding in JavaScript”中有一个非常简单的双向数据绑定实现。

之前的链接以及来自knockoutjs, backbone.js和agility.js的想法,导致了这个轻量级和快速的MVVM框架,ModelView.js基于jQuery,它很好地使用jQuery,我是这个框架的谦虚(或者不那么谦虚)的作者。

复制下面的示例代码(来自博客文章链接):

DataBinder的示例代码

function DataBinder( object_id ) {
  // Use a jQuery object as simple PubSub
  var pubSub = jQuery({});

  // We expect a `data` element specifying the binding
  // in the form: data-bind-<object_id>="<property_name>"
  var data_attr = "bind-" + object_id,
      message = object_id + ":change";

  // Listen to change events on elements with the data-binding attribute and proxy
  // them to the PubSub, so that the change is "broadcasted" to all connected objects
  jQuery( document ).on( "change", "[data-" + data_attr + "]", function( evt ) {
    var $input = jQuery( this );

    pubSub.trigger( message, [ $input.data( data_attr ), $input.val() ] );
  });

  // PubSub propagates changes to all bound elements, setting value of
  // input tags or HTML content of other tags
  pubSub.on( message, function( evt, prop_name, new_val ) {
    jQuery( "[data-" + data_attr + "=" + prop_name + "]" ).each( function() {
      var $bound = jQuery( this );

      if ( $bound.is("input, textarea, select") ) {
        $bound.val( new_val );
      } else {
        $bound.html( new_val );
      }
    });
  });

  return pubSub;
}

对于JavaScript对象而言,是一个最小的实现 本实验的用户模型如下:

function User( uid ) {
  var binder = new DataBinder( uid ),

      user = {
        attributes: {},

        // The attribute setter publish changes using the DataBinder PubSub
        set: function( attr_name, val ) {
          this.attributes[ attr_name ] = val;
          binder.trigger( uid + ":change", [ attr_name, val, this ] );
        },

        get: function( attr_name ) {
          return this.attributes[ attr_name ];
        },

        _binder: binder
      };

  // Subscribe to the PubSub
  binder.on( uid + ":change", function( evt, attr_name, new_val, initiator ) {
    if ( initiator !== user ) {
      user.set( attr_name, new_val );
    }
  });

  return user;
}

现在,每当我们想要将一个模型的属性绑定到一个UI块时,我们 只需要设置相应的数据属性 HTML元素:

// javascript
var user = new User( 123 );
user.set( "name", "Wolfgang" );

<!-- html -->
<input type="number" data-bind-123="name" />

我想给我的预习添点什么。我建议使用一种稍微不同的方法,它允许您不使用方法而简单地为对象分配一个新值。但必须指出的是,特别是老版本的浏览器不支持这一点,IE9仍然需要使用不同的界面。

最值得注意的是,我的方法没有使用事件。

getter和setter

我的建议利用了getter和setter相对较年轻的特性,特别是仅使用setter。一般来说,突变器允许我们“自定义”某些属性如何赋值和检索的行为。

这里我将使用的一个实现是Object.defineProperty方法。它适用于火狐,谷歌浏览器,我想还有IE9。还没有测试其他浏览器,但由于这只是理论…

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

两个特别有趣的描述符是get和set。示例如下所示。注意,使用这两个描述符将禁止使用其他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/

绑定任何html输入

<input id="element-to-bind" type="text">

定义两个函数:

function bindValue(objectToBind) {
var elemToBind = document.getElementById(objectToBind.id)    
elemToBind.addEventListener("change", function() {
    objectToBind.value = this.value;
})
}

function proxify(id) { 
var handler = {
    set: function(target, key, value, receiver) {
        target[key] = value;
        document.getElementById(target.id).value = value;
        return Reflect.set(target, key, value);
    },
}
return new Proxy({id: id}, handler);
}

使用函数:

var myObject = proxify('element-to-bind')
bindValue(myObject);