Vue响应式原理模拟

整体结构
image-20210303083609219.png
  • Vue

    • 记录传入的选项,设置 el
    • 把 data 的成员注入到 Vue 实例
    • 负责调用 Observer 实现数据响应式处理(数据劫持)
    • 负责调用 Compiler 编译指令/插值表达式等
  • Observer

    • 数据劫持

      负责把 data 中的成员转换成 getter/setter

      负责把多层属性转换成 getter/setter

      如果给属性赋值为新对象,把新对象的成员设置为 getter/setter

    • 添加 Dep 和 Watcher 的依赖关系

    • 数据变化发送通知

  • Compiler

    • 负责编译模板,解析指令/插值表达式
    • 负责页面的首次渲染过程
    • 当数据变化后重新渲染
  • Dep

    • 收集依赖,添加订阅者(watcher)
    • 通知所有订阅者
  • Watcher

    • 自身实例化的时候往dep对象中添加自己
    • 当数据变化dep通知所有的 Watcher 实例更新视图
Vue
  • 功能

    1. 负责接收初始化的参数
    2. 负责把data中的属性注入到Vue实例,转换为getter/setter
    3. data中的成员记录在data中的setter是真正接收数据变化的地方;接着把构造函数的参数记录在data指向同一个对象,__开头的都是私有属性或方法 el对应选项中的el
    4. 负责调用observer监听data中所有属性变化
    5. 负责调用compiler解析指令/插值表达式
  • 结构

image-20210301085130273.png
  • 代码

    class Vue {
      constructor(options) {
        // 1.保存所有选项
        this.$options = options
        this.$data = options.data
        this.$methods = options.methods
        this.$el =
          typeof options.el === 'string'
            ? document.querySelector(options.el)
            : options.el
    
        // 2.将data中的数据转换为getter/setter,存入到$data中
        this._proxyData(this.$data)
        // 3.将methods注入到vue实例
        this._proxyMethods(this.$methods)
        // 4.负责调用Observer实现数据劫持
        new Observer(this.$data)
        // 5.负责调用Compiler解析指令/插值表达式等
        new Compiler(this)
      }
      // 设置响应式
      _proxyData(data) {
        Object.keys(data).forEach((key) => {
          Object.defineProperty(this, key, {
            enumrable: true,
            configurable: true,
            get() {
              return data[key]
            },
            set(newValue) {
              if (newValue === data[key]) return
              data[key] = newValue
            },
          })
        })
      }
      _proxyMethods(methods) {
        Object.keys(methods).forEach((key) => {
          this[key] = this.$methods[key]
        })
      }
    }
    
    // 测试
    let vm = new Vue({
        el: '#app',
        data: {
            msg: 'Hello Vue',
            count: 100,
        },
    })
    

    测试,并在控制台打印vm实例

image-20210301090341826.png
Observer
  • 功能

    1. 负责把data选项中的属性转换为响应式对象
    2. data中的某个属性也是对象,把该属性转换为响应式对象
    3. 数据变化发送通知
  • 结构

image-20210302081600702.png
  • 代码

    class Observer {
      constructor(data) {
        this.walk(data)
      }
    
      // 如果不是对象则返回;如果是对象,遍历对象所有属性,设置getter/setter
      walk(data) {
        if (!data || typeof data !== 'object') {
          return
        }
        Object.keys(data).forEach((key) => {
          this.defineReactive(data, key, data[key])
        })
      }
    
      // 定义响应式成员
      defineReactive(data, key, val) {
        const self = this
        // 如果val是对象,继续设置其下面的响应式
        this.walk(val)
        // 添加观察者 get负责添加依赖,set触发更新
        let dep = new Dep()
        Object.defineProperty(data, key, {
          enumerable: true,
          configurable: true,
          get() {
            // Dep.target存储的是watcher对象
            Dep.target && dep.addSub(Dep.target)
            return val
          },
          set(newValue) {
            if (newValue !== val) {
              // 如果newValue是对象,设置newValkue的响应式成员
              self.walk(newValue)
              val = newValue
              // 发送通知
              dep.notify()
            }
          },
        })
      }
    }
    
    // 测试
    let vm = new Vue({
        el: '#app',
        data: {
            msg: 'Hello Vue',
            count: 100,
        },
    })
    vm.msg = {
        a: 123
    }
    
image-20210302082359401.png
Compiler
  • 功能

    1. 负责编译模板,解析指令/插值表达式
    2. 负责页面首次渲染
    3. 当数据变化后重新渲染视图
  • 结构

    image-20210302082740514.png
  • 代码

    class Compiler {
      constructor(vm) {
        this.el = vm.$el
        this.vm = vm
        this.compile(this.el)
      }
    
      // 编译模板,处理文本节点和元素节点
      compile(el) {
        let childNodes = el.childNodes
        Array.from(childNodes).forEach((node) => {
          // 处理文本节点
          if (this.isTextNode(node)) {
            this.compileText(node)
          } else if (this.isElementNode(node)) {
            // 处理元素节点
            this.compileElement(node)
          }
          // node有子节点时,递归调用
          if (node.childNodes && node.childNodes.length) {
            this.compile(node)
          }
        })
      }
    
      // 编译元素节点,处理指令
      compileElement(node) {
        // 获取所有属性节点
        // console.log(node.attributes)
        // 遍历所有属性节点
        Array.from(node.attributes).forEach((attr) => {
          // 判断是否是指令
          let attrName = attr.name
          if (this.isDirective(attrName)) {
            // v-text --> text
            attrName = attrName.substr(2)
            let key = attr.value
            this.update(node, key, attrName)
          }
        })
      }
      update(node, key, attrName) {
        // 删除on:前缀
        if (attrName.startsWith('on:')) {
          attrName = attrName.replace('on:', '')
        }
        let updateFn = this[`${attrName}Updater`]
        // 因为在 textUpdater等中要使用 this
        updateFn && updateFn.call(this, node, this.vm[key], key)
      }
      // 处理v-text指令
      textUpdater(node, value, key) {
        node.textContent = value
        new Watcher(this.vm, key, (newValue) => {
          node.textContent = newValue
        })
      }
      // 处理v-model
      modelUpdater(node, value, key) {
        node.value = value
          // 每一个指令中创建一个 watcher,观察数据的变化
        new Watcher(this.vm, key, (newValue) => {
          node.value = newValue
        })
    
        window.addEventListener('input', () => {
          this.vm[key] = node.value
        })
      }
      // 只需将textContent替换为innerHTML
      htmlUpdater(node, value, key) {
        node.innerHTML = value
        new Watcher(this.vm, key, (newValue) => {
          node.innerHTML = newValue
        })
      }
      clickUpdater(node, value, key) {
        node.onclick = value
      }
      // 编译文本节点,处理插值表达式
      compileText(node) {
        // console.dir(node)
        // assignedSlot: null
        // baseURI: "file:///F:/%E5%A4%A7%E5%89%8D%E7%AB%AF/%E7%AC%AC%E4%B8%89%E9%83%A8%E5%88%86/03-01-study-     materials/03-01-codes/03-01-03-06-vue-reactivity/vue/index.html"
        // childNodes: NodeList []
        // data: "{{ msg }}"
        // firstChild: null
        // isConnected: true
        // lastChild: null
        // length: 9
        // nextElementSibling: null
        // nextSibling: null
        // nodeName: "#text"
        // nodeType: 3
        // nodeValue: "{{ msg }}"
        // ownerDocument: document
        // parentElement: h3
        // parentNode: h3
        // previousElementSibling: null
        // previousSibling: null
        // textContent: "{{ msg }}"
        // wholeText: "{{ msg }}"
        // 正则获取{{ msg }}
        let reg = /\{\{(.+?)\}\}/
        // 获取文本节点内容
        let value = node.textContent
        if (reg.test(value)) {
          // 获取第一个分组内容
          let key = RegExp.$1.trim()
          node.textContent = value.replace(reg, this.vm[key])
    
          // 数据改变,更新视图
          new Watcher(this.vm, key, (newValue) => {
            node.textContent = newValue
          })
        }
      }
      // 是否是指令
      isDirective(attrName) {
        return attrName.startsWith('v-')
      }
      // 文本节点
      isTextNode(node) {
        // nodeType节点类型
        return node.nodeType === 3
      }
      // 元素节点
      isElementNode(node) {
        return node.nodeType === 1
      }
    }
    
    
    Dep(Dependency)
image-20210303082520641.png
  • 功能

    收集依赖,添加观察者(watcher)

    通知所有观察者

  • 结构


    image-20210303082608791.png
  • 代码

    class Dep {
      constructor() {
        // 存储所有观察者
        this.subs = []
      }
    
      // 添加观察者
      addSub(sub) {
        if (sub && sub.update) {
          this.subs.push(sub)
        }
      }
      // 发送通知
      notify() {
        this.subs.forEach((sub) => {
          sub.update()
        })
      }
    }
    
    // 在 compiler.js 中收集依赖,发送通知
    // defineReactive 中
    // 创建 dep 对象收集依赖
    const dep = new Dep()
    // getter 中
    // get 的过程中收集依赖
    Dep.target && dep.addSub(Dep.target)
    // setter 中
    // 当数据变化之后,发送通知
    dep.notify()
    
Watcher
image-20210303082708782.png
  • 功能

    当数据变化触发依赖,dep通知所有的Watcher实例更新视图

    自身实例化的时候往dep对象上添加自己

  • 结构

image-20210303082801222.png
  • 代码

    class Watcher {
      constructor(vm, key, cb) {
        this.vm = vm
        // 保存data中的属性名
        this.key = key
        // 回调函数
        this.cb = cb
    
        // 在 Dep 的静态属性上记录当前 watcher 对象,当访问数据的时候把 watcher 添加到dep 的 subs 中
        Dep.target = this
        // 触发一次 getter,让 dep 为当前 key 记录 watcher
        this.oldValue = vm[key]
        // 清空 target
        Dep.target = null
      }
      // 当数据变化时更新视图
      update() {
        let newValue = this.vm[this.key]
        if (newValue === this.oldValue) {
          return
        }
        this.cb(newValue)
      }
    }
    // 在 compiler.js 中为每一个指令/插值表达式创建 watcher 对象,监视数据的变化
    // 因为在 textUpdater等中要使用 this
    updaterFn && updaterFn.call(this, node, this.vm[key], key)
    // v-text 指令的更新方法
    textUpdater (node, value, key) {
        node.textContent = value
        // 每一个指令中创建一个 watcher,观察数据的变化
        new Watcher(this.vm, key, value => {
            node.textContent = value
        })
    }
    
总结
  • 问题

    1. 给属性重新赋值成对象,是否是响应式?

      是,属性本身是响应式,重新赋值时会触发observer中定义的响应式数据的set方法,调用walk方法遍历对象设置为响应式数据

    2. 给Vue实例新增一个成员是否是响应式的?

      不是,因为data中的响应式对象是在创建vue实例的时候定义的,所以直接新增成员不是响应式的

你可能感兴趣的:(Vue响应式原理模拟)