前言
此文采用了设计模式中的观察者模式,模拟实现了 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)
