上一篇 Vue 原理解析(五): 虚拟Dom到真实Dom的生成过程
vue 之所以能数据驱动视图发生变更的关键就是:依赖它的响应式系统了。 响应式系统如果根据数据类型区分: 对象和数组两者的实现会有所不同。 解释响应式原理,需要从整体流程出发, 不在vue 组件化的整体流程中找到响应式原理的位置,对深刻理解响应式原理不太好。 接下来我们从整体流程出发, 试着站在巨人的肩膀上分别说明对象和数组的实现原理。
对象的响应式原理
对象响应式数据的创建
- 在数组的初始化阶段, 将对传入的状态进行初始化, 以下以data为例, 会将传入数据包装为响应式的数据。
对象示例:
main.js
new Vue({ // 根组件
render: h => h(App)
})
---------------------------------------------------
app.vue
<template>
<div>{
{info.name}}</div> // 只用了info.name属性
</template>
export default { // app组件
data() {
return {
info: {
name: 'cc',
sex: 'man' // **即使是响应式数据,没被使用就不会进行依赖收集**
}
}
}
}
接下来的分析将以上面代码为例, 这种结构其实是一个嵌套组件,只不过根组件一般定义的参数比较少而已,理解这个很重要的。
在组件=new Vue() 后执行vm._init() 初始化过程中, 当执行到initState(vm)时就会对内部使用到的一些状态, 如: props, data, computed, watch, methods 分别进行初始化, 再对data 进行初始化的最后有这么一句:
function initData(vm) { //初始化data
...
observe(data) // info:{name:'cc',sex:'man'}
}
这个observer 就是将用户定义的data变成响应式的数据, 接下来看看它的创建过程:
export function observe(value) {
if(!isObject(value)) { // 不是数组或对象,再见
return
}
return new Observer(value)
}
简单理解这个observer 方法就是Observer 这个类的工厂方法, 所以还是要看下Observer 这个类的定义:
export class Observer {
constructor(value) {
this.value = value
this.walk(value) // 遍历value
}
walk(obj) {
const keys = Object.keys(obj)
for(let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i]) // 只传入了两个参数
}
}
}
当执行new Observer 时, 首先将传入的对象挂载到当前this 下, 然后遍历当前对象的每一项, 执行defineReactive 这个方法, 看看它的定义:
export function defineReactive(obj, key, val) {
const dep = new Dep() // 依赖管理器
val = obj[key] // 计算出对应key的值
observe(val) // 递归包装对象的嵌套属性
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
... 收集依赖
},
set(newVal) {
... 派发更新
}
})
}
这个方法的作用就是使用Object.defineProperty创建响应式数据。 首先根据传入的obj 和 key 计算出 val 具体的值; 如果val 还是对象, 那就使用observe 方法进行递归创建, 在递归的过程中使用Object.defindeProperty 将对象的每一个属性都变成响应式数据:
...
data() {
return {
info: {
name: 'cc',
sex: 'man'
}
}
}
这段代码就会有三个响应式数据:
info, info.name, info.sex