深入分析Vue响应式原理
/ / 点击 / 阅读耗时 7 分钟Vue中的响应式
Vue 初始化的过程,就是把原始的数据最终映射到 DOM
中,而 Vue
的数据驱动除了数据渲染 DOM
之外,还有一个很重要的体现就是数据的变更会触发 DOM
的变化。
其实前端开发最重要的两个工作,一个是把数据渲染到页面,另一个是处理用户交互。
考虑如下示例:
当我们去修改 this.message
的时候,模板对应的插值也会渲染成新的数据,那么这一切是怎么做到的呢?
在分析前,我们先直观的想一下,如果不用 Vue
的话,我们会通过最简单的方法实现这个需求:监听点击事件,修改数据,手动操作 DOM
重新渲染。这个过程和使用 Vue
的最大区别就是多了一步“手动操作 DOM
重新渲染”。这一步看上去并不多,但它背后又潜在的几个要处理的问题:
我需要修改哪块的 DOM?
我的修改效率和性能是不是最优的?
我需要对数据每一次的修改都去操作 DOM 吗?
我需要 case by case 去写修改 DOM 的逻辑吗?
如果我们使用了 Vue
,那么上面几个问题 Vue
内部就帮你做了,那么 Vue
是如何在我们对数据修改后自动做这些事情呢,接下来我们将进入一些 Vue
响应式系统的底层的细节。
响应式对象
很多小伙伴之前都了解过 Vue.js
实现响应式的核心是利用了 ES5
的 Object.defineProperty
,这也是为什么 Vue.js
不能兼容 IE8
及以下浏览器的原因,我们先来对它有个直观的认识。
Object.defineProperty
Object.defineProperty
方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象,先来看一下它的语法:
1 | Object.defineProperty(obj, prop, descriptor) |
obj
是要在其上定义属性的对象;prop
是要定义或修改的属性的名称;descriptor
是将被定义或修改的属性描述符。
比较核心的是 descriptor
,它有很多可选键值,具体的可以去参阅它的文档。这里我们最关心的是 get
和 set
,get
是一个给属性提供的 getter
方法,当我们访问了该属性的时候会触发 getter
方法;set
是一个给属性提供的 setter
方法,当我们对该属性做修改的时候会触发 setter
方法。
一旦对象拥有了 getter
和 setter
,我们可以简单地把这个对象称为响应式对象。
defineReactive
defineReactive
的功能就是定义一个响应式对象,给对象动态添加 getter
和 setter
:
defineReactive
函数最开始初始化 Dep
对象的实例,接着拿到 obj
的属性描述符,然后对子对象递归调用 observe
方法,这样就保证了无论 obj
的结构多复杂,它的所有子属性也能变成响应式的对象,这样我们访问或修改 obj
中一个嵌套较深的属性,也能触发 getter
和 setter
。最后利用 Object.defineProperty
去给 obj
的属性 key
添加 getter
和 setter
。
Vue
中的响应式,核心就是利用 Object.defineProperty
给数据添加了 getter
和 setter
,目的就是为了在我们访问数据以及写数据的时候能自动执行一些逻辑:getter
做的事情是依赖收集,setter
做的事情是派发更新,
依赖收集
响应式对象 getter
相关的逻辑就是做依赖收集。
收集依赖的目的是为了当这些响应式数据发生变化时,触发它们的 setter
的时候,能知道应该通知哪些订阅者去做相应的逻辑处理,我们把这个过程叫派发更新,其实 Watcher
和 Dep
就是一个非常经典的观察者设计模式的实现
派发更新
Vue
数据修改派发更新的过程就是当数据发生变化的时候,触发 setter
逻辑,把在依赖过程中订阅的的所有观察者,也就是 watcher
,都触发它们的 update
过程,这个过程又利用了队列做了进一步优化,在 nextTick
后执行所有 watcher
的 run
,最后执行它们的回调函数。