初始化数据
// TODO: 初始化数据function initData (vm: Component) { let data = vm.$options.data // data 是不是函数,是函数就让他执行获取数据 挂到 vm._data data = vm._data = typeof data === 'function' ? getData(data, vm) : data || {} // 观测数据 observe(data, true /* asRootData */)}
观测数据
/** * 响应式处理的真正入口 * 为对象创建观察者实例,如果对象已经被观察过,则返回已有的观察者实例,否则创建新的观察者实例 * @param {object} value * @param {boolean} asRootData * @returns */export function observe (value, asRootData){ // 如果是对象并且不是 vnode 的时候才观测 if (!isObject(value) || value instanceof VNode) { return } let ob // value 对象上存在 __ob__ 属性,则表示已经做过观察了,直接返回 __ob__ 属性 if(hasOwn(value, '__ob__') && value.__ob__ instanceof Observer){ ob = value.__ob__ } if ( shouldObserve && !isServerRendering() && // 是否需要被观测 (Array.isArray(value) || isPlainObject(value)) && // 是一个数组或对象 Object.isExtensible(value) && // 支持 defineProperty !value._isVue // 不是vue 实例 ) { // 创建观察者实例 ob = new Observer(value) } // 记录观测数量 if (asRootData && ob) { ob.vmCount++ } return ob}
创建观察者实例
/** * 2.观察者类,会被附加到每个被观察的对象上,value.__ob__ = this * 而对象的各个属性则会被转换成 getter/setter,并收集依赖和通知更新 */export class Observer { constructor (value) { this.value = value // ob.value this.dep = new Dep() // 给对象和数组实例化一个 dep 属性 $set 之后就会触发他们自己的 update 方法 this.vmCount = 0 // data.__ob__ = this // 将当前 Observer 实例挂载到 data 上 __ob__ 主要是用来取一些方法,这样被劫持过的所有属性都有 __ob__ def(value, '__ob__', this) // 不可枚举 TODO: /** * value 为数组 * hasProto = '__proto__' in {} * 用于判断对象是否存在 __proto__ 属性,通过 obj.__proto__ 可以访问对象的原型链 * 但由于 __proto__ 不是标准属性,所以有些浏览器不支持,比如 IE6-10,Opera10.1 * 为什么要判断,是因为一会儿要通过 __proto__ 操作数据的原型链 * 覆盖数组默认的七个原型方法,以实现数组响应式 */ if (Array.isArray(value)) { // 数组劫持 切片编程 // 如果支持原型链 if (hasProto) { protoAugment(value, arrayMethods) } else { // 不支持就拷贝一份 copyAugment(value, arrayMethods, arrayKeys) } // 观测数组 如果数组中的数据是对象类型,需要监控对象的变化 arr: [{name: 'dabao'}, {age: 3}] this.observeArray(value) } else { // 对象劫持 this.walk(value) } } /** * 遍历对象上的每个 key,为每个 key 设置响应式 * 仅当值为对象时才会走这里 */ walk (obj) { const keys = Object.keys(obj) for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i]) } } /** * 遍历数组的每一项.目的是对数组中的数组和数组中的对象再次劫持,递归 */ observeArray (items) { for (let i = 0, l = items.length; i < l; i++) { observe(items[i]) } }}/** * 设置 target.__proto__ 的原型对象为 src * 比如 数组对象,arr.__proto__ = arrayMethods * @param {Array} target value * @param {object} src arrayMethods */function protoAugment (target, src) { target.__proto__ = src}/** * 在目标对象上定义指定属性 * 比如数组:为数组对象定义那七个方法 */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]) }}/** * Define a property. */export function def (obj: Object, key: string, val: any, enumerable?: boolean) { Object.defineProperty(obj, key, { value: val, enumerable: !!enumerable, writable: true, configurable: true })}
拦截 obj[key] 的读取和设置操作
/** * 拦截 obj[key] 的读取和设置操作: * 1、在第一次读取时收集依赖,比如执行 render 函数生成虚拟 DOM 时会有读取操作 * 2、在更新时设置新值并通知依赖更新 */export function defineReactive ( obj, key, val, customSetter, shallow) { // 实例化 dep,一个 key 一个 dep const dep = new Dep() // 获取 obj[key] 的属性描述符,发现它是不可配置对象的话直接 return const property = Object.getOwnPropertyDescriptor(obj, key) // 数据必须支持 可配置 object.freeze 包装的对象 configurable 为 false if (property && property.configurable === false) { return } /** * getter 和 setter 是为了兼容这种情况 * a: { * get(){}, * set(){}, * } */ const getter = property && property.get const setter = property && property.set // 不存在 getter 只有 setter if ((!getter || setter) && arguments.length === 2) { val = obj[key] } // 递归调用,处理 val 即 obj[key] 的值为对象的情况,保证对象中的所有 key 都被观察 let childOb = !shallow && observe(val) Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { // 取值 const value = getter ? getter.call(obj) : val /** * Dep.target 为 Dep 类的一个静态属性,值为 watcher,在实例化 Watcher 时会被设置 * 实例化 Watcher 时会执行 new Watcher 时传递的回调函数(computed 除外,因为它懒执行) * 而回调函数中如果有 vm.key 的读取行为,则会触发这里的 读取 拦截,进行依赖收集 * 回调函数执行完以后又会将 Dep.target 设置为 null,避免这里重复收集依赖 */ if (Dep.target) { // 依赖收集,在 dep 中添加 watcher,也在 watcher 中添加 dep dep.depend() // 有子元素 也对其进行依赖收集 if (childOb) { // 这就是 this.key.chidlKey 被更新时能触发响应式更新的原因 childOb.dep.depend() // 如果这个 obj[key] 是 数组,则触发数组响应式 if (Array.isArray(value)) { // 数组嵌套数组 [[]] 递归收集 arr:[[[]]] 改最里面的一层 dependArray(value) // 嵌套数组递归收集 } } } return value }, // set 拦截对 obj[key] 的设置操作 set: function reactiveSetter (newVal) { // 旧的 obj[key] const value = getter ? getter.call(obj) : val // 新旧值一样直接return if (newVal === value || (newVal !== newVal && value !== value)) { return } // setter 不存在说明该属性是一个只读属性,直接 return // #7981: for accessor properties without setter if (getter && !setter) return // 设置新值 if (setter) { setter.call(obj, newVal) } else { val = newVal } // 观测这个新值, 如果赋值的是一个新对象,需要对这个新对象进行劫持 childOb = !shallow && observe(newVal) // 依赖通知更新 dep.notify() } })}
创建渲染watcher
// lifecycle.js/** * TODO: * 实例化 渲染 Watcher */ new Watcher(vm, updateComponent, noop, { before () { if (vm._isMounted && !vm._isDestroyed) { callHook(vm, 'beforeUpdate') } } }, true /* isRenderWatcher */)
watcher源码
import Dep, { pushTarget, popTarget } from './dep'// let uid = 0/** * * 渲染watcher 每个组件是一个watcher * Vue1 是每个差值表达式都有一个 wacher,这就导致粒度太细,追踪的依赖就太多了,很消耗内存, * Vue2.x 和 Vue3.x 都是以整个组件为一个 Watcher,这样状态发生变化后会通知到组件,组件内部在使用虚拟dom 进行对比,降低消耗 * 计算属性、 * vm.$watch * * expOrFn 只能是一个 以点分割的路径或者是个函数 对于渲染watcher来说这里的 expOrFn 其实就是 vm._render 生成虚拟 dom、执行 dom-diff、更新真实 dom。 * vm.$watch('a.b.c',function(newValue, oldValue) {console.log('读数据')}) * vm.$watch(function(){return data.a + data.b)},function(newValue, oldValue) {console.log('读数据')}) * */export default class Watcher { // 希望在第一个参数发生变化时触发第二个参数中的函数 constructor ( vm, expOrFn, cb, options, isRenderWatcher ) { this.vm = vm // 当前组件实例 if (isRenderWatcher) { // 渲染watcher vm._watcher = this } vm._watchers.push(this) // options if (options) { this.deep = !!options.deep this.user = !!options.user this.lazy = !!options.lazy this.sync = !!options.sync this.before = options.before } else { this.deep = this.user = this.lazy = this.sync = false } this.cb = cb // //用户传入的回调函数 vm.$watch('msg',cb) this.id = ++uid // watcher的唯一标识 每次产生一个watcher都要有一个唯一标识 this.active = true this.dirty = this.lazy // for lazy watchers this.deps = [] // 存 watcher 的依赖收集器 this.newDeps = [] // 存 dep 的 this.depIds = new Set() this.newDepIds = new Set() this.expression = process.env.NODE_ENV !== 'production' ? expOrFn.toString() : '' // 判断传入的是表达式还是函数 赋值给 this.getter if (typeof expOrFn === 'function') { this.getter = expOrFn } else { this.getter = parsePath(expOrFn) // 用parsePath 读取一个字符串的 keyPath } // 对于渲染watcher 会立马执行 this.get()进行页面渲染 this.value = this.lazy ? undefined : this.get() } /** * 让getter执行,从实例上取值渲染页面 就会调用Object.defineProperty 的get方法,就会走依赖收集的过程 * this.getter 是实例化 watcher 时传递的第二个参数,一个函数或者字符串,比如:updateComponent 或者 parsePath 返回的读取 this.xx 属性值的函数 */ get () { // 将 Dep.target 属性指向自己 pushTarget(this) // Dep.target = target // value 为回调函数执行的结果 比如执行 updateComponent,进入 patch 阶段 let value = this.getter.call(vm, vm) const vm = this.vm // popTarget() // 这里清空了Dep.target,为了防止notify触发时,不停的绑定Watcher与Dep, // 造成代码死循环 Dep.target = null; this.cleanupDeps() return value } /** 两件事: * 1、添加 dep 给自己(watcher) * 2、添加自己(watcher)到 dep */ addDep (dep) { // 判重,如果 dep 已经存在则不重复添加 const id = dep.id if (!this.newDepIds.has(id)) { // 缓存 dep.id,用于判重 this.newDepIds.add(id) // 将 dep 存入 watcher this.newDeps.push(dep) // 避免在 dep 中重复添加 watcher if (!this.depIds.has(id)) { // 将watcher 存入 dep dep.addSub(this) } } } /** * Clean up for dependency collection. */ cleanupDeps () { let i = this.deps.length while (i--) { const dep = this.deps[i] if (!this.newDepIds.has(dep.id)) { dep.removeSub(this) } } let tmp = this.depIds this.depIds = this.newDepIds this.newDepIds = tmp this.newDepIds.clear() tmp = this.deps this.deps = this.newDeps this.newDeps = tmp this.newDeps.length = 0 } /** * 更新方法 * 根据 watcher 配置项,决定接下来怎么走,一般是 queueWatcher */ update () { /* istanbul ignore else */ if (this.lazy) { // 懒执行时走这里,比如 computed // 将 dirty 置为 true,可以让 computedGetter 执行时重新计算 computed 回调函数的执行结果 this.dirty = true } else if (this.sync) { // 同步执行,在使用 vm.$watch 或者 watch 选项时可以传一个 sync 选项, // 当为 true 时在数据更新时该 watcher 就不走异步更新队列,直接执行 this.run // 方法进行更新 this.run() } else { // 更新时一般都这里,将 watcher 放入 watcher 队列 queueWatcher(this) } } /** * 由 刷新队列函数 flushSchedulerQueue 调用,完成如下几件事: * 1、执行实例化 watcher 传递的第二个参数,updateComponent 或者 获取 this.xx 的一个函数(parsePath 返回的函数) * 2、更新旧值为新值 * 3、执行实例化 watcher 时传递的第三个参数,比如用户 watcher 的回调函数 */ run () { if (this.active) { // 调用 this.get 方法 const value = this.get() if ( value !== this.value || isObject(value) || this.deep ) { // 更新旧值为新值 const oldValue = this.value this.value = value if (this.user) { // 如果是用户 watcher,则执行用户传递的第三个参数 —— 回调函数,参数为 val 和 oldVal try { this.cb.call(this.vm, value, oldValue) } catch (e) { handleError(e, this.vm, `callback for watcher "${this.expression}"`) } } else { // 渲染 watcher,this.cb = noop,一个空函数 this.cb.call(this.vm, value, oldValue) } } } } /** * Evaluate the value of the watcher. * This only gets called for lazy watchers. */ evaluate () { this.value = this.get() this.dirty = false } /** * 依赖于这个观察者收集的所有 deps. */ depend () { let i = this.deps.length while (i--) { this.deps[i].depend() } } /** * Remove self from all dependencies' subscriber list. */ teardown () { if (this.active) { // remove self from vm's watcher list // this is a somewhat expensive operation so we skip it // if the vm is being destroyed. if (!this.vm._isBeingDestroyed) { remove(this.vm._watchers, this) } let i = this.deps.length while (i--) { this.deps[i].removeSub(this) } this.active = false } }}
依赖收集器Dep
/* @flow */import type Watcher from './watcher'import { remove } from '../util/index'import config from '../config'let uid = 0/** * 一个 dep 对应一个 obj.key [] * 在读取响应式数据时,负责收集依赖,每个 dep(或者说 obj.key)依赖的 watcher 有哪些 * 在响应式数据更新时,负责通知 dep 中那些 watcher 去执行 update 方法 */export default class Dep { constructor () { this.id = uid++ this.subs = [] } addSub (sub) { // sub: Watcher this.subs.push(sub) } removeSub (sub) { // sub: Watcher remove(this.subs, sub) } // 向 watcher 中添加 dep depend () { if (Dep.target) { Dep.target.addDep(this) } } /** * 通知 dep 中的所有 watcher, * 执行 watcher.update() 方法 */ notify () { const subs = this.subs.slice() // 返回一个新数组 // 遍历 dep 中存储的 watcher,执行 watcher.update() for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() } }}Dep.target = nullconst targetStack = []// 设置 Dep.target = watcheexport function pushTarget (target) { targetStack.push(target) Dep.target = target}// 依赖收集结束调用,设置 Dep.target = nullexport function popTarget () { targetStack.pop() Dep.target = targetStack[targetStack.length - 1]}