昨天写了一下节点绑定 model 的替换,已经如何检测 model 的方法
今天优化一下,勉强实现一个双向绑定
看下昨天的代码
<script>
function Vue(obj) {var app = document.querySelector(obj.el);
for(let i in obj.data) {this[i] = obj.data[i];
}
var html = app.childNodes;
var that = this;
html.forEach(function(data) {if(data.nodeName == '#text') {data.data = data.data.replace(/\{\{([\s\S]+)\}\}/g, function(a, b){return that[b]})
}
})
}
</script>
<div id='app'>
这里是{{title}}
</div>
<script>
var app = new Vue({
el: '#app',
data: {title: 'Hello World'}
})
</script>
在实现双向绑定之前,先普及一个 set 和 get 的知识。我说点大白话,反正也不是专业的。
一个对象的 key 在被赋值 value 的时候,被拦截,触发 set 方法,set 的参数就是那个 value
但是这个赋值就会被拦截了,所以需要给一个新的 key 一个值。然后把 value 赋值到新的值上面去。如果强行给之前的 key 赋值,就会发生无限 set 的情况。
稍微加工了一下
<script>
function Vue(obj) {this.app = document.querySelector(obj.el); // 获取函数作用域
this.data = obj.data; // 获取 data 值
this.getData(this.data); // 设置钩子函数
this.getWatcher(this.app.childNodes); // 获得包含 Model 的节点
}
Vue.prototype.getData = function(data) {for(var i in data) { // 遍历 data 里面的数据
Object.defineProperty(this, i, { // 在 this 里面 设置同名 key, 比如,data 里面有 title, 那么就去设置一个 this.title
set: function(e) {this.data[i] = e; // 当 this.title 被设置 value 的时候,将 this.data.title = value
this.digest(i); // 更新包含这个 model 的 dom
}.bind(this),
get: function() {return this.data[i]; // 想获取 this.title 的话,就去调取 this.data.title 返回
}
})
}
}
Vue.prototype.getWatcher = function(inner) { // 这个代码昨天写过,就是去检测节点中包不包含 model
this.watcher = {};
var that = this;
inner.forEach(function(data) {if(data.nodeName == '#text') {data.data = data.data.replace(/\{\{([\s\S]+)\}\}/g, function(a, b){data.realData = data.data; // 自定义一个属性 realData,把原始内容保存一下,方便复用 dom.realData = 这里是{{title}}
if(!that.watcher[b])
that.watcher[b] = [];
that.watcher[b].push(data); // 把这个节点添加到被监听对象中去 {title:[dom]}
return that[b]; // 先把 {{title}} 替换成 helloworld
})
}
})
}
Vue.prototype.digest = function(e) {
var that = this;
this.watcher[e].forEach(function(node) {node.data = node.realData.replace(/\{\{([\s\S]+)\}\}/g, function(a, b) {return that[b]}); // 把 title 那个节点改成新的数值
})
}
</script>
<div id='app'>
这里是{{title}}
</div>
<script>
var app = new Vue({
el: '#app',
data: {title: 'Hello World'}
})
setTimeout(function(){app.title = 5;}, 1000)
setTimeout(function(){app.title = 10;}, 2000)
</script>
代码有些粗劣,但是功能是实现了。1. 首先 先读取 new Vue 传入对象时候,data 里面的数据,然后对其进行遍历,给 实例化对象添加 同名属性,这个同名属性主要就是用来被监听 set,get(双向绑定实现的基本原理)。也就是 vue.prototype.getData
2. 然后呢,去递归 dom 树(这个以后再写,先简单就一层,不然写的内容会增多很多),然后用正则过滤出里面 {{}}类型的节点,将 这个节点的原始内容(比如 {{title}} 保存到这个节点的一个自定义属性里,我写的是 realData),并将这个节点添加到 watcher 里面去。也就是 vue.prototype.watcher
3. 然后呢,在同名属性被设置的时候呢,就触发钩子函数,将这个同名属性所涉及到的节点的 data 给更新。也就是 vue.prototype.digest
如果上面的内容没有看的太明白的话,可以参考一下代码注释。。。我下线看看怎么优化代码或者优化思路,晚上或者明天更新。我还得看看 vue 的文档。毕竟还在正式学习期。