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

博士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”这种形式的答案。最理想的情况是,我希望未来不知道如何使用许多框架的读者能够自己掌握如何实现双向数据绑定。我不期待一个完整的答案,但希望能让人理解。


当前回答

对象绑定是如何工作的? 倾听形式上的变化是如何起作用的?

更新两个对象的抽象

我认为还有其他技术,但最终我将拥有一个对象,该对象保存对相关DOM元素的引用,并提供一个接口,以协调对其自身数据及其相关元素的更新。

addeventlistener()为此提供了一个非常好的接口。您可以给它一个实现eventListener接口的对象,它将用该对象作为this值调用它的处理程序。

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

定义对象

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

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

因此,这里构造函数将元素和数据存储在新对象的属性上。它还将更改事件绑定到给定元素。有趣的是,它将new对象而不是函数作为第二个参数传递。但单靠这个是不行的。

实现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);

演示:http://jsfiddle.net/RkTMD/

其他回答

<!DOCTYPE html>
<html>
<head>
    <title>Test</title>
</head>
<body>

<input type="text" id="demo" name="">
<p id="view"></p>
<script type="text/javascript">
    var id = document.getElementById('demo');
    var view = document.getElementById('view');
    id.addEventListener('input', function(evt){
        view.innerHTML = this.value;
    });

</script>
</body>
</html>

对象绑定是如何工作的? 倾听形式上的变化是如何起作用的?

更新两个对象的抽象

我认为还有其他技术,但最终我将拥有一个对象,该对象保存对相关DOM元素的引用,并提供一个接口,以协调对其自身数据及其相关元素的更新。

addeventlistener()为此提供了一个非常好的接口。您可以给它一个实现eventListener接口的对象,它将用该对象作为this值调用它的处理程序。

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

定义对象

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

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

因此,这里构造函数将元素和数据存储在新对象的属性上。它还将更改事件绑定到给定元素。有趣的是,它将new对象而不是函数作为第二个参数传递。但单靠这个是不行的。

实现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);

演示:http://jsfiddle.net/RkTMD/

将变量绑定到输入的一个简单方法(双向绑定)是直接访问getter和setter中的输入元素:

var variable = function(element){                    
                   return {
                       get : function () { return element.value;},
                       set : function (value) { element.value = value;} 
                   }
               };

在HTML中:

<input id="an-input" />
<input id="another-input" />

并使用:

var myVar = new variable(document.getElementById("an-input"));
myVar.set(10);

// and another example:
var myVar2 = new variable(document.getElementById("another-input"));
myVar.set(myVar2.get());

一个不需要getter/setter的更好的方法:

var variable = function(element){

                return function () {
                    if(arguments.length > 0)                        
                        element.value = arguments[0];                                           

                    else return element.value;                                                  
                }

        }

使用方法:

var v1 = new variable(document.getElementById("an-input"));
v1(10); // sets value to 20.
console.log(v1()); // reads value.

我已经通过一些基本的javascript示例,使用onkeypress和onchange事件处理程序,使绑定视图到我们的js和js视图

这里的例子plunker http://plnkr.co/edit/7hSOIFRTvqLAvdZT4Bcc?p=preview

<!DOCTYPE html>
<html>
<body>

    <p>Two way binding data.</p>

    <p>Binding data from  view to JS</p>

    <input type="text" onkeypress="myFunction()" id="myinput">
    <p id="myid"></p>
    <p>Binding data from  js to view</p>
    <input type="text" id="myid2" onkeypress="myFunction1()" oninput="myFunction1()">
    <p id="myid3" onkeypress="myFunction1()" id="myinput" oninput="myFunction1()"></p>

    <script>

        document.getElementById('myid2').value="myvalue from script";
        document.getElementById('myid3').innerHTML="myvalue from script";
        function myFunction() {
            document.getElementById('myid').innerHTML=document.getElementById('myinput').value;
        }
        document.getElementById("myinput").onchange=function(){

            myFunction();

        }
        document.getElementById("myinput").oninput=function(){

            myFunction();

        }

        function myFunction1() {

            document.getElementById('myid3').innerHTML=document.getElementById('myid2').value;
        }
    </script>

</body>
</html>

在这个链接“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" />