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文件中: