FE

Vue 响应式系统的原理实现

Vue 如何追踪DOM变化

Posted by nolan on February 7, 2020

Vue 是一种非侵入式的响应式框架。数据模型仅是普通 js 对象,当你修改他们,视图会自动更新。

如何追踪 Dom 变化


  • 当把普通的 js 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象的所有属性,并使用 Object.defineProperty() 把这些属性全部转换为 getter 和 setter;
  • 这些 getter 和 setter 用户无感知,Vue 利用他们跟踪依赖,在属性被访问和修改时通知变更。
  • 每一个 Vue 都对应一个 watcher 实例,会在第一次组件渲染的过程中把“数据属性”(也就是 data 对象)记录为依赖,当 setter 触发时,通知 watcher 触发其关联的组件重新渲染; image

声明响应式 data 属性


  • Vue 不允许动态添加根级响应式属性,所以你必须在初始化实例前声明所有根级响应式属性,哪怕只是一个空值

    var vm = new Vue({
      data: {
        // 声明 message 为一个空值字符串
        message: ''
      },
      template: '<div></div>'
    })
    // 之后设置 `message`
    vm.message = 'Hello!'
    
    
  • data 对象就像组件状态的结构 (schema)。提前声明所有的响应式属性,可以让组件代码在未来修改或给其他开发人员阅读时更易于理解

异步更新队列


  • Vue 更新 Dom 是异步的;
  • 侦听到数据变化,Vue 开启一个队列,缓冲在同一个事件循环中发生变更的所有数据;
  • 若一个 watcher 被多次触发,只会被推入队列一次(这样可以去除重复数据对于避免不必要的计算和 DOM 操作)
  • Vue 在内部对异步队列尝试使用原生的 Promise.then、MutationObserver 和 setImmediate,如果执行环境不支持,则会采用 setTimeout(fn, 0) 代替
  • 如何基于更新后的 Dom 做些操作?

        Vue.component('example', {
          template: '<span></span>',
          data: function () {
            return {
              message: '未更新'
            }
          },
          methods: {
            updateMessage: function () {
              this.message = '已更新'
              console.log(this.$el.textContent) // => '未更新'
              this.$nextTick(function () {
                console.log(this.$el.textContent) // => '已更新'
              })
            }
          }
        })
    
    
    • $nextTick() 返回一个 Promise 对象,所以你可以使用新的 ES2017 async/await 语法完成相同的事情:

      methods: {
        updateMessage: async function () {
          this.message = '已更新'
          console.log(this.$el.textContent) // => '未更新'
          await this.$nextTick()
          console.log(this.$el.textContent) // => '已更新'
        }
      }
      

参考 Vue 文档