javascript中的双向绑定
阅读目录
前言:
双向数据绑定的含义:可以将对象的属性绑定到UI,具体的说,我们有一个对象,该对象有一个name属性,当我们给这个对象name属性赋新值的时候,新值在UI上也会得到更新。同样的道理,当我们有一个输入框或者textarea的时候,我们输入一个新值的时候,也会在该对象的name属性得到更新。
双向数据绑定的思想是:
1. 我们需要一个方法来识别那个UI元素被绑定了相对应的属性。
2. 我们需要监听属性和UI元素的变化。
3. 我们需要将所有的变化传播到绑定的对象和元素上。
实现数据绑定的做法有如下几种:
1. 发布者--订阅模式(backbone.js)
2. 脏值检查(angular.js)
3. 数据劫持 (vue.js)
一:发布订阅模式实现数据双向绑定
我们现在使用 发布者-订阅模式来实现一个简单的 双向绑定数据;
发布-订阅模式的原理:它是一种一对多的关系,让多个观察者对象同时监听某一个主题对象,当一个对象发生改变时,所有依赖于它的对象都将得到通知。
先来理解下 现实生活中的发布-订阅模式
比如小红最近在淘宝网上看上一双鞋子,但是呢 联系到卖家后,才发现这双鞋卖光了,但是小红对这双鞋又非常喜欢,所以呢联系卖家,问卖家什么时候有货,卖家告诉她,要等一个星期后才有货,卖家告诉小红,要是你喜欢的话,你可以收藏我们的店铺,等有货的时候再通知你,所以小红收藏了此店铺,但与此同时,小明,小花等也喜欢这双鞋,也收藏了该店铺;等来货的时候就依次会通知他们;
在上面的故事中,可以看出是一个典型的发布订阅模式,卖家是属于发布者,小红,小明等属于订阅者,订阅该店铺,卖家作为发布者,当鞋子到了的时候,会依次通知小明,小红等。
发布订阅模式的优点:
1. 支持简单的广播通信,当对象状态发生改变时,会自动通知已经订阅过的对象。
2. 发布者与订阅者耦合性降低,发布者只管发布一条消息出去,它不关心这条消息如何被订阅者使用,同时,订阅者只监听发布者的事件名,只要发布者的事件名不变,它不管发布者如何改变;
发布订阅模式的缺点:
1. 创建订阅者需要消耗一定的时间和内存。
2. 虽然可以弱化对象之间的联系,如果过度使用的话,反而使代码不好理解及代码不好维护等等。
实现发布-订阅模式的步骤:
1. 谁是发布者?(比如上面的卖家)
2. 给发布者添加一个缓存列表,用于存放回调函数来通知订阅者。
3. 发布消息,发布者遍历这个缓存列表,依次触发存放的订阅者回调函数。
下面我们先来实现一个简单的发布-订阅模式(用户订阅鞋子的简单demo如下:)
// 定义发布者
var pubSub = {
// 缓存列表,存放订阅者的回调函数
callbacks: [],
// 增加订阅者
on: function(msg, callback) {
if (!this.callbacks[msg]) {
// 如果没有订阅过此消息,给这个消息创建一个缓存列表
this.callbacks[msg] = [];
}
this.callbacks[msg].push(callback);
},
// 发布消息
public: function() {
// 取出该消息对应的回调函数集合
var key = Array.prototype.shift.call(arguments);
var fns = this.callbacks[key]; // 如果没有订阅过该消息的话,直接返回
if(!fns || fns.length === 0) {
return;
}
for(var i = 0, fn; fn = fns[i++]; ) {
fn.apply(this, arguments);
}
}
};
// 现在空智 订阅如下消息
pubSub.on('red', function(size) {
console.log("你的尺码是:"+size); // 控制台会打印出: 你的尺码是:40
}); // 空智2 订阅如下消息
pubSub.on('blue', function(size) {
console.log("你的尺码是:"+size); // 控制台会打印出: 你的尺码是:42
}); // 发布消息
pubSub.public('red', 40);
pubSub.public('blue', 42);
通过上面了解了简单的发布-订阅模式,现在我们来看看使用发布-订阅模式来实现双向绑定。
思路如下:
在HTML代码中使用一个自定义属性进行绑定,所有进行绑定的javascript对象以及DOM元素都将订阅一个发布者对象。不管什么时候如果一个javascript对象或一个输入框被监听到发生变化时候,所有依赖发布者对象都将会得到通知。
代码如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no" name="viewport">
<meta content="yes" name="apple-mobile-web-app-capable">
<meta content="black" name="apple-mobile-web-app-status-bar-style">
<meta content="telephone=no" name="format-detection">
<meta content="email=no" name="format-detection">
<title>标题</title>
<link rel="shortcut icon" href="/favicon.ico">
</head>
<body>
<h3>发布订阅模式实现数据双向绑定demo</h3>
<input type="text" id="inputId" data-bind-objId="name" style="border: 1px solid #ccc; width:200px; height: 24px; "/>
<div id="modelView" style="border: 1px solid red; width: 200px; height: 24px; margin-top:20px; margin-bottom:20px;"></div>
<button id="btn">model的变化导致view的变化</button>
<script>
function DataBinder(objId, changeId) {
// 发布订阅原型
var pubSub = {
allCallbacks: [],
// 增加订阅者
on: function(eventName, callback) {
// 如果没有订阅过该消息,给这个消息创建一个缓存列表
if(!this.allCallbacks[eventName]) {
this.allCallbacks[eventName] = [];
}
this.allCallbacks[eventName].push(callback);
},
// 发布消息
public: function() { var eventName = Array.prototype.shift.call(arguments);
// 取出该消息对应的回调函数集合
var callbacks = this.allCallbacks[eventName];
if (!callbacks || callbacks.length === 0) {
return false;
}
for (var i = 0; i < callbacks.length; i++) {
var callback = callbacks[i];
callback.apply(this, arguments);
}
}
};
var dataAttr = "data-bind-" + objId;
var message = objId + ":change"; var changeHandler = function(e) {
var target = e.target || e.srcElement;
var attrName = target.getAttribute(dataAttr);
if (attrName && attrName !== "") {
// 发布消息
pubSub.public(message, attrName, target.value);
}
};
// 监听视图层的事件变化
if (document.addEventListener) {
document.addEventListener('input', changeHandler, false);
} else {
document.attachEvent("oninput", changeHandler);
} // 监听模型上的变化,并把变化传播到所有绑定的元素上
pubSub.on(message, function(attrName, newVal) {
var elements = document.querySelectorAll("[" + dataAttr + "=" + attrName + "]");
var tagName;
for (var i = 0, ilen = elements.length; i < ilen; i++) {
tagName = elements[i].tagName.toLowerCase();
if (tagName === 'input' || tagName === 'textarea' || tagName === 'select') {
elements[i].value = newVal;
changeId.innerHTML = newVal;
} else {
elements[i].innerHTML = newVal;
changeId.innerHTML = newVal;
}
}
});
return pubSub;
} // 定义一个User模型
function User(uid, changeId) {
var binder = new DataBinder(uid, changeId);
var user = {
attrs: {},
set: function(key, value){
this.attrs[key] = value;
// model变化通知更新view
binder.public(uid + ":change", key, value);
},
get: function(key) {
return this.attrs[key];
}
};
return user;
} // 绑定model到view
var modelView = document.getElementById("modelView");
var inputId = document.getElementById("inputId"); // 测试demo
var user = new User("objId", modelView);
user.set("name", 1); modelView.innerHTML = user.get("name"); // 测试模型的变化到 视图层的变化
var btn = document.getElementById("btn");
btn.onclick = function() {
var value = inputId.value;
user.set("name", parseInt(value) + 1);
modelView.innerHTML = user.get("name");
};
</script>
</body>
</html>
二:使用Object.defineProperty 来实现简单的双向绑定。
想了解 Object.defineProperty 时的话 请看这篇文章
实现的效果简单如下:页面上有一个input输入框和div显示框,当在input输入框输入值的时候,div也会显示对应的值,当我打开控制台改变 obj.name="输入任意值"的时候,按回车键运行下,input输入框的值也会跟着变,可以简单的理解为 模型-> 视图的 改变,以及 视图 -> 模型的改变。如下代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no" name="viewport">
<meta content="yes" name="apple-mobile-web-app-capable">
<meta content="black" name="apple-mobile-web-app-status-bar-style">
<meta content="telephone=no" name="format-detection">
<meta content="email=no" name="format-detection">
<title>标题</title>
<link rel="shortcut icon" href="/favicon.ico">
</head>
<body>
<h3>使用Object.defineProperty实现简单的双向数据绑定</h3>
<input type="text" id="input" />
<div id="div"></div>
<script>
var obj = {};
var inputVal = document.getElementById("input");
var div = document.getElementById("div"); Object.defineProperty(obj, "name", {
set: function(newVal) {
inputVal.value = newVal;
div.innerHTML = newVal;
}
});
inputVal.addEventListener('input', function(e){
obj.name = e.target.value;
});
</script>
</body>
</html>
javascript中的双向绑定的更多相关文章
- AngularJS中数据双向绑定(two-way data-binding)
1.切换工作目录 git checkout step-4 #切换分支,切换到第4步 npm start #启动项目 2.代码 app/index.html Search: <input ng-m ...
- React中的“双向绑定”
概述 React并不是一个MVVM框架,其实它连一个框架都算不上,它只是一个库,但是react生态系统中的flux却是一个MVVM框架,所以我研究了一下flux官方实现中的"双向绑定&quo ...
- JavaScript中this的绑定规则
JavaScript中this的绑定规则 前言 我们知道浏览器运行环境下在全局作用域下的this是指向window的,但是开发中却很少在全局作用域下去使用this,通常都是在函数中进行使用,而函数使用 ...
- vue中数据双向绑定注意点
最近一个vue和element的项目中遇到了一个问题: 动态生成的对象进行双向绑定是失败 直接贴代码: <el-form :model="addClass" :rules=& ...
- vue中数据双向绑定的实现原理
vue中最常见的属v-model这个数据双向绑定了,很好奇它是如何实现的呢?尝试着用原生的JS去实现一下. 首先大致学习了解下Object.defineProperty()这个东东吧! * Objec ...
- vue中的双向绑定
概述 今天对双向绑定感兴趣了,于是去查了下相关文章,发现有用脏检查的(angular.js),有用发布者-订阅者模式的(JQuery),也有用Object.defineProperty的(vue),其 ...
- wp中的双向绑定
using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; usin ...
- 理解Javascript中的事件绑定与事件委托
最近在深入实践js中,遇到了一些问题,比如我需要为动态创建的DOM元素绑定事件,那么普通的事件绑定就不行了,于是通过上网查资料了解到事件委托,因此想总结一下js中的事件绑定与事件委托. 事件绑定 ...
- 利用JS实现vue中的双向绑定
Vue 已经是主流框架了 它的好处也不用多说,都已经是大家公认的了 那我们就来理解一下Vue的单向数据绑定和双向数据绑定 然后再使用JS来实现Vue的双向数据绑定 单向数据绑定 指的是我们先把模板写好 ...
随机推荐
- ImageMagick图片服务器
1.前置准备工具如下: nodejs express(nodejs mvc框架) body-parser(express middleware) gm(nodejs中用来处理图片的) uuid(nod ...
- [转载] Spark:大数据的“电光石火”
转载自http://www.csdn.net/article/2013-07-08/2816149 Spark已正式申请加入Apache孵化器,从灵机一闪的实验室“电火花”成长为大数据技术平台中异军突 ...
- Linux命令kill和signal
Linux命令kill和signal kill命令用于终止指定的进程(terminate a process),是Unix/Linux下进程管理的常用命令.通常,我们在需要终止某个或某些进程时,先使用 ...
- Ext3和Ext4文件系统区别
inode http://www.cnblogs.com/itech/archive/2012/05/15/2502284.html Ex3使用15个inode查询数据块,前12个为直接数据块,直接指 ...
- JAVA提高十三:Hashtable&Properties深入分析
最近因为一些琐碎的事情,导致一直没时间写博客,正好今天需求开发完的早,所以趁早写下本文,本文主要学习的是Hashtable的分析,因为上面一篇文章研究的是HashMap,而Hashtable和Hash ...
- No bean named 'sqlSessionFactory' is defined
其实程序给出这个错误已经很明确了,说你的sqlSessionFactory是未定义的. 首先你要查看一下自己的web.xml文件是否配置了application*.xml <!--设置sprin ...
- smartClient 1--框架介绍
一.是什么(以下简称SC) smartClient 是一个基于web技术的开发框架,主要包括: 一个无需安装的 Ajax/HTML5 客户端引擎 UI组件和服务(采用富客户端RIA)--- 提 ...
- 【机器学习】DNN训练中的问题与方法
感谢中国人民大学的胡鹤老师,人工智能课程讲的很有深度,与时俱进 由于深度神经网络(DNN)层数很多,每次训练都是逐层由后至前传递.传递项<1,梯度可能变得非常小趋于0,以此来训练网络几乎不会有什 ...
- laravel whereDate()方法的使用
今天在做日期查询,过过滤的时候,发现了一个问题,用where()语句,要求时间格式比较严格,比如你要查2017-09-23这一天的活动,你在输入框传入2017-09-23,系统会默认帮你补上后面缺少的 ...
- LINQ学习系列-----1.3 扩展方法
这篇内容继续接着昨天的Lambda表达式的源码继续下去.昨天讲了Lambda表达式,此篇讲扩展方法,这两点都是Linq带来的新特性. 一.扩展方法介绍 废话不多说,先上源码截图: 上图中Ge ...