一.数据劫持
在上节中我们已经知道,vue在哪里做了状态的初始化(initState)
export function initState (vm: Component) {vm._watchers = []const opts = vm.$optionsif (opts.props) initProps(vm, opts.props)if (opts.methods) initMethods(vm, opts.methods)if (opts.data) {initData(vm)} else {observe(vm._data = {}, true /* asRootData */)}if (opts.computed) initComputed(vm, opts.computed)if (opts.watch && opts.watch !== nativeWatch) {initWatch(vm, opts.watch)}}
这里又进行了细化和拆分,对不同的属性做了不同的初始化操作,原来我们常用的 api 都在这里做的初始化~
1.数据的初始化
这里我们先关心数据是如何进行初始化操作的
function initData (vm: Component) {let data = vm.$options.datadata = vm._data = typeof data === 'function'? getData(data, vm): data || {}// 1.数据不是对象则发生异常if (!isPlainObject(data)) {data = {}process.env.NODE_ENV !== 'production' && warn('data functions should return an object:\n' +'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',vm)}const keys = Object.keys(data)const props = vm.$options.propsconst methods = vm.$options.methodslet i = keys.length// 2.校验数据是否在method中已经声明过while (i--) {const key = keys[i]if (process.env.NODE_ENV !== 'production') {if (methods && hasOwn(methods, key)) {warn(`Method "${key}" has already been defined as a data property.`,vm)}}// 3.校验数据是否在属性中已经声明过if (props && hasOwn(props, key)) {process.env.NODE_ENV !== 'production' && warn(`The data property "${key}" is already declared as a prop. ` +`Use prop default value instead.`,vm)} else if (!isReserved(key)) {// 4.将_data代理到实例上proxy(vm, `_data`, key)}}// 5.观测数据observe(data, true /* asRootData */)}
这里主要是检测属性是否被重复声明,并对属性进行观测
2.观测数据
export function observe (value: any, asRootData: ?boolean): Observer | void {// 1.如果不是对象直接returnif (!isObject(value) || value instanceof VNode) {return}let ob: Observer | void// 2.如果已经观测过则直接返回上次观测的实例if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { //ob = value.__ob__} else if (shouldObserve &&!isServerRendering() &&(Array.isArray(value) || isPlainObject(value)) &&Object.isExtensible(value) &&!value._isVue) {// 3.如果可以观测就进行观测ob = new Observer(value)}// 4.如果是根数据 vmCount标注为1if (asRootData && ob) {ob.vmCount++}return ob}
只观测对象数据类型,已经观测的不在进行观测,不能扩展的属性不进行观测。
export class Observer {constructor (value: any) {this.value = valuethis.dep = new Dep()this.vmCount = 0def(value, '__ob__', this)// 1.数组的话重写数组原型方法if (Array.isArray(value)) {if (hasProto) {protoAugment(value, arrayMethods)} else {copyAugment(value, arrayMethods, arrayKeys)}// 2.观测数组中是对象类型的数据this.observeArray(value)} else {// 3.对象的话使用defineProperty重新定义属性this.walk(value)}}walk (obj: Object) {const keys = Object.keys(obj)for (let i = 0; i < keys.length; i++) {defineReactive(obj, keys[i])}}observeArray (items: Array<any>) {for (let i = 0, l = items.length; i < l; i++) {observe(items[i])}}}
这里要区分对象和数组,如果是数组不能使用Object.defineProperty会造成性能浪费,所以采用重写可以更改数组本身的方法的方式。
3.对象的观测
对象的观测就是将所有属性使用 defineProperty 进行重新定义
export function defineReactive (obj: Object,key: string,val: any,customSetter?: ?Function,shallow?: boolean) {// 1.如果对象不可配置则直接退出const property = Object.getOwnPropertyDescriptor(obj, key)if (property && property.configurable === false) {return}// 2.获取getter和setterconst getter = property && property.getconst setter = property && property.set// 3.重新定义set和get方法Object.defineProperty(obj, key, {enumerable: true,configurable: true,get: function reactiveGetter () {const value = getter ? getter.call(obj) : valreturn value},set: function reactiveSetter (newVal) {const value = getter ? getter.call(obj) : valif (newVal === value || (newVal !== newVal && value !== value)) {return}if (getter && !setter) returnif (setter) {setter.call(obj, newVal)} else {val = newVal}}})}
对象的属性劫持已经烂大街了,非常简单就是通过 defineProperty 来实现的, 如果你还不会那得好好反思一下了。这里提一下:想减少观测可以使用 Object.freeze 冻结对象
4.数组的观测
数组的观测就是通过重写原型方法来实现的
export const arrayMethods = Object.create(arrayProto)const methodsToPatch = ['push','pop','shift','unshift','splice','sort','reverse']methodsToPatch.forEach(function (method) {// cache original methodconst original = arrayProto[method]def(arrayMethods, method, function mutator (...args) {const result = original.apply(this, args)const ob = this.__ob__let insertedswitch (method) {case 'push':case 'unshift':inserted = argsbreakcase 'splice':inserted = args.slice(2)break}// 对新增的属性再次进行观测if (inserted) ob.observeArray(inserted)return result})})
这里我们所谓的数据观测就是当数据变化时我们可以知道,像对象更改时可以触发 set 方法,像数组调用 push 方法可以触发我们自己写的 push

二.依赖收集
这里我们要回想一下 vue 的渲染过程是通过渲染 watcher 来实现的
let updateComponent = updateComponent = () => {vm._update(vm._render(), hydrating)}new Watcher(vm, updateComponent, noop, {}, true /* isRenderWatcher */
在我们创建 watcher 时,会对变量进行取值
2.1 对象依赖收集
对于对象而言,取值就会触发 get 方法,我们可以在 defineProperty 的 get 中进行依赖收集,在 set 中通知 watcher 进行更新操作
class Watcher {constructor (vm: Component,expOrFn: string | Function, // updateComponentcb: Function,options?: ?Object,isRenderWatcher?: boolean) {this.vm = vmif (isRenderWatcher) {vm._watcher = this}this.cb = cbthis.id = ++uid // uid for batchingthis.active = truethis.dirty = this.lazy // for lazy watchersthis.deps = []this.newDeps = []this.depIds = new Set()this.newDepIds = new Set()this.expression = process.env.NODE_ENV !== 'production'? expOrFn.toString(): ''// 将updateComponent 放到this.getter上this.getter = expOrFnthis.value = this.lazy? undefined: this.get() // 执行get方法}get () {pushTarget(this) // Dep.target = 渲染watcherlet valueconst vm = this.vmtry {value = this.getter.call(vm, vm) // 开始取值 那么在get方法中就可以获取到这个全局变量Dep.target} catch (e) {if (this.user) {handleError(e, vm, `getter for watcher "${this.expression}"`)} else {throw e}} finally {// "touch" every property so they are all tracked as// dependencies for deep watchingif (this.deep) {traverse(value)}popTarget() // 结束后进行清理操作this.cleanupDeps()}return value}}
渲染 watcher,默认会调用 get 方法也就是我们传入的 updateComponent 方法,在调用此方法前先将 watcher存到全局中,这样再取值时可以获取到这个 watcher。
const dep = new Dep()get: function reactiveGetter () {const value = getter ? getter.call(obj) : valif (Dep.target) { // 如果有watcher 将dep和watcher对应起来dep.depend()}return value}set: function reactiveSetter (newVal) {dep.notify(); // 当属性更新时通知dep中的所有watcher进行更新操作}
2.2 数组的依赖收集
let childOb = !shallow && observe(val)get: function reactiveGetter () {const value = getter ? getter.call(obj) : valif (Dep.target) {dep.depend()if (childOb) { // 如果值也是个对象的话,对这个值进行依赖收集childOb.dep.depend()if (Array.isArray(value)) { // 如果是数组对数组中的内容继续进行依赖收集dependArray(value)}}}return value}// 调用数组方法时进行watcher的更新操作methodsToPatch.forEach(function (method) {ob.dep.notify()})
这里的 watcher 和 dep 的关系是多对多的关系,一个属性一个 dep,每个 dep 里存放着多个watcher,同时watcher 也会记住对应的 dep。
export default class Dep {constructor () {this.id = uid++this.subs = []}addSub (sub: Watcher) {this.subs.push(sub)}depend () {if (Dep.target) {Dep.target.addDep(this) // 让watcher记住自己}}notify () {const subs = this.subs.slice()for (let i = 0, l = subs.length; i < l; i++) {subs[i].update() // 让存储的watcher依次调用更新方法}}}
class Watcher {constructor (vm: Component,expOrFn: string | Function, // updateComponentcb: Function,options?: ?Object,isRenderWatcher?: boolean) {this.deps = []this.newDeps = []this.depIds = new Set()this.newDepIds = new Set()this.expression = process.env.NODE_ENV !== 'production'? expOrFn.toString(): ''// 1.将updateComponent 放到this.getter上this.getter = expOrFnthis.value = this.lazy? undefined// 2.执行get方法: this.get()}addDep (dep: Dep) {const id = dep.idif (!this.newDepIds.has(id)) {this.newDepIds.add(id)this.newDeps.push(dep)if (!this.depIds.has(id)) {// 3.让dep记录watcherdep.addSub(this)}}}update () {queueWatcher(this)}}
watcher 中会进行虑重操作,实现 watcher 和 dep 互相记忆
三.异步更新
为了防止多次更改同一个属性或者多次修改不同属性(他们依赖的 watcher 相同) 会导致频繁更新渲染
export function queueWatcher (watcher: Watcher) {const id = watcher.id// 1.判断watcher是否已经存放了if (has[id] == null) {has[id] = true// 2.将watcher存放到队列中queue.push(watcher)// queue the flushif (!waiting) {waiting = truenextTick(flushSchedulerQueue) // 在下一队列中清空queue}}}
对相同 watcher 进行过滤操作,当同步的更改状态完毕时再去更新watcher

