Observe的实现原理在很多地方都有分析,主要是利用了Object.defineProperty()来建立对数据更改的订阅,在很多地方也称之为数据劫持。下面我们来学习从零开始建立这样一个数据的订阅发布体系。
从简单处开始,我们希望有个函数可以帮我们监听数据的改变,每当数据改变时执行特定回调函数
function observe(data, callback) { if (!data || typeof data !== 'object') { return } // 遍历key Object.keys(data).forEach((key) => { let value = data[key]; // 递归遍历监听深度变化 observe(value, callback); // 监听单个可以的变化 Object.defineProperty(data, key, { configurable: true, enumerable: true, get() { return value; }, set(val) { if (val === value) { return } value = val; // 监听新的数据 observe(value, callback); // 数据改变的回调 callback(); } }); }); } // 使用observe函数监听data const data = {}; observe(data, () => { console.log('data修改'); })
上面我们实现了一个简单的observe函数,只要我们将编译函数作为callback传入,那么每次数据更改时都会触发回调函数。但是我们现在不能为单独的key设置监听及回调函数,只能监听整个对象的变化执行回调。下面我们对函数进行改进,达到为某个key设置监听及回调。同时建立调度中心,让整个订阅发布模式更加清晰。
// 首先是订阅中心 class Dep { constructor() { this.subs = []; // 订阅者数组 } addSub(sub) { // 添加订阅者 this.subs.push(sub); } notify() { // 发布通知 this.subs.forEach((sub) => { sub.update(); }); } } // 当前订阅者,在getter中标记 Dep.target = null; // 订阅者 class Watch { constructor(express, cb) { this.cb = cb; if (typeof express === 'function') { this.expressFn = express; } else { this.expressFn = () => { return new Function(express)(); } } this.get(); } get() { // 利用Dep.target存当前订阅者 Dep.target = this; // 执行表达式 -> 触发getter -> 在getter中添加订阅者 this.expressFn(); // 及时置空 Dep.taget = null; } update() { // 更新 this.cb(); } addDep(dep) { // 添加订阅 dep.addSub(this); } } // 观察者 建立观察 class Observe { constructor(data) { if (!data || typeof data !== 'object') { return } // 遍历key Object.keys(data).forEach((key) => { // key => dep 对应 const dep = new Dep(); let value = data[key]; // 递归遍历监听深度变化 const observe = new Observe(value); // 监听单个可以的变化 Object.defineProperty(data, key, { configurable: true, enumerable: true, get() { if (Dep.target) { const watch = Dep.target; watch.addDep(dep); } return value; }, set(val) { if (val === value) { return } value = val; // 监听新的数据 new Observe(value); // 数据改变的回调 dep.notify(); } }); }); } } // 监听数据中某个key的更改 const data = { name: 'xiaoming', age: 26 }; const observe = new Observe(data); const watch = new Watch('data.age', () => { console.log('age update'); }); data.age = 22
现在我们实现了订阅中心,订阅者,观察者。观察者监测数据的更新,订阅者通过订阅中心订阅数据的更新,当数据更新时,观察者会告诉订阅中心,订阅中心再逐个通知所有的订阅者执行更新函数。到现在为止,我们可以大概猜出vue的实现原理:
建立观察者观察data数据的更改 (new Observe)
在编译的时候,当某个代码片段或节点依赖data数据,为该节点建议订阅者,订阅data中某些数据的更新(new Watch)
当dada数据更新时,通过订阅中心通知数据更新,执行节点更新函数,新建或更新节点(dep.notify())
上面是我们对vue实现原理订阅发布模式的基本实现,及编译到更新过程的猜想,现在我们接着分析vue源码的实现:
在实例的初始化中
// ... // 为数据建立数据观察 this._ob = observe(options.data) this._watchers = [] // 添加订阅者 执行render 会触发 getter 订阅者订阅更新,数据改变触发 setter 订阅中心通知订阅者执行 update this._watcher = new Watcher(this, render, this._update) // ...
vue中数据观察的实现
// observe函数 export function observe (value, vm) { if (!value || typeof value !== 'object') { return } if ( hasOwn(value, '__ob__') && value.__ob__ instanceof Observer ) { ob = value.__ob__ } else if ( shouldConvert && (isArray(value) || isPlainObject(value)) && Object.isExtensible(value) && !value._isVue ) { // 为数据建立观察者 ob = new Observer(value) } // 存储关联的vm if (ob && vm) { ob.addVm(vm) } return ob } // => Observe 函数 export function Observer (value) { this.value = value // 在数组变异方法中有用 this.dep = new Dep() // observer实例存在__ob__中 def(value, '__ob__', this) if (isArray(value)) { var augment = hasProto ? protoAugment : copyAugment // 数组遍历,添加变异的数组方法 augment(value, arrayMethods, arrayKeys) // 对数组的每个选项调用observe函数 this.observeArray(value) } else { // walk -> convert -> defineReactive -> setter/getter this.walk(value) } } // => walk Observer.prototype.walk = function (obj) { var keys = Object.keys(obj) for (var i = 0, l = keys.length; i < l; i++) { this.convert(keys[i], obj[keys[i]]) } } // => convert Observer.prototype.convert = function (key, val) { defineReactive(this.value, key, val) } // 重点看看defineReactive export function defineReactive (obj, key, val) { // key对应的的订阅中心 var dep = new Dep() var property = Object.getOwnPropertyDescriptor(obj, key) if (property && property.configurable === false) { return } // 兼容原有setter/getter // cater for pre-defined getter/setters var getter = property && property.get var setter = property && property.set // 实现递归监听属性 val = obj[key] // 深度优先遍历 先为子属性设置 reactive var childOb = observe(val) // 设置 getter/setter Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { var value = getter ? getter.call(obj) : val // Dep.target 为当前 watch 实例 if (Dep.target) { // dep 为 obj[key] 对应的调度中心 dep.depend 将当前 wtcher 实例添加到调度中心 dep.depend() if (childOb) { // childOb.dep 为 obj[key] 值 val 对应的 observer 实例的 dep // 实现array的变异方法和$set方法订阅 childOb.dep.depend() } // TODO: 此处作用未知? if (isArray(value)) { for (var e, i = 0, l = value.length; i < l; i++) { e = value[i] e && e.__ob__ && e.__ob__.dep.depend() } } } return value }, set: function reactiveSetter (newVal) { var value = getter ? getter.call(obj) : val // 通过 getter 获取 val 判断是否改变 if (newVal === value) { return } if (setter) { setter.call(obj, newVal) } else { val = newVal } // 为新值设置 reactive childOb = observe(newVal) // 通知key对应的订阅中心更新 dep.notify() } }) }
订阅中心的实现