Vue 模拟|数据双向绑定

前言

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

实现效果

Jietu20210612-194953-HD.gif

DOM

使用 Vue 语法规则书写 DOM

carbon (1).png

Vue 2.0 使用

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

carbon (2).png

模拟实现

myVue 类使用

carbon (5).png

myVue 类实现

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

carbon (1).png

被观察者 data

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

carbon (4).png

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

carbon (3).png

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

image.png

观察者 Watcher 类

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

carbon (11).png

DOM 解析方法

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

遍历 DOM 节点

carbon (6).png

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

carbon (14).png

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

carbon (13).png

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

image.png

实验源码

<!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,望读者指点

参考资料

《JavaScript设计模式之观察者模式》

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享