【Vue2】2. 文本响应式更新(响应式原理)

概述

Vue是数据驱动视图模式,数据变动后,会自动更新视图。开发很便利,不要手动管理视图更新。哪 Vue 是如何实现自动更新,也就是响应式的?vue

以原生 JS 为例,数据变化了须要手动获取 Dom 节点,而后使用相关 Api 去修改视图git

而 Vue 数据变化后须要自动更新视图,因此流程应该:

从图能够大体感觉到,响应式更新有两个难点须要解决:

  1. 如何监听数据的变化?
  2. 如何触发引用有变化数据的视图镜像更新?

代码实现, 一共 373 行JS代码,包含Watcher/Observer的简单实现github

DEMO展现

入口代码:数据结构

new Vue({
  el: '#app',
  data() {
    return {
      message: 'hello'
    }
  },
  render(h) {
    return h('div', null, this.message)
  },
})
复制代码

渲染效果: 闭包

响应式更新:app

监听数据变化

通常而言,对于基本类型的值是否变化,能够对比先后值是否相同。对于引用类型是否有变化,则可能须要递归判断属性是否都一致。异步

在 Vue 中,组件数据 data 是 Object 类型的,这样作的好处,我理解一是方便扩展属性、二是为了能方便对数据进行拦截、代理等。函数

针对 Object 类型的的数据,其相关key/value能够经过 数据属性/访问器属性 获取。二者能够是能够互换的: 性能

数据属性访问简单直观,而访问器属性扩展性更好。在Vue种为了监听属性变化,使用访问器属性 覆写了 数据属性。

在Vue中定义的数据结构为 ui

通过Vue解析处理后 data 函数的值会存储到 _data 私有属性中:
Vue 也是借助 Object.defineProperty 实现的:

function proxy(target, sourceKey, key) {
  Object.defineProperty(target, key, {
    enumerable: true,
    configurable: true,
    get() {
      return target[sourceKey][key]
    },
    set(val) {
      target[sourceKey][key] = val
    }
  })
}
复制代码

借助 getter/setter 便可感知到数据变化,而自动触发视图更新作准备

数据关联视图(依赖收集)

Vue视图渲染的过程为: 模板 + 数据 -> 虚拟Dom -> Diff ... Dom操做 -> 真实Dom(渲染到页面中),这个过程能够很天然的使用数据。但数据不只包含视图展现数据、还会有一些视图不须要数据,若是每次检测到数据变化都从新执行一遍视图的渲染,这样可能形成极大的性能损耗。那如何才能识别哪些是视图所依赖的数据呢?

数据是在函数中引用的,故能够在访问数据时,记录当前执行的函数便可,整个思路以下:

简单实现步骤为:

  1. 设置一个全局变量记录当前执行的 render 函数
  2. render 函数会引用data,属性的 getter 访问器,在经过闭包保存当前的执行的 render 函数(此时记录在 全局变量中)
  3. 将全局变量置空
  4. 数据变化时,会触发属性的 setter

访问器,执行闭包的保存的render函数,触发视图从新渲染 在Vue中实现,为了更好的性能(异步渲染,可能多处修改属性值)、更好的扩展性(属性可能多处被引用,须要自动更新、以及支持watch、compute属性等),引入了 watcher/observer/dep 来实现依赖收集以及自动更新。

经过更多的分层,实现更好的扩展性。代码的思路:

  1. initData 属性时,对属性进行 observe 处理 <= observer概念
  2. mount 时,使用watcher管理更新
  3. 渲染时,设置一个全局变量,被引用属性的 getter 属性会经过该所有变量 收集对应的依赖
  4. 数据更新时,触发watcher 的 update 方法进行从新渲染操做

总结

经过 defineProperty 对数据属性进行拦截,再拦截的基础上 基于 JS 单线程原理进行依赖收集,数据变动时触发视图的从新渲染。

相关文章
相关标签/搜索