Vue源码解析之数据响应系统的使用(3)

当第一个函数执行时,就会触发this.a、this.b的get拦截器,从而收集依赖。

我们的watch函数第一个参数是函数时watch函数要做些什么改变呢?要想能够收集依赖,就得读取属性触发get函数。当第一个参数是函数时怎么读取属性呢?函数内是有读取属性的,所以只要执行一下函数就行了。

function watch (exp, fn) {
 // 将fn赋值给Target
 Target = fn;
 
 // 如果 exp 是函数,直接执行该函数
 if (typeof exp === 'function') {
  exp()
  return
 }

 let obj = data;
 if (/\./.test(exp)) {
  const path = exp.split('.');
  path.forEach(p => obj = obj[p])

  return;
 }


 data[exp];
}

对象的处理暂且就到这里,具体的我们在源码中去看。

2. 数组

数组有几个变异方法会改变数组本身:push pop shift unshift splice sort reverse, 那怎么才能知道何时调用了这些变异方法呢?我们可以在保证原来方法功能不变的前提下对方法进行扩展。可是如何扩展呢?

数组实例的方法都来自于数组构造函数的原型, 数组实例的__proto__属性指向数组构造函数的原型,即:arr.__proto__ === Array.prototype, 我们可以定义一个对象,它的原型指向Array.prototype,然后在这个对象中重新定义与变异方法重名的函数,然后让实例的__proto__指向该对象,这样调用变异方法的时候,就会先调用重定义的方法。
const mutationMethods = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'];

// 创建以Array.prototype为原型的对象
const arrayMethods = Object.create(Array.prototype);
// 缓存Array.prototype
const originMethods = Array.prototype;

mutationMethods.forEach(method => {
 arrayMethods[method] = function (...args) {
  // 调用原来的方法获取结果
  const result = originMethods[method].apply(this, args);
  console.log(`重定义了${method}方法`)
  return result;
 }
})

我们来测试一下:

const arr = [];
arr.__proto__ = arrayMethods;
arr.push(1);

可以看到在控制台打印出了重定义了push方法这句话。

先大概有个印象,接下来我们来看源码吧。

实例对象代理访问data

在initState方法中,有这样一段代码:

const opts = vm.$options
...
if (opts.data) {
 initData(vm)
} else {
 observe(vm._data = {}, true /* asRootData */)
}

opts就是vm.$options,如果opts.data存在,就执行initData方法,否则执行observe方法,并给vm._data赋值空对象。我们就从initData方法开始,开启探索数据响应系统之路。

initData方法定义在core/instance/state.js文件中: