参考 vue 2.2.6版本
/* @flow */ //引入订阅者模式 import Dep from './dep'
import { arrayMethods } from './array' import { def, isObject, isPlainObject, hasProto, hasOwn, warn, isServerRendering } from '../util/index' const arrayKeys = Object.getOwnPropertyNames(arrayMethods) /** * By default, when a reactive property is set, the new value is * also converted to become reactive. However when passing down props, * we don't want to force conversion because the value may be a nested value * under a frozen data structure. Converting it would defeat the optimization.
默认新的值也会被definePropty, 但是有些情况是不希望被转换的, 比如 frozen的属性, 实际不需要监听, 如果强制转换, 会破坏做的性能优化, 所以这里监听做成可配置 */ export const observerState = { shouldConvert: true, isSettingProps: false } /** * Observer class that are attached to each observed * object. Once attached, the observer converts target * object's property keys into getter/setters that * collect dependencies and dispatches updates.
每个监听对象在Observer里面被监听, 对象属性进入 getter setter方法后, 会收集依赖项 和 触发更新也的回调方法 */ export class Observer { value: any; dep: Dep; vmCount: number; // number of vms that has this object as root $data constructor (value: any) { this.value = value
// 监听的是 obj = {a:{}}, 这里的观察是整个对象 obj, 而不是某个属性的变化
/*
举个列子
data: { a:{b:{c:1}} } var w1 = this.$watch('a.b.c', fn1);
初始化的时候, 会把这个data生成 6 个dep, 其中三个是这个方法生成的, 另外三个是下面那个defineReact方法生成的
前三个对应的值分别是 {a:{b:{c:1}}} {b:{c:1}} {c:1}
后三个对应的值分别是 data.a a.b b.c
上面的 w1 这个watcher收集依赖的时候, 会有5个依赖, 分别是 data.a || {b:{c:1}} || a.b || {c:1} || b.c 对应的dep
在wathc a.b.c 的时候就会依次去取值 data.a => a.b => b.c, 收集这个三个属性依赖就可以了, 至于为什么要收集前三个对象依赖,
还没有搞清楚, 可能在一些地方会在这三个对象ob中挂载一些东西, 以便发布一些东西.
*/
this.dep = new Dep()
this.vmCount = 0
//value增加一个 属性 __ob__ , 值就是 observer def(value, '__ob__', this)
if (Array.isArray(value)) {
//有__proto__的浏览器才能监控数组的push pop 方法, 如果没有, 则不能监控 const augment = hasProto ? protoAugment : copyAugment
augment(value, arrayMethods, arrayKeys)
//监控数组的每一项 this.observeArray(value) } else { this.walk(value) } } /** * Walk through each property and convert them into * getter/setters. This method should only be called when * value type is Object.
循环每一个对象属性, 逐个监控 */ walk (obj: Object) { const keys = Object.keys(obj) for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i], obj[keys[i]]) } } /** * Observe a list of Array items.
监控数组每个元素, 如果元素也是数组, 又走数组监控的分支 */ observeArray (items: Array<any>) { for (let i = 0, l = items.length; i < l; i++) { observe(items[i]) } } } // helpers /** * Augment an target Object or Array by intercepting * the prototype chain using __proto__
利用 __proto__ 截断原有的原型链
相当于
arrA.__proto__ = arrayMethods
这样的话 , 用户使用 arrA.push('haha');
就会沿着 __proto__找到 arrayMethods 属性 __proto__ 的push方法, 因为这个方法被检测了, 所以会触发set方法, 最终触发 notify() */ function protoAugment (target, src: Object) { /* eslint-disable no-proto */ target.__proto__ = src /* eslint-enable no-proto */ } /** * Augment an target Object or Array by defining * hidden properties. *通过定义隐藏属性来加强一个目标对象 或者 数组 /* istanbul ignore next */ function copyAugment (target: Object, src: Object, keys: Array<string>) { for (let i = 0, l = keys.length; i < l; i++) { const key = keys[i] def(target, key, src[key]) } } /** * Attempt to create an observer instance for a value, * returns the new observer if successfully observed, * or the existing observer if the value already has one.
如果监控的值已经存在, 返回这个订阅者 */ export function observe (value: any, asRootData: ?boolean): Observer | void { if (!isObject(value)) { return } let ob: Observer | void
//如果这个值有__ob__, 直接返回 if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { ob = value.__ob__ } else if (
//如果不是服务器 而且 能被监控, 而且是一个数组或者对象, 而且这个对象不是vue实例 observerState.shouldConvert && !isServerRendering() && (Array.isArray(value) || isPlainObject(value)) && Object.isExtensible(value) && !value._isVue ) {
//根据值来创建ob ob = new Observer(value) } if (asRootData && ob) { ob.vmCount++ } return ob } /** * Define a reactive property on an Object.
为属性添加监控主方法 */ export function defineReactive ( obj: Object, key: string, val: any, customSetter?: Function ) {
//而这个dep是 observe下的某一个属性, 比如监听的是 obj = {a:{}}, 这里的dep是obj的属性 a const dep = new Dep()
//获取Object的get set函数 const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) { return } // cater for pre-defined getter/setters const getter = property && property.get const setter = property && property.set
//如果val也是一个对象, 而且监控成功, 返回 一个observe实例 let childOb = observe(val)
Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { const value = getter ? getter.call(obj) : val
if (Dep.target) { dep.depend() if (childOb) {
childOb.dep.depend() } if (Array.isArray(value)) { dependArray(value) } } return value }, set: function reactiveSetter (newVal) { const value = getter ? getter.call(obj) : val /* eslint-disable no-self-compare */ if (newVal === value || (newVal !== newVal && value !== value)) { return } /* eslint-enable no-self-compare */ if (process.env.NODE_ENV !== 'production' && customSetter) { customSetter() } if (setter) { setter.call(obj, newVal) } else { val = newVal } childOb = observe(newVal) dep.notify() } }) } /** * Set a property on an object. Adds the new property and * triggers change notification if the property doesn't * already exist.
对象添加一个属性, 如果新的属性, 触发通知, 数组的$set 方法 其实就是通过splice方法触发的页面变化 */ export function set (target: Array<any> | Object, key: any, val: any): any { if (Array.isArray(target) && typeof key === 'number') { target.length = Math.max(target.length, key) target.splice(key, 1, val) return val } if (hasOwn(target, key)) { target[key] = val return val } const ob = (target : any).__ob__ if (target._isVue || (ob && ob.vmCount)) { process.env.NODE_ENV !== 'production' && warn( 'Avoid adding reactive properties to a Vue instance or its root $data ' + 'at runtime - declare it upfront in the data option.' ) return val } if (!ob) { target[key] = val return val } defineReactive(ob.value, key, val) ob.dep.notify() return val } /** * Delete a property and trigger change if necessary.
Vue.$remove 方法触发页面变化的来源 */ export function del (target: Array<any> | Object, key: any) { //判断如果是数组
if (Array.isArray(target) && typeof key === 'number') { target.splice(key, 1) return } const ob = (target : any).__ob__
//避免删除vue实例 if (target._isVue || (ob && ob.vmCount)) { process.env.NODE_ENV !== 'production' && warn( 'Avoid deleting properties on a Vue instance or its root $data ' + '- just set it to null.' ) return } if (!hasOwn(target, key)) { return }
//如果是对象 delete target[key] if (!ob) { return }
//触发通知 ob.dep.notify() }
/** * Collect dependencies on array elements when the array is touched, since * we cannot intercept array element access like property getters. */ function dependArray (value: Array<any>) { for (let e, i = 0, l = value.length; i < l; i++) { e = value[i] e && e.__ob__ && e.__ob__.dep.depend() if (Array.isArray(e)) { dependArray(e) } } }