内容简介:近一年多都在做小程序开发,提到
近一年多都在做小程序开发, Vue
感觉都有写些生疏了,从今天开始阅读一下 Vue
的源码,了解其内部的工作机制,本文涉及的 Vue
版本为 2.6.11
,我已经提前 fork
了一份到 github
上
双向数据绑定
提到 Vue
,自然会想到双向数据绑定,要说他的原理,你也能脱口而出,使用 Object.defineProperty
的 get
、 set
来实现,但要把功能做更强大健壮,往往并不是这么简单
从入口开始
https://github.com/wclimb/vue/blob/dev/src/core/instance/init.js#L15
export function initMixin (Vue: Class<Component>) { Vue.prototype._init = function (options?: Object) { ...... // expose real self vm._self = vm initLifecycle(vm) initEvents(vm) initRender(vm) callHook(vm, 'beforeCreate') initInjections(vm) // resolve injections before data/props initState(vm) initProvide(vm) // resolve provide after data/props callHook(vm, 'created') ...... if (vm.$options.el) { vm.$mount(vm.$options.el) } } }
从上面可以看出初始化 Vue
会调用多个函数来做不同的事情,本文主要讲解双向数据绑定,所以其他的在这里都不重要,这里我们着重关注 initState
内部方法
initState
https://github.com/wclimb/vue/blob/dev/src/core/instance/state.js#L42
export function initState (vm: Component) { vm._watchers = [] const opts = vm.$options // 如果存在props,初始化props if (opts.props) initProps(vm, opts.props) // 挂载定义的方法 if (opts.methods) initMethods(vm, opts.methods) // 如果存在data,执行initData开始创建观察者 if (opts.data) { initData(vm) } else { observe(vm._data = {}, true /* asRootData */) } // 以下同理,都是初始化定义的一些数据 if (opts.computed) initComputed(vm, opts.computed) if (opts.watch) initWatch(vm, opts.watch) }
initData
https://github.com/wclimb/vue/blob/dev/src/core/instance/state.js#L102
function initData (vm: Component) { .... // observe data observe(data, true /* asRootData */) }
observe
https://github.com/wclimb/vue/blob/dev/src/core/observer/index.js#L106
export function observe (value: any, asRootData: ?boolean): Observer | void { 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 }
Observer
export 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其实就是给当前对象定义一个key为__ob__,值为this def(value, '__ob__', this) // 判断当前值是否为数组,如果是数组需要单独处理 // 因为我们知道vue主要原理是 Object.defineProperty,但是它监听不了数组的变化,所以3.0采用了proxy // 但是数组只能监听到部分方法的改动,感兴趣可以看 https://github.com/wclimb/vue/blob/dev/src/core/observer/array.js if (Array.isArray(value)) { if (hasProto) { protoAugment(value, arrayMethods) } else { copyAugment(value, arrayMethods, arrayKeys) } this.observeArray(value) } else { this.walk(value) } } // 除了数组,都会调用该方法来劫持数据 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
下面代码是双向数据绑定额核心
https://github.com/wclimb/vue/blob/dev/src/core/observer/index.js#L131
export function defineReactive ( obj: Object, key: string, val: any, customSetter?: ?Function, shallow?: boolean ) { // 创建dep,主要做依赖收集 const dep = new Dep() const property = Object.getOwnPropertyDescriptor(obj, key) // 判断当前对象是否可定义,不能的话后面执行也没有意义了,所以直接return if (property && property.configurable === false) { return } // 是否有自定义get set const getter = property && property.get const setter = property && property.set // 取当前的值 if ((!getter || setter) && arguments.length === 2) { val = obj[key] } // shallow为undfined,因为上一步没有传,只有定义$attrs和$listeners使用true // 继续调用observe(val)劫持 let childOb = !shallow && observe(val) // 重头戏,调用Object.defineProperty Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { const value = getter ? getter.call(obj) : val // 此时的Dep.target是一个Watcher类,Dep.target全局只有一个 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 if (newVal === value || (newVal !== newVal && value !== value)) { return } ...... if (setter) { setter.call(obj, newVal) } else { // 把值更新为最新的 val = newVal } // 把新值变成响应式的对象 childOb = !shallow && observe(newVal) // 数据发生变化之后通知 dep.notify() } }) }
Dep
Dep
在 Vue
里面也是关键的一环,它负责依赖收集,从上面 defineReactive
内部方法有看到实例化了一个 dep
,然后有使用到 Dep.target
、 dep.depend()
、 dep.notify()
,那么我们来看看 Dep
的实现
let uid = 0 /** * A dep is an observable that can have multiple * directives subscribing to it. */ export default class Dep { static target: ?Watcher; id: number; subs: Array<Watcher>; constructor () { this.id = uid++ this.subs = [] } addSub (sub: Watcher) { this.subs.push(sub) } removeSub (sub: Watcher) { remove(this.subs, sub) } // 收集依赖 depend () { if (Dep.target) { // Dep.target是一个Watcher类,所以Dep.target.addDep调用的是Watcher类里面的方法 Dep.target.addDep(this) } } notify () { // stabilize the subscriber list first const subs = this.subs.slice() for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() } } } // the current target watcher being evaluated. // this is globally unique because there could be only one // watcher being evaluated at any time. Dep.target = null const targetStack = [] // 该方法会用在Watcher内部 export function pushTarget (_target: Watcher) { if (Dep.target) targetStack.push(Dep.target) Dep.target = _target } // 和上面方法一样,调用完pushTarget之后接着会调用popTarget,删除当前目标watcher export function popTarget () { Dep.target = targetStack.pop() }
为什么 Dep.target
会有值呢?因为实例化一个 Watcher
会调用 pushTarget
,但是什么时候会实例化 Watcher
呢?1. 初始化 initComputed
2. 绑定 $watch
3. mountComponent
。
实际上是来自 mountComponent
内部的实例化 Watcher
,我们看看代码
mountComponent
export function mountComponent ( vm: Component, el: ?Element, hydrating?: boolean ): Component { vm.$el = el if (!vm.$options.render) { ..... } callHook(vm, 'beforeMount') let updateComponent /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { updateComponent = () => { ...... } } else { updateComponent = () => { vm._update(vm._render(), hydrating) } } // we set this to vm._watcher inside the watcher's constructor // since the watcher's initial patch may call $forceUpdate (e.g. inside child // component's mounted hook), which relies on vm._watcher being already defined new Watcher(vm, updateComponent, noop, { before () { if (vm._isMounted && !vm._isDestroyed) { callHook(vm, 'beforeUpdate') } } }, true /* isRenderWatcher */) hydrating = false // manually mounted instance, call mounted on self // mounted is called for render-created child components in its inserted hook if (vm.$vnode == null) { vm._isMounted = true callHook(vm, 'mounted') } return vm }
上面代码很重要,主要是视图的初始化过程,我们还可以看到两个生命周期, beforeMount
和 mounted
,实例化 Watcher
,传递了几个主要参数,分别是 vm
、 updateComponent
、和一个 before
方法, vm
就是当前 Vue
的 this
了,重点在 updateComponent
方法,内部是 vm._update(vm._render(), hydrating)
,既然是初始化渲染视图,那么它也没有调用呀,我们先说 vm._render()
,它的作用是生成当前 vnode
,也就是虚拟 dom
,通过 vm.__update
来更新视图,这里其实就牵扯到了新旧虚拟 dom
的比较,也就是 diff
算法,我们知道现在是初始化,也就是说只有一个 vnode
,直接渲染就可以了,关键是在这里它没有执行,所以接下来我们需要看看 Watcher
做了什么
Wachter
https://github.com/wclimb/vue/blob/dev/src/core/observer/watcher.js#L22
export default class Watcher { constructor ( vm: Component, expOrFn: string | Function, cb: Function, options?: ?Object, isRenderWatcher?: boolean ) { this.vm = vm if (isRenderWatcher) { 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 this.id = ++uid // uid for batching this.active = true this.dirty = this.lazy // for lazy watchers this.deps = [] this.newDeps = [] this.depIds = new Set() this.newDepIds = new Set() this.expression = process.env.NODE_ENV !== 'production' ? expOrFn.toString() : '' // parse expression for getter if (typeof expOrFn === 'function') { this.getter = expOrFn } else { this.getter = parsePath(expOrFn) if (!this.getter) { this.getter = noop ... } } this.value = this.lazy ? undefined : this.get() } get () { pushTarget(this) let value const vm = this.vm try { value = this.getter.call(vm, vm) } catch (e) { ..... } finally { // "touch" every property so they are all tracked as // dependencies for deep watching if (this.deep) { traverse(value) } popTarget() this.cleanupDeps() } return value } addDep (dep: Dep) { const id = dep.id if (!this.newDepIds.has(id)) { this.newDepIds.add(id) this.newDeps.push(dep) if (!this.depIds.has(id)) { dep.addSub(this) } } } ....
我们上一步实例化了 Wathcer
,传入了一些参数, Watcher
内部构造函数最后执行了 this.get()
,也就是内部 get
方法,重点来了
get () { pushTarget(this) let value const vm = this.vm try { value = this.getter.call(vm, vm) } catch (e) { ..... } finally { // "touch" every property so they are all tracked as // dependencies for deep watching if (this.deep) { traverse(value) } popTarget() this.cleanupDeps() } return value }
首先它会调用之前的方法 pushTarget
来赋值 Dep.target
,所以我们会知道 Dep.target
一定是一个 Watcher
,紧接着调用了 this.getter
, getter
是什么? getter
实际就是传进来的第二个参数 expOrFn
,是一个方法。之前我们说的 mountComponent
方法内部传入的 updateComponent
方法,会在这里被调用,达到初始化视图的作用。
继续往下看,我们可以看到 addDep
方法,是不是很眼熟,之前我们说了 Vue
劫持数据,在 get
内会收集依赖,调用 dep.depend()
方法,然后 depend
方法执行了 Dep.target.addDep(this)
,接着会调用这里我们说的 addDep
方法,传入的 this
就是 Dep
addDep (dep: Dep) { const id = dep.id if (!this.newDepIds.has(id)) { this.newDepIds.add(id) this.newDeps.push(dep) if (!this.depIds.has(id)) { dep.addSub(this) } } }
首先判断是否已经存在 dep
,没有就往调用 dep
的方法 addSub
追加当前的 Watcher
,调用 addSub
会存在 dep
内的 subs
, subs
是一个数组,整个流程是不是很绕,我们可以画个图来看看
派发更新
我们收集了依赖有什么用呢?我们修改值之后需要更新视图和数据,这个动作我们是可以预知的,因为会触发之前的 setter
, setter
内部调用了 dep.notify()
,这个时候就可以通知所有订阅者更新视图
setter
set: function reactiveSetter (newVal) { var 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 (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.notify()
会调用 dep
的 notify
方法,我们知道 subs
内部都是 Watcher
类的数组,需要更新全部就得其遍历他们,执行 Watcher
内部的 update
方法
export default class Dep { constructor () { this.id = uid++ this.subs = [] } .... 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) } for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() } } }
update
https://github.com/wclimb/vue/blob/dev/src/core/observer/watcher.js#L153
update () { /* istanbul ignore else */ if (this.lazy) { this.dirty = true } else if (this.sync) { this.run() } else { queueWatcher(this) } }
实际上大部分情况都会直接执行 queueWatcher
方法
https://github.com/wclimb/vue/blob/dev/src/core/observer/scheduler.js#L98
queueWatcher
export function queueWatcher (watcher: Watcher) { const id = watcher.id if (has[id] == null) { has[id] = true if (!flushing) { queue.push(watcher) } else { // 如果已经刷新,则根据其ID拼接观察者 // 如果已经超过其ID,它将立即运行 let i = queue.length - 1 while (i > index && queue[i].id > watcher.id) { i-- } queue.splice(i + 1, 0, watcher) } // queue the flush if (!waiting) { waiting = true // 开发环境并且不是异步的就直接执行 if (process.env.NODE_ENV !== 'production' && !config.async) { flushSchedulerQueue() return } // 这里其实就是执行了个异步操作,可以了解一些nextTick的实现 // https://github.com/wclimb/vue/blob/dev/src/core/util/env.js#L51 nextTick(flushSchedulerQueue) } } }
https://github.com/wclimb/vue/blob/dev/src/core/observer/scheduler.js#L34
flushSchedulerQueue
function flushSchedulerQueue () { currentFlushTimestamp = getNow() flushing = true let watcher, id //在刷新之前对队列进行排序。 //这样可以确保: //1.组件从父级更新为子级。 (因为父母总是在子级之前创建) //2.组件的用户监视程序先于其呈现监视程序运行(因为用户观察者先于渲染观察者创建) //3.如果在父组件的观察者运行期间破坏了某个组件,可以跳过其观察者 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] // 调用之前执行一下before,目的是可以触发beforeUpdate生命周期 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) { .... break } } } .... }
上面代码主要是对 queue
内的数据根据 watcher
的 id
来排序,确保数据是从父级更新到子级,因为是有先后顺序的,先有父后有子。接着就是批量去更新他们,可以看到先执行了 before
方法,然后调用了 watcher
的 run
方法,我们来看看 run
方法的代码
run
run () { if (this.active) { const value = this.get() if ( value !== this.value || // Deep watchers and watchers on Object/Arrays should fire even // when the value is the same, because the value may // have mutated. isObject(value) || this.deep ) { // set new value const oldValue = this.value this.value = value if (this.user) { try { this.cb.call(this.vm, value, oldValue) } catch (e) { handleError(e, this.vm, `callback for watcher "${this.expression}"`) } } else { this.cb.call(this.vm, value, oldValue) } } } }
我们主要关注 const value = this.get()
这段代码,其实 this.get
我们之前就说过了,初始化的时候它就会调用,实际上执行的是以下代码
updateComponent = () => { vm._update(vm._render(), hydrating) }
自此整个流程已经讲完,以上流程可以通过下面一张图来表达
以上所述就是小编给大家介绍的《Vue源码之双向数据绑定》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Google模式
Eric Schmidt、Jonathan Rosenberg / 李芳齡 / 天下雜誌出版社 / 2014-11-7 / TWD 420.00
上市即登紐約時報暢銷書、Amazon科技經營排行榜TOP1 未上市即售出美、英、德、日、荷等12國版權 Google創辦人Larry Page專文推薦 第一本由Google領導團隊人首度公開的企業內部運作與思維 Google董事會執行主席艾力克.施密特獨家揭露 Google從崛起到稱霸超過10年的管理與工作秘笈, 以及如何吸引21世紀最搶手的人才-智慧創做者(S......一起来看看 《Google模式》 这本书的介绍吧!