前言
此文采用了设计模式中的观察者模式,模拟实现了 Vue 中数据双向绑定的效果。如果您对观察者模式不熟悉,可先阅读《JS 设计模式|观察者模式》,本文只模拟了数据双向绑定的效果,与 Vue 源码有很大出入
实现效果

DOM
使用 Vue 语法规则书写 DOM

Vue 2.0 使用
使用原生的 Vue 2.0 实现数据双向绑定

模拟实现
myVue 类使用

myVue 类实现
myVue 类中实现对 data 的数据监听,DOM 的简单解析

被观察者 data
_obverse 方法通过 Object.defineProperty() 方法来将 data 改造成一个可观察对象

同时将 data 对象属性名作为通知事件的 key 注册到 _directives 对象中,实现 data 中的 value 更新时,通知绑定的观察者更新

按照“myVue 类使用”中的方式调用 myVue 类,当 myVue 类中执行了 this._obverse() 方法后 this._directives 中的属性如下图所示

观察者 Watcher 类
声明观察者类 Watcher 当数据更新时执行 Watcher 实例中的 _update() 方法,更新绑定的 DOM 上的数据

DOM 解析方法
myVue 中的 _compile() 方法用来解析 DOM 节点
遍历 DOM 节点

解析 DOM 节点上的 v-text 属性,为相应的节点绑定 Watcher 监听,当监听的值改变时,通过 Watcher 中的 _update() 方法进行更新

解析 DOM 节点上的 v-model 属性,对此节点注册 input 输入监听,当 input 输入时,更新 data 中相应的值,同时在此节点上绑定 Watcher 监听,将值更新到 DOM 节点的 value 属性上进行值同步

按照“myVue 类使用”中的方式调用 myVue 类,当 myVue 类中执行了 this._obverse() 方法和 this._compile() 方法后 this._directives 中的属性如下图所示

实验源码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
</head>
<body>
<div id="app">
<div class="text" v-text="myText"></div>
<input class="input" type="text" v-model="myText" >
</div>
</body>
<script>
class myVue {
constructor (options) {
this.$el = document.querySelector(options.el)
this.$data = options.data
this._directives = {}
this._obverse(this.$data)
this._compile(this.$el)
}
_obverse (data) {
let _dir
let value
// 遍历 data 对象
Object.entries(data).forEach(dataArray => {
this._directives[dataArray[0]] = [];
value = dataArray[1]
// 值存在且为对象
if(value && value === 'object'){
this._obverse(value)
}
_dir = this._directives[dataArray[0]]
Object.defineProperty(this.$data, dataArray[0], {
enumerable: true,
configurable: true,
get: function() {
return value
},
set: function(newValue) {
// 此处值做简单的恒等比较,不考虑复杂对象
if(value !== newValue){
value = newValue
_dir.forEach(function(item){
item._update();
})
}
}
})
})
}
_compile (el) {
let nodes = el.children
for(let i = 0; i < nodes.length; i ++){
if(nodes[i].children.length){
this._compile(nodes[i])
}
// 处理 dom 上的 v-text 属性
if(nodes[i].hasAttribute('v-text')){
let attrValue = nodes[i].getAttribute('v-text')
this._directives[attrValue].push(new Watcher(nodes[i], this, attrValue, 'innerHTML'))
}
// 处理 input 和 textArea 上的 v-model 属性
if(nodes[i].hasAttribute('v-model') && (nodes[i].tagName === 'INPUT' || nodes[i].tagName === 'TEXTAREA')){
let attrValue = nodes[i].getAttribute('v-model')
this._directives[attrValue].push(new Watcher(nodes[i], this, attrValue, 'value'))
nodes[i].addEventListener('input', () => {
this.$data[attrValue] = nodes[i].value
})
}
}
}
}
class Watcher {
constructor (el, vm, dataKey, elAttr) {
// 需更新的 dom 节点
this.el = el
// myVue 实例
this.vm = vm
// data 的属性名
this.dataKey = dataKey
// dom 节点的属性值
this.elAttr = elAttr
}
_update() {
this.el[this.elAttr] = this.vm.$data[this.dataKey]
}
}
</script>
<script>
const app = new myVue({
el: '#app',
data: {
myText: ''
}
})
</script>
</html>
复制代码
总结
此文参考了大佬的博文进行改写,有兴趣读者可通过“参考资料”跳转阅读,给大老点个赞。这是观察者模式在 JS 项目中的实际应用,通过此例子可以更深层次的理解 Vue 中一些 api 设计的意义,比如 Vue.set() 方法。本人是 react 开发者,没有多少 vue 经验,如果文中存在着 bug,望读者指点























![[桜井宁宁]COS和泉纱雾超可爱写真福利集-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/4d3cf227a85d7e79f5d6b4efb6bde3e8.jpg)

![[桜井宁宁] 爆乳奶牛少女cos写真-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/d40483e126fcf567894e89c65eaca655.jpg)