vue实例里的data数据代理是怎么实现的?

2023/6/28 Proxy

# 问题

let vm = new Vue({
  el: "#app",
  data() {
    return { name: "smeb", obj: { key: "val" }, arr: [1, 2, 3] };
  },
});

vm.name等价于$options.data.name,是如何实现的?

# 数据代理的实现

# 思路

为了让外部的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);
}
  • 首先,先做一次代理 —— vm._data = isFunction(data) ? data.call(vm): data ,将data挂载到vm._data下,这样 vm 实例就能够在外部通过vm._data.message获取到data.message —— 用_datadata的数据存起来

  • 然后,再做一次代理 —— data = vm._data,将vm实例操作vm.message代理到vm._data上,这样,外部就可以直接通过vm.message获取到data.message

# 实现

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的实现就是通过Object.defineProperty来实现数据代理和数据劫持的 —— 配置getset

# ==自己写一个数据代理==

let _data = { name: "smeb", age: 25 };

let vm = {};

console.log("初始化的Data: ", _data);
console.log("初始化的Vm: ", vm);
function xxx(vm, key, source) {
  Object.defineProperty(vm, key, {
    enumerable: true,
    get() {
      return source[key];
    },
    set(val) {
      source[key] = val;
    },
  });
}

for (let i in _data) {
  xxx(vm, i, _data);
}

console.log("数据代理之后的vm: ", vm);
console.log("数据代理之后的_data: ", _data);

// console.log(": ", vm);
console.log("修改前的_data:  ", _data);
vm.name = "the shy";
console.log("修改后的_data: ", _data);

输出结果如下:

1

可以看到通过代理之后的vm上存在了其属性的getset方法,实现了数据代理和数据劫持,我们修改vm的数据,_data的数据也被成功修改了~

How to love
Lil Wayne