【Vue2.x 源码学习】第六篇 - 数据代理的实现

337 阅读2分钟

这是我参与更文挑战的第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.xxxvm实例上的取值操作,代理到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)

image.png

观察打印结果:

获取 vm 实例时,会通过 get 方法将 _data 全部属性打印出来

当前 vm 实例上,包含 data 全部属性及对应的 getset 方法

这样,就实现了数据代理;

当从vm实例取值时,就会被代理到vm._data取值;


三,结尾

本篇主要介绍了 Vue 数据初始化流程中,Vue 实例上数据代理的实现,核心思路如下:

  • 将 data 暴露在 vm._data 实例属性上;
  • 利用 Object.defineProperty 将 vm.xxx 操作代理到 vm._data 上;

下一篇,调试数据劫持和代理


维护日志

  • 20230110:重新调整目录结构和排版,调整文字内容中的代码显示标签,添加数据代理过程详细描述;