原文转自:http://www.sellarafaeli.com/blog/native_javascript_data_binding

Two-way data-binding is such an important feature - align your JS models with your HTML view at all times, to reduce boilerplate coding and enhance UX. We will observe two ways of doing this using native JavaScript, with no frameworks - one with revolutionary technology (Object.observe), and one with an original concept (overriding get/set). Spoiler alert - the second one is better. See TL;DR at bottom.

1: Object.observe && DOM.onChange

Object.observe() is the new kid on the block. This native JS ability - well, actually it’s a future ability since it’s only proposed for ES7, but it’s already[!] available in the current stable Chrome - allows for reactive updates to changes to a JS object. Or in simple English - a callback run whenever an object(‘s properties) change(s).

An idiomatic usage could be:

log = console.log
user = {}
Object.observe(user, function(changes){
    changes.forEach(function(change) {
        user.fullName = user.firstName + " " + user.lastName;
    });
});

user.firstName = 'Bill';
user.lastName = 'Clinton';
user.fullName // 'Bill Clinton'

This is already pretty cool and allows reactive programming within JS - keeping everything up-to-date by push.

But let’s take it to the next level:

//<input id="foo">
user = {};
div = $("#foo");
Object.observe(user, function(changes){
    changes.forEach(function(change) {
        var fullName = (user.firstName || "") + " " + (user.lastName || "");
        div.text(fullName);
    });
});

user.firstName = 'Bill';
user.lastName = 'Clinton';

div.text() //Bill Clinton

JSFiddle

Cool! We just got model-to-view databinding! Let’s DRY ourselves with a helper function.

//<input id="foo">
function bindObjPropToDomElem(obj, property, domElem) {
  Object.observe(obj, function(changes){
    changes.forEach(function(change) {
      $(domElem).text(obj[property]);
    });
  });
}

user = {};
bindObjPropToDomElem(user,'name',$("#foo"));
user.name = 'William'
$("#foo").text() //'William'

JSFiddle

Sweet!

Now for the other way around - binding a DOM elem to a JS value. A pretty good solution could be a simple use of jQuery’s .change (http://api.jquery.com/change/):

//<input id="foo">
$("#foo").val("");
function bindDomElemToObjProp(domElem, obj, propertyName) {
  $(domElem).change(function() {
    obj[propertyName] = $(domElem).val();
    alert("user.name is now "+user.name);
  });
}

user = {}
bindDomElemToObjProp($("#foo"), user, 'name');
//enter 'obama' into input
user.name //Obama.

JSFiddle

That was pretty awesome. To wrap up, in practice you could combine the two into a single function to create a two-way data-binding:

function bindObjPropToDomElem(obj, property, domElem) {
  Object.observe(obj, function(changes){
    changes.forEach(function(change) {
      $(domElem).text(obj[property]);
    });
  });
}

function bindDomElemToObjProp(obj, propertyName, domElem) {
  $(domElem).change(function() {
    obj[propertyName] = $(domElem).val();
    console.log("obj is", obj);
  });
}

function bindModelView(obj, property, domElem) {
  bindObjPropToDomElem(obj, property, domElem)
  bindDomElemToObjProp(obj, propertyName, domElem)
}

Take note to use the correct DOM manipulation in case of a two-way binding, since different DOM elements (input, div, textarea, select) answer to different semantics (text, val). Also take note that two-way data-binding is not always necessary – “output” elements rarely need view-to-model binding and “input” elements rarely need model-to-view binding. But wait – there’s more:

2: Go deeper: Changing ‘get’ and ‘set’

We can do even better than the above. Some issues with our above implementation is that using .change breaks on modifications that don’t trigger jQuery’s “change” event - for example, DOM changes via the code, e.g. on the above code the following wouldn’t work:

$("#foo").val('Putin')
user.name //still Obama. Oops.

We will discuss a more radical way - to override the definition of getters and setters. This feels less ‘safe’ since we are not merely observing, we will be overriding the most basic of language functionality, get/setting a variable. However, this bit of metaprogramming will allow us great powers, as we will quickly see.

So, what if we could override getting and setting values of objects? After all, that’s exactly what data-binding is. Turns out that using Object.defineProperty() we can in fact do exactly that.

We used to have the old, non-standard, deprecated way but now we have the new cool (and most importantly, standard) way, using Object.defineProperty, as so:

user = {}
nameValue = 'Joe';
Object.defineProperty(user, 'name', {
  get: function() { return nameValue },
  set: function(newValue) { nameValue = newValue; },
  configurable: true //to enable redefining the property later
});

user.name //Joe
user.name = 'Bob'
user.name //Bob
nameValue //Bob

OK, so now user.name is an alias for nameValue. But we can do more than just redirect the variable to be used - we can use it to create an alignment between the model and the view. Observe:

//<input id="foo">
Object.defineProperty(user, 'name', {
  get: function() { return document.getElementById("foo").value },
  set: function(newValue) { document.getElementById("foo").value = newValue; },
  configurable: true //to enable redefining the property later
});

user.name is now binded to the input #foo. This is a very concise expression of ‘binding’ at a native level - by defining (or extending) the native get/set. Since the implementation is so concise, one can easily extend/modify this code for custom situation - binding only get/set or extending either one of them, for example to enable binding of other data types.

As usual we make sure to DRY ourselves with something like:

function bindModelInput(obj, property, domElem) {
  Object.defineProperty(obj, property, {
    get: function() { return domElem.value; },
    set: function(newValue) { domElem.value = newValue; },
    configurable: true
  });
}

usage:

user = {};
inputElem = document.getElementById("foo");
bindModelInput(user,'name',inputElem);

user.name = "Joe";
alert("input value is now "+inputElem.value) //input is now 'Joe';

inputElem.value = 'Bob';
alert("user.name is now "+user.name) //model is now 'Bob';

JSFiddle

Note the above still uses ‘domElem.value’ and so will still work only on <input> elements. (This can be extended and abstracted away within the bindModelInput, to identify the appropriate DOM type and use the correct method to set its ‘value’).

Discussion:

  • DefineProperty is available in pretty much every browser.
  • It is worth mentioning that in the above implementation, the view is now the ‘single point of truth’ (at least, to a certain perspective). This is generally unremarkable (since the point of two-way data-binding means equivalency. However on a principle level this may make some uncomfortable, and in some cases may have actual effect - for example in case of a removal of the DOM element, would our model would essentially be rendered useless? The answer is no, it would not. Our bindModelInput creates a closure over domElem, keeping it in memory - and perserving the behavior a la binding with the model - even if the DOM element is removed. Thus the model lives on, even if the view is removed. Naturally the reverse is also true - if the model is removed, the view still functions just fine. Understanding these internals could prove important in extreme cases of refreshing both the data and the view.

Using such a bare-hands approach presents many benefits over using a framework such as Knockout or Angular for data-binding, such as:

  • Understanding: Once the source code of the data-binding is in your own hands, you can better understand it and modify it to your own use-cases.
  • Performance: Don’t bind everything and the kitchen sink, only what you need, thus avoiding performance hits at large numbers of observables.
  • Avoiding lock-in: Being able to perform data-binding yourself is of course immensely powerful, if you’re not in a framework that supports that.

One weakness is that since this is not a ‘true’ binding (there is no ‘dirty checking’ going on), some cases will fail - updating the view will not ‘trigger’ anything in the model, so for example trying to ‘sync’ two dom elements via the view will fail. That is, binding two elements to the same model will only refresh both elements correctly when the model is ‘touched’. This can be amended by adding a custom ‘toucher’:

//<input id='input1'>
//<input id='input2'>
input1 = document.getElementById('input1')
input2 = document.getElementById('input2')
user = {}
Object.defineProperty(user, 'name', {
  get: function() { return input1.value; },
  set: function(newValue) { input1.value = newValue; input2.value = newValue; },
  configurable: true
});
input1.onchange = function() { user.name = user.name } //sync both inputs.

TL;DR:

Create a two way data-binding between model and view with native JavaScript as such:

function bindModelInput(obj, property, domElem) {
  Object.defineProperty(obj, property, {
    get: function() { return domElem.value; },
    set: function(newValue) { domElem.value = newValue; },
    configurable: true
  });
}

//<input id="foo">
user = {}
bindModelInput(user,'name',document.getElementById('foo')); //hey presto, we now have two-way data binding.

Thanks for reading. Comments at discussion on reddit or at sella.rafaeli@gmail.com.

【转】Native JavaScript Data-Binding的更多相关文章

  1. Data Binding使用技巧

    Data Binding 根据变量,自动赋值到各widget. How 1.编写layout文件,这里的layout为: act_data_bind_demo.xml 这里需要先准备变量 在具体的wi ...

  2. Data Binding和INotifyPropertyChanged是如何协调工作的?

    前言 WPF的一大基础就是Data Binding.在基于MVVM架构的基础上,只有通过实现INotifyPropertyChanged接口的ViewModel才能够用于Data Binding. 要 ...

  3. WPF QuickStart系列之数据绑定(Data Binding)

    这篇博客将展示WPF DataBinding的内容. 首先看一下WPF Data Binding的概览, Binding Source可以是任意的CLR对象,或者XML文件等,Binding Targ ...

  4. XAML数据绑定(Data Binding)

    XAML数据绑定(Data Binding)   Data Binding可以使得XAML标签属性的赋值更为灵活和方便.在绑定过程中,获取数据的标签成为目标标签:提供数据的标签成为源标签.在XAML中 ...

  5. Optimizing Performance: Data Binding(zz)

    Optimizing Performance: Data Binding .NET Framework 4.5 Other Versions   Windows Presentation Founda ...

  6. .NET: WPF Data Binding

    WPF是分离UI和Logic的最佳工具,不同于Window Form的事件驱动原理,WPF采用的是数据驱动,让UI成为了Logic的附属,达到分离的效果. 本篇主要讲讲wpf的精华:data bind ...

  7. WP8.1 Study5:Data binding数据绑定

    一.数据绑定 最简单的编程UI控件的方法是写自己的数据来获取和设置控件的属性,e.g. , textBox1.Text = "Hello, world"; 但在复杂的应用程序,这样 ...

  8. Data Binding in WPF

    http://msdn.microsoft.com/en-us/magazine/cc163299.aspx#S1   Data Binding in WPF John Papa Code downl ...

  9. Data Binding(数据绑定)用户指南

    1)介绍 这篇文章介绍了如何使用Data Binding库来写声明的layouts文件,并且用最少的代码来绑定你的app逻辑和layouts文件. Data Binding库不仅灵活而且广泛兼容- 它 ...

随机推荐

  1. 程设大作业xjb写——魔方复原

    鸽了那么久总算期中过[爆]去[炸]了...该是时候写写大作业了 [总不能丢给他们不会写的来做吧 一.三阶魔方的几个基本定义 ↑就像这样,可以定义面的称呼:上U下D左L右R前F后B UD之间的叫E,LR ...

  2. [原创]C#应用WindowsApi实现查找(FindWindowEx)文本框(TextBox、TextEdit)。

    /// <summary> /// 获取文本框控件 /// </summary> /// <param name="hwnd">文本框所在父窗口 ...

  3. 当 jquery.unobtrusive-ajax.js 遇上Web API

    最近在熟悉Abp框架,其基于DDD领域驱动设计...前段可以绕过mvc直接调用根据app层动态生成的webapi,有点神奇~,Web API之前有简单接触过,WCF的轻量级版,一般用于做一写开发性的服 ...

  4. Oracle连接数据库的封装类OracleDB

    import java.sql.Connection;import java.sql.DriverManager;import java.sql.ResultSet;import java.sql.S ...

  5. IIS7 WebAPI 404.0 Error

    <system.webServer><modules runAllManagedModulesForAllRequests="true"/></sys ...

  6. php 数组的常用函数

    在php教程中数组是种强大的数据类型,他可以做的事情很多,可以存储不同的数据类型在一个数组中,下面我们列出了数组常用的操作,排序,键名对数组排序等做法. /* 数组的常用函数  *  * 数组的排序函 ...

  7. windows8.1下javaweb环境搭建及基本配置(jdk+tomcat+eclipse)

    1.下载安装jdk在无空格的路径下,否则在linux下可能出问题.配置环境变量: a.新建系统变量——JAVA_HOME,值——D:\programming\java\jdk8 // win8下若建为 ...

  8. C# 版本的冒泡排序,包括该死的控制台读取

    期末出成绩了,绩点被数分拉下来太多,虽然我很想不在意,但是还是受不了 学了两天的JAVA了,无爱,还是喜欢C#,喜欢VS 一直学一下控制台读取来着,但是C#控制台读取真的很麻烦 using Syste ...

  9. wpf初步-grid布局-连连看棋盘

    private void Window_Loaded_1(object sender, RoutedEventArgs e) { //Button btn1 = new Button(); //btn ...

  10. hihoCoder#1000

    刚开始学习C语言,准备在做hiho的题目的过程中来学习,在此进行记录,如果代码中有错误或者不当的地方还请指正.   时间限制:1000ms 单点时限:1000ms 内存限制:256MB 描述 求两个整 ...