这是我参与更文挑战的第4天,活动详情查看: 更文挑战
一,前言
上篇,主要介绍了在 Vue 的数据初始化流程中,对象属性单层劫持的实现;
回顾一下,主要涉及以下几个核心点:
- data 为函数和对象的处理,及当 data 为函数时的 this 指向问题
- Observer 类:对数据进行观测
- walk 方法:遍历 data 属性(对象属性遍历)
- defineReactive 方法:利用 Object.defineProperty 实现数据劫持(单层劫持)
本篇,继续对 data 数据进行初始化操作,实现对象属性的深层劫持;
二,对象属性的深层观测问题
1,抛出问题
当前版本存在的问题:
在 data 对象中,如果存在属性值依然为对象类型的情况
这个对象是不会被观测到的(目前所说的观测,还仅仅停留在数据劫持的阶段)
当前仅实现了对 data 对象中的属性进行单层劫持,当出现嵌套对象是,则不会被深层劫持;
2,测试问题
<script>
let vm = new Vue({
el: '#app',
data() {
// 在data函数返回的对象中,存在obj属性的值仍为对象类型,此时深层的对象不会被观测
return { message: 'Hello Vue', obj: { key: "val" } }
}
});
</script>
3,验证结果
用户执行new Vue传入的 options 选项中,
在 data 函数中,存在属性 obj 的值依然是一个对象 { key: "val" } ,
控制台输出的对象 { key: "val" } 中,key 并没有被添加 get、set 方法,说明此时并没有实现数据的深层劫持;
三,对象属性深层观测的实现
0,回顾:对象属性单层观测
new Vue时传入options,此处options.data可能是对象也可能是函数;- Vue 初始化阶段,执行原型方法 _init:
- 通过
vm.$options暴露options - 调用
initState方法完成状态的初始化; - 如果存在
options.el,需要将 data 数据渲染到视图上;
- 通过
- 状态初始化阶段,执行 initState 方法:
- 在 initState 方法中,状态的来源有多种:data、props、watch、computed,目前仅处理 data;
- 通过
vm.$options.data获取到 data,如果 data 为函数,执行函数设置上下文为 vm 实例,获取内部返回的对象;通过这异步处理之后,data 必为函数; - 通过 observe 方法,实现数据观测;
- 数据响应式阶段,执行 observe 方法:
- 创建Observer实例,遍历 data 对象中的属性,使用
Object.defineProperty重写对象中的所有属性,实现数据劫持(单层劫持);
- 创建Observer实例,遍历 data 对象中的属性,使用
1,实现思路:对象属性深层观测
有了之前对象属性单层观测的基础,对象属性的深层观测就简单了:
以{ message: 'Hello Vue', obj: { key: "val" } }为例:
当对象中的属性 obj 即将通过 Object.defineProperty 被劫持之前,先执行一次“对象属性的单层劫持”;
2,代码实现
// src/obseve/index.js
function defineReactive(obj, key, value) {
// 对 key 进行观测前,调用 observe方法,如果属性值为对象则会继续向下找,实现深层递归观测
observe(value);
Object.defineProperty(obj, key, {
get() {
return value;
},
set(newValue) {
if (newValue === value) return
value = newValue;
}
})
}
在 data.obj 属性的值 { key: "val" }对象中,如果依然存在属性的值为对象或更深层的对象嵌套时(当属性值为对象:Object类型且非null),则继续执行 observe 进行观测,将会形成 observe “对象的单层劫持”的递归,从而实现对象属性的深层劫持;
3,验证结果
<script>
let vm = new Vue({
el: '#app',
data() {
return { message: 'Hello Vue', obj: { key: "val" } }
}
});
</script>
data 对象中属性 obj 值为对象类型,实现了深层观测,如果存在更深层次的对象嵌套,也会递归实现数据观测;
<script>
let vm = new Vue({
el: '#app',
data() {
return { message: 'Hello Vue', obj_lv1: { obj_lv2: { obj_lv3: "val" } } }
}
});
</script>
四,data 性能与优化
由于Object.defineProperty的性能比较低,这也成为了 vue2 的性能瓶颈;
Vue 加载时需要对数据进行递归观测,数据越多、层级越深,性能开销就会越大;
优化注意:
- 1,非响应式数据尽量不要放在 data 中,以减少
Object.defineProperty性能开销; - 2,数据层次尽量扁平化,不要嵌套过深,深层递归将导致性能降低;
五,结尾
本篇主要实现了 Vue 数据初始化流程中,对象属性的深层劫持,核心思路就是递归;
- 通过
data = isFunction(data) ? data.call(vm) : data;处理后的 data 一定是对象类型; - 通过
data = observe(data)处理后的 data 就实现了数据的响应式(目前只有劫持) - observe 方法最终会返回一个 Observer 类
- Observer 类初始化时,通过 walk 遍历属性
- 对每一个属性进行 defineReactive(Object.defineProperty)实现对象属性的单层数据劫持
- 在 defineReactive 中调用 observe,如果当前属性的值为对象类型,继续对当前对象属性进行观测(即递归执行步骤 3~5),实现对象属性的深层数据劫持
下一篇,数组的劫持
维护日志
- 20230108:调整内容排版,优化部分文字描述和代码注释,添加性能问题说明与性能优化,使内容更加清晰易懂;
- 20230109:添加 data 对象深层观测的验证,补充描述和图片;