在实现双向数据绑定的方法中主要有如下几种方式:
(1)发布订阅者模式(backbone.js)
(2)脏检查(angular.js)
(3)数据劫持
在vue中是结合了(1)和(3)两种方式来实现的,主要通过Object.defineProperty()方式进行数据劫持,然后订阅发布者模式监测数据的变化并进行广播。所谓的双向数据绑定无非是在单向数据绑定的基础上,利用change(H5中input)事件监听表单元素的变化,在事件回调函数中修改vue中的属性的值。
可以参考下面的文章:文章里介绍的比较详细
下面附上自己实现的代码(本代码中只实现了v-model指令的匹配):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>双向数据绑定</title>
</head>
<body>
<div id="app">
<input type="text" v-model="text">
{{ text }}
</div>
<script type="text/javascript">
/*几种实现双向数据绑定的方式
* (1)发布订阅者模式(backbone.js)
* (2)脏检查 (angular.js)
* (3)数据劫持(vue.js)
* vue中则是利用的发布订阅者和数据劫持Object.defineProperty()相结合的方式来实现双向数据绑定
* * */
function Vue(){
this.options = arguments[0];
data = this.options.data;
observe(data,this);
var id = this.options.el;
var dom = nodeToFragment(document.getElementById(id),this);
document.getElementById(id).appendChild(dom)
}
function nodeToFragment(node,vm){
var nodeFragment = document.createDocumentFragment();
var child;
while(child = node.firstChild){
compile(child,vm);
nodeFragment.append(child);
}
return nodeFragment;
}
function compile(node,vm) {
var reg = /\{\{(.*)\}\}/;
if(node.nodeType === 1){
var attr = node.attributes;/*获取所有的属性节点*/
for(var i=0;i<attr.length;i++){
if(attr[i].nodeName === "v-model"){
var name = attr[i].nodeValue; // 获取v-model绑定的属性名
node.addEventListener("input",function (e) {
vm[name] = e.target.value;
});
node.value = vm[name];
node.removeAttribute("v-model");
}
}
new Watcher(vm,node,name,'input');
}
if(node.nodeType === 3){
if(reg.test(node.nodeValue)){
var name = RegExp.$1;
name = name.trim();
new Watcher(vm,node,name,"text");
}
}
}
function observe(data,vm) {
if(!data || typeof data !== "object")
{
return;
}
Object.keys(data).forEach(function (key) {
defineReactive(vm,key,data[key]);
})
}
function defineReactive(obj,key,val) {
var dep =new Dep();
if(typeof val ==="object")
{
observe(val);
}
Object.defineProperty(obj,key,{
get:function () {
Dep.target && dep.addSub(Dep.target);
return val;
},
set:function (newVal) {
if(val === newVal){
return;
}
val = newVal;
dep.notify();
}
})
}
function Dep() {
this.subs = [];
}
Dep.prototype ={
addSub:function (sub) {
this.subs.push(sub);
},
notify:function () {
this.subs.forEach(function (sub) {
sub.update();
})
}
}
function Watcher (vm, node, name, nodeType) {
Dep.target = this;
this.name = name;
this.node = node;
this.vm = vm;
this.nodeType = nodeType;
this.update();
Dep.target = null;
}
Watcher.prototype = {
update: function () {/*在实际的vue当中不会直接执行update方法,会将监听到的变化压入到堆栈中,在一定的时机之后在执行,进行一步优化,防止过于频繁的操作dom*/
this.get();
if (this.nodeType == 'text') {
this.node.nodeValue = this.value;
}
if (this.nodeType == 'input') {
this.node.value = this.value;
}
},
// 获取data中的属性值
get: function () {
this.value = this.vm[this.name]; // 触发相应属性的get
}
}
var vm = new Vue({
el: 'app',
data: {
text: 'hello world'
}
});
</script>
</body>
</html>