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

2022-08-30 00:11:53

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

tl;博士

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

数据绑定到 DOM

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

这是AngularJS关于它是什么样子的图表:

two way data binding

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

var a = {b:3};

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

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

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

问题

在普通的JavaScript中实现这一点有哪些基本技术?

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

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

我试过了什么?

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

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

注意:请不要提供使用外部库的答案,尤其是那些包含数千行代码的答案。我用过(并且喜欢!AngularJS 和 KnockoutJS。我真的不想要“使用框架x”形式的答案。理想情况下,我希望未来的读者不知道如何使用许多框架来掌握如何实现双向数据绑定。我不期望一个完整的答案,但一个能把这个想法传达出去的答案。


答案 1
  • 绑定如何为对象工作?
  • 倾听表单的变化如何工作?

更新两个对象的抽象

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

为此提供了一个非常好的界面。您可以为它提供一个实现接口的对象,它将调用其处理程序,并将该对象作为值。.addEventListener()eventListenerthis

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

定义对象

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

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

因此,此处的构造函数将元素和数据存储在新对象的属性上。它还将事件绑定到给定的 .有趣的是,它传递新对象而不是函数作为第二个参数。但仅凭这一点是行不通的。changeelement

实现接口eventListener

要实现此目的,您的对象需要实现接口。完成此操作所需的只是为对象提供一个方法。eventListenerhandleEvent()

这就是继承的用武之地。

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()handleEventchange()

因此,现在,当事件发生时,它将同时更新元素和属性。当你调用你的JavaScript程序时,也会发生同样的情况。change.data.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/


答案 2

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

它使用什么

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

  • MutationObservers 用于检测 dom 中的变化(也使用事件侦听器)
  • Object.observe 来检测对象的变化并通知 dom。危险,由于这个答案已经写成 O.o 已经被 ECMAScript TC 讨论并决定反对,请考虑一个 polyfill

它是如何运作的

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

解决方案

这是函数,请注意,它只有20行代码,可以更短:dataBind

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]; 
}

以下是一些用法:

网页:

<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 和 mutation observer shimming 可用。