这是我参与更文挑战的第6天,活动详情查看: 更文挑战
一,前言
上篇,主要介绍了 Vue 数据初始化流程中,数组类型的数据劫持是如何实现的,核心思路如下:
出于对性能的考虑,Vue 没有对数组采用Object.defineProperty进行递归劫持,而是对能够导致原数组变化的 7 个方法进行了拦截和重写,实现了对数组的数据劫持;
至此,已经完成了对响应式数据(对象和数组)的劫持(深层劫持)操作;
本篇,继续介绍 Vue 数据初始化流程中,Vue 实例上数据代理的实现;
二,提出问题
1,Vue 是如何操作数据的
在 Vue 中,可以在外部直接通过vm实例进行数据访问和操作:
let vm = new Vue({
el: '#app',
data() {
return { message: 'Hello Vue', obj: { key: "val" }, arr:[1,2,3]}
}
});
console.log(vm.message)
console.log(vm.arr.push(4))
抛出问题:vm.message等价于$options.data.message,是如何实现的?
2,当前是如何操作数据的
当前代码中,外部通过vm实例只能拿到 vm.$options,想要拿到data需要 vm.$options.data
// src/state.js#initData
function initData(vm) {
let data = vm.$options.data;
data = isFunction(data) ? data.call(vm) : data;
observe(data);
data.message
data.arr.push(4);
}
结论:要想实现vm.message和$options.data.message等效,就需要想办法将vm实例操作“代理”到$options.data上;这样,就实现了 “Vue 的数据代理”;
二,数据代理的实现
1,数据代理的实现思路
为了让外部的vm实例能够拿到被观测后的data,可以将被处理后的data直接挂载到vm实例上:
// src/state.js#initData
function initData(vm) {
let data = vm.$options.data;
data = vm._data = isFunction(data) ? data.call(vm) : data;
observe(data);
}
-
首先,先做一次代理,将
data挂载到vm._data下,这样 vm 实例就能够在外部通过vm._data.message获取到data.message; -
之后,再做一次代理,将
vm实例操作vm.message代理到vm._data上,这样,外部就可以直接通过vm.message获取到data.message;
2,数据代理的实现
Vue 状态初始化阶段,通过observe()实现数据响应式之后,通过Object.defineProperty对_data中的数据操作进行劫持;将vm.xxx在vm实例上的取值操作,代理到vm._data.xxx上:
// src/state.js#initData
function initData(vm) {
let data = vm.$options.data;
data = vm._data = isFunction(data) ? data.call(vm) : data;
observe(data);
// 当 vm.message 在 vm 实例上取值时,将它代理到vm._data上去取
for(let key in data){
Proxy(vm, key, '_data')
}
}
Proxy 取值代理方法:
// src/state.js#Proxy
/**
* 代理方法
* 当取 vm.key 时,将它代理到 vm._data上去取
* @param {*} vm vm 实例
* @param {*} key 属性名
* @param {*} source 代理目标,这里是vm._data
*/
function Proxy(vm, key, source) {
Object.defineProperty(vm, key, {
get(){
return vm[source][key]
},
set(newValue){
vm[source][key] = newValue;
}
})
}
3,数据代理的测试
let vm = new Vue({
el: '#app',
data() {
return { message: 'Hello Vue', obj: { key: "val" }, arr:[1,2,3]}
}
});
console.log(vm)
console.log(vm.message)
观察打印结果:
获取 vm 实例时,会通过 get 方法将 _data 全部属性打印出来
当前 vm 实例上,包含 data 全部属性及对应的 get、set 方法
这样,就实现了数据代理;
当从vm实例取值时,就会被代理到vm._data取值;
三,结尾
本篇主要介绍了 Vue 数据初始化流程中,Vue 实例上数据代理的实现,核心思路如下:
- 将 data 暴露在 vm._data 实例属性上;
- 利用 Object.defineProperty 将 vm.xxx 操作代理到 vm._data 上;
下一篇,调试数据劫持和代理
维护日志
- 20230110:重新调整目录结构和排版,调整文字内容中的代码显示标签,添加数据代理过程详细描述;