vue源码分析
1数据响应式原理分析
1关键原理
Object.defineProperty,从get方法中添加监听依赖,在set方法中触发依赖
2 结合源码,详细分析
-
在vue初始化的时候,会触发
initData函数,拿到this.$option.data值,此时的data并不是一个对象,而是函数,需要通过getData将其转成对象的形式,在根据在methods、props是否已经有申明了data对象的值,对其进行校验,之后就是通过observe函数进行监听。observe(data, true /* asRootData */)// state.js function initData (vm: Component) { let data = vm.$options.data // 拿到vue的data函数 // 这里的data如果是函数的话,会在getData中执行,data.call(vm,vm),返回一个对象 // {name: '小明'} data = vm._data = typeof data === 'function' ? getData(data, vm) : data || {} 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 ) } // proxy data on instance const keys = Object.keys(data) const props = vm.$options.props const methods = vm.$options.methods let i = keys.length 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 ) } } 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)) { proxy(vm, `_data`, key) } } // observe data 进行数据监听 observe(data, true /* asRootData */) } -
observe函数,主要是判断data是否已经标志为观察了,如果没有,则实例化new Observe(value)// observe/index.js export function observe (value: any, asRootData: ?boolean): Observer | void { // 如果data不是对象的话,就返回 if (!isObject(value) || value instanceof VNode) { return } let ob: Observer | void // 是否已经标志为监听对象了 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 ) { // 若没有观察,则触发观察函数 ob = new Observer(value) } if (asRootData && ob) { // 计数累加 ob.vmCount++ } return ob } -
在
Observe类中,实例化后会注册Dep()实例,Array.isArray(value)判断是否是数组,是的话执行observeArray,还是遍历执行observe函数,深度观察数组。对象则直接调用defineReactive(obj, key),即重新定义对象数据// observe/index.js class Observer { value: any; dep: Dep; vmCount: number; // number of vms that have this object as root $data constructor (value: any) { this.value = value this.dep = new Dep() this.vmCount = 0 def(value, '__ob__', this) // Array.isArray(value)判断是否是数组,是的话执行observeArray,还是遍历执行observe函数,对象则直接调用defineReactive(obj, key) if (Array.isArray(value)) { if (hasProto) { protoAugment(value, arrayMethods) } else { copyAugment(value, arrayMethods, arrayKeys) } this.observeArray(value) // 深度观察数组 } else { this.walk(value) } } /** * Walk through all properties 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]) // 重新定义对象类型-> 响应式数据 } } /** * Observe a list of Array items. */ observeArray (items: Array<any>) { for (let i = 0, l = items.length; i < l; i++) { observe(items[i]) } } } -
defineReactive重新定义响应式数据,定义Object.defineProperty,获取数据时,触发dep.depend,修改数据时,触发对应数值更新dep.notify()export function defineReactive ( obj: Object, key: string, val: any, customSetter?: ?Function, shallow?: boolean ) { const dep = new Dep() // 实例化一个Dep,监视器 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 if ((!getter || setter) && arguments.length === 2) { val = obj[key] } let childOb = !shallow && observe(val) // 递归,如果是还是对象,则继续执行observe Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { const value = getter ? getter.call(obj) : val if (Dep.target) { dep.depend() // 收集依赖watcher if (childOb) { childOb.dep.depend() // 收集依赖watcher 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() } // #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() // 触发对应数值更新 } }) } -
解决疑难点。
这里为什么会判断Dep.target?
if (Dep.target) { dep.depend() // 收集依赖watcher if (childOb) { childOb.dep.depend() // 收集依赖watcher if (Array.isArray(value)) { dependArray(value) } } } // 这里的`Dep.target`为`true`是因为前面`notify()`调用了`pushTarget` function pushTarget (target: ?Watcher) { targetStack.push(target) Dep.target = target }
3 总结
2数组是如何被监测的
1关键原理
- 在VUE原型链上重写了数组的方法
push、pop等方法,这样在调用数组API的时候,会进行数据监测,通知依赖更新。如果数组中含有引用类型,会对引用类型进行监测。
2结合源码,详细分析
-
Observe实例中,如果new Observer(value),则会对value进行判断,如果为数组的话,会对数组的操作方法进行重写// observe/index.js // Observe类中一截 if (Array.isArray(value)) { if (hasProto) { protoAugment(value, arrayMethods) } else { copyAugment(value, arrayMethods, arrayKeys) // arrayMethods为重写的数组的方法 } this.observeArray(value) // 深度遍历数组,如果数组中还有对象,则继续走observe } else { this.walk(value) } // observeArray方法 observeArray (items: Array<any>) { for (let i = 0, l = items.length; i < l; i++) { observe(items[i]) } } -
对数组方法重写,调用时触发
dep.notify(),进行视图更新// observe/array.js const arrayProto = Array.prototype export const arrayMethods = Object.create(arrayProto) const methodsToPatch = [ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' ] /** * Intercept mutating methods and emit events */ // 对原来的数组的方法进行重写,保留了原来的方法,扩展了观察。手动调用dep.notify(),更新视图 methodsToPatch.forEach(function (method) { // cache original method const original = arrayProto[method] def(arrayMethods, method, function mutator (...args) { const result = original.apply(this, args) const ob = this.__ob__ let inserted switch (method) { case 'push': case 'unshift': inserted = args break case 'splice': inserted = args.slice(2) break } if (inserted) ob.observeArray(inserted) // notify change ob.dep.notify() return result }) })
vue异步渲染原理
1. 特点
vue如果不进行异步更新的话,会在每次数据更新时,重新渲染组件,造成性能问题。
2. 结合源码,详细分析
-
dep.notify(),触发数据更新,通知watcher
// dep.js中 // 执行watcher中的update方法 notify () { // stabilize the subscriber list first const subs = this.subs.slice() if (process.env.NODE_ENV !== 'production' && !config.async) { // subs aren't sorted in scheduler if not running async // we need to sort them now to make sure they fire in correct // order subs.sort((a, b) => a.id - b.id) } // 遍历subs,触发watcher.js中的update // subs中存放的是多个watcher,会一并触发。 for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() } } -
watcher类中的update触发,会将watch放在queueWatcher队列中update () { /* istanbul ignore else */ if (this.lazy) { this.dirty = true } else if (this.sync) { this.run() } else { queueWatcher(this) } } -
queueWatcher中主要是过滤watcher,根据plushing状态去区分push到queue中,还是移除。最后执行nextTick(flushSchedulerQueue)function queueWatcher (watcher: Watcher) { const id = watcher.id // 过滤watcher的id,如果存在则不处理,不存在则标记 if (has[id] == null) { has[id] = true if (!flushing) { // 如果状态是!flushing的,则放入queue队列中 queue.push(watcher) } else { // if already flushing, splice the watcher based on its id // if already past its id, it will be run next immediately. let i = queue.length - 1 while (i > index && queue[i].id > watcher.id) { i-- } queue.splice(i + 1, 0, watcher) // flushing则移除 } // queue the flush if (!waiting) { waiting = true if (process.env.NODE_ENV !== 'production' && !config.async) { flushSchedulerQueue() return } nextTick(flushSchedulerQueue) } } } -
``nextTick(flushSchedulerQueue)
中执行watcher.run()`function flushSchedulerQueue () { currentFlushTimestamp = getNow() flushing = true let watcher, id // Sort queue before flush. // This ensures that: // 1. Components are updated from parent to child. (because parent is always // created before the child) // 2. A component's user watchers are run before its render watcher (because // user watchers are created before the render watcher) // 3. If a component is destroyed during a parent component's watcher run, // its watchers can be skipped. queue.sort((a, b) => a.id - b.id) // do not cache length because more watchers might be pushed // as we run existing watchers for (index = 0; index < queue.length; index++) { watcher = queue[index] if (watcher.before) { watcher.before() } id = watcher.id has[id] = null watcher.run() // in dev build, check and stop circular updates. if (process.env.NODE_ENV !== 'production' && has[id] != null) { circular[id] = (circular[id] || 0) + 1 if (circular[id] > MAX_UPDATE_COUNT) { warn( 'You may have an infinite update loop ' + ( watcher.user ? `in watcher with expression "${watcher.expression}"` : `in a component render function.` ), watcher.vm ) break } } } // keep copies of post queues before resetting state const activatedQueue = activatedChildren.slice() const updatedQueue = queue.slice() resetSchedulerState() // call component updated and activated hooks callActivatedHooks(activatedQueue) callUpdatedHooks(updatedQueue) // devtool hook /* istanbul ignore if */ if (devtools && config.devtools) { devtools.emit('flush') } }
3总结
- 调用 notify() 方法,通知watcher 进行更新操作
- 依次调用watcher 的 update 方法
- 对watcher 进行去重操作(通过id),放到队列里
- 执行完后异步清空这个队列, nextTick(flushSchedulerQueue) 进行批量更新操作