使用 vue 已经很久了,vue的一大特性,双向绑定的原理也了解一个大概,大致是通过 Object.defineProperty()的 set 和 get 方法来劫持属性,修改属性。这次疫情在家无事,看了不少这方面的源码,所以打算自己写一个简要版本的双向绑定.考虑不周,大家发现问题的话,欢迎提出来,一起学习。
实现思路
1.需要一个 oberser,用来监听所有的数据和属性变化,有变动通知订阅者
2.需要一个compile 扫描节点的指令,初始化模板,更新视图
3.需要一个watcher 接受受来自于oberser 的变化,然后执行响应的方法
oberser
Vue.prototype.oberser = function (obj) {
let that = this
Object.keys(obj).forEach(key => {
var value = obj[key]; //获取循环每一项的值
Object.defineProperty(that.data,key, {
configurable: true, // 可以删除
enumerable: true, // 可以遍历
get() {
return value; // 获取值的时候 直接返回
},
set(newVal) { // 改变值的时候 触发set
value = newVal;
that.Pool.notice() //执行方法更新视图
}
})
})
}
这里直接使用 forEach 来循环传入的数据,然后我们就可以使用 Object.defineProperty来获取数据和设置新的数据。
compile
Vue.prototype.compile = function (el) {
let that = this,nodes = el.children;
for(let i = 0;i<nodes.length;i++) {
if(nodes[i].hasAttribute('v-model') && nodes[i].tagName == 'INPUT'){
nodes[i].addEventListener('input',(function(key) { //监听输入框的事件
var attVal = nodes[i].getAttribute('v-model');
that.Pool.add(
new Watcher(
nodes[i],
that,
attVal,
'value'
)
)
return function () {
that.data[attVal] = nodes[key].value; // input值改变的时候 将新值赋给数据 触发set=>set触发watch 更新视图
}
})(i))
}
if (nodes[i].hasAttribute('v-bind')) { // v-bind指令
var attVal = nodes[i].getAttribute('v-bind'); // 绑定的data
that.Pool.add(new Watcher(
nodes[i],
that,
attVal,
'innerHTML'
))
}
}
}
循环 app,里面的子节点,如果是 输入框并且有 v-modle的话,就给输入框一个监听事件,获取值,并且往订阅池里面添加一个 watcher,后续用来更新输入框。 如果节点是有 v-bind 的话,也是往订阅池添加一个 watcher,但是这次是改成了innerHTML,后续用来改变文本
Watcher
function Watcher(el,vm,val,attr) {
this.el =el;
this.vm = vm;
this.val = val;
this.attr = attr;
this.update();
}
Watcher.prototype.update = function () {
this.el[this.attr] = this.vm.data[this.val]
}
上面是一个watcher ,用来接收来自 compile 的数据,watcher有一个更新方法,根据传入的节点,更新页面
function Pool() {
this.subs = [],
this.add(),
this.notice()
}
Pool.prototype.add = function (sub) {
if(sub) {
this.subs.push(sub)
}
}
Pool.prototype.notice = function () {
this.subs.forEach(item => {
item.update()
})
}
这是一个订阅池,用来收集所有的 watcher,里面有两个属性,一个是添加新的watcher,另一个循环执行 watcher 的更新方法
总结
最后所有的函数都需要页面加载之后,把他们放到一个主函数里面。
window.onload = function () {
//加载好页面之后就加载实例
var app = new Vue({
el: '#app',
data: {
data1: '888888'
}
})
}
function Vue(config = {}) {
this.watcher = {}
this.data = config.data
this.Pool = new Pool()
this.oberser(config.data)
this.compile(document.querySelector(config.el))
}
如说多层嵌套之后就不能使用了,但是精力有限,只能先到这里。