前文中,曾经剖析了在vuejs源码中是如何定义Vue类,以及如何增加实例属性和静态方法:大数据进阶-读懂vuejs源码1。

Vue实例化时调用_init,本文将深刻该办法外部做了哪些事件及vuejs如何实现数据响应式。

Vue初始化

core/instance/index.js文件中定义了Vue的构造函数:

function Vue (options) {  // 执行_init办法,此办法在initMixin中定义  this._init(options)}

_init办法定义在core/instance/init.js中:

Vue.prototype._init = function (options?: Object) {    // 。。。    // 1. 合并options    if (options && options._isComponent) {        // 此处有重要的事件做。        initInternalComponent(vm, options)    } else {        vm.$options = mergeOptions(            resolveConstructorOptions(vm.constructor),            options || {},            vm        )    }    // 2. 初始化属性    // 初始化$root,$parent,$children    initLifecycle(vm)    // 初始化_events    initEvents(vm)    // 初始化$slots/$scopedSlots/_c/$createElement/$attrs/$listeners    initRender(vm)    // 执行生命周期钩子    callHook(vm, 'beforeCreate')    // 注册inject成员到vue实例上    initInjections(vm) // resolve injections before data/props    // 初始化_props/methods/_data/computed/watch    initState(vm)    // 初始化_provided    initProvide(vm) // resolve provide after data/props    // 执行生命周期钩子    callHook(vm, 'created')    // 3. 调用$mount办法    if (vm.$options.el) {        vm.$mount(vm.$options.el)    }}

在合并options的时候,如果options示意一个组件(_isComponent)则调用了initInternalComponent函数:

export function initInternalComponent(vm: Component, options: InternalComponentOptions) {    // 此处保留组件之间的父子关系,    const parentVnode = options._parentVnode    opts.parent = options.parent    opts._parentVnode = parentVnode    //...}

此办法中设置了组件之间的父子关系,在后续的注册及渲染组件的时候会用到。

initProvide

定义在core/instance/inject.js文件中。

export function initProvide (vm: Component) {  const provide = vm.$options.provide  if (provide) {    vm._provided = typeof provide === 'function'      ? provide.call(vm)      : provide  }}

在下面的代码中能够看出,如果provide是一个函数,那么会调用这个函数,并将this指向vm实例。因为initProvide在_init办法中最初被调用,因而可能拜访到实例的属性。

initInjections

定义在core/instance/inject.js文件中。

export function initInjections (vm: Component) {  const result = resolveInject(vm.$options.inject, vm)  if (result) {    // 遍历result属性,利用Object.defineProperty将其增加到vue实例上    // ...  }}

此办法调用resolveInject办法获取所有inject值。

export function resolveInject(inject: any, vm: Component): ?Object {    if (inject) {        const result = Object.create(null)        const keys = hasSymbol            ? Reflect.ownKeys(inject)            : Object.keys(inject)        for (let i = 0; i < keys.length; i++) {            // ....            const provideKey = inject[key].from            let source = vm            while (source) {                if (source._provided && hasOwn(source._provided, provideKey)) {                    result[key] = source._provided[provideKey]                    break                }                source = source.$parent            }            // ...        }        return result    }}

在resolveInject办法中会从以后实例登程,延着parent始终向上找,直到找到_provided中存在。

总结

此时整个Vue定义和初始化流程能够总结为如下:

数据响应式

vuejs框架的整个数据响应式实现过程比较复杂,代码散落在各个文件中。咱们都晓得,在定义组件的时候,组件会主动将data属性中的数据增加上响应式监听,因而咱们从_init办法中调用initState函数开始。

启动监听

在initState函数中:

export function initState (vm: Component) {  // ...  if (opts.data) {    // 解决data数据    initData(vm)  } else {    observe(vm._data = {}, true /* asRootData */)  }  // ...}

options中的data数据会交由initData办法解决:

function initData(vm: Component) {    // ... 1. 获取data数据,如果data是一个函数,但没有返回值,会提醒谬误。    // ... 2. 遍历data所有的属性,首先判断在props和methods是否同名,而后将其代理到vue实例上。    // 3. 增加响应式数据监听    observe(data, true /* asRootData */)}

增加监听

observe函数

定义在core/observer/index.js文件中:

export function observe (value: any, asRootData: ?boolean): Observer | void {  if (!isObject(value) || value instanceof VNode) {    return  }  let ob: Observer | void  // 通过__ob__属性判断该属性是否增加过响应式监听  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  ) {    // 创立Observer实例,其为响应式的外围    ob = new Observer(value)  }  // 通过vmCount能够判断某个响应式数据是否是根数据,能够了解为data属性返回的对象是根数据,如果data对象的某个属性也是一个对象,那么就不再是根数据。  // vmCount属性后续会被用到  if (asRootData && ob) {    ob.vmCount++  }  return ob}

该办法的外围就是为data数据创立Observer实例ob, ob对象会为data增加getter/setter办法,其能够用来收集依赖并在变动的时候触发dom更新。

Observer类

定义在core/observer/index.js文件中,在其构造函数中,依据传入data的类型(Array/Object),别离进行解决。

Object
constructor(value: any) {    this.value = value    // Observer实例上蕴含dep属性,这个属性后续会有很大作用,有些无奈监听的数据变动能够由此属性实现    this.dep = new Dep()    this.vmCount = 0    // 为data增加__ob__属性    def(value, '__ob__', this)    if (Array.isArray(value)) {        // ... 解决数组    } else {        // 解决对象        this.walk(value)    }}
  • walk

遍历data的所有属性,调用defineReactive函数增加getter/setter。

walk(obj: Object) {    const keys = Object.keys(obj)    for (let i = 0; i < keys.length; i++) {        // 增加数据拦挡        defineReactive(obj, keys[i])    }}
  • defineReactive

数据响应式实现的外围办法,原理是通过Object.defineProperty为data增加getter/setter拦挡,在拦挡中实现依赖收集和触发更新。

export function defineReactive(    obj: Object,    key: string,    val: any,    customSetter?: ?Function,    shallow?: boolean) {    // 1. 创立闭包作用域内的Dep对象,用于收集观察者,当数据发生变化的时候,会触发观察者进行update    const dep = new Dep()    const property = Object.getOwnPropertyDescriptor(obj, key)    if (property && property.configurable === false) {        return    }    // 2. 获取对象形容中原有的get和set办法    const getter = property && property.get    const setter = property && property.set    if ((!getter || setter) && arguments.length === 2) {        val = obj[key]    }    let childOb = !shallow && observe(val)    // 3. 增加getter/setter    Object.defineProperty(obj, key, {        enumerable: true,        configurable: true,        get: function reactiveGetter() {            const value = getter ? getter.call(obj) : val            // 动态属性target存储的是以后观察者。            if (Dep.target) {                dep.depend()                if (childOb) {                    // 将观察者增加到Obsetver实例属性dep中。                    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            }            // ... 一些判断,省略            // 当赋值的时候,如果值为对象,须要为新赋值的对象增加响应式            childOb = !shallow && observe(newVal)            // 调用set就是为属性赋值,赋值阐明有新的变动,所以要触发更新            dep.notify()        }    })}

整个defineReactive有两个中央比拟难以了解:

  1. 通过Dep.target获取依赖

因为这个中央波及到前面的编译局部,所以咱们把这部分逻辑独自拿进去,用一段简短的代码来形容整个过程,如下:

// 模仿Deplet Dep = {}Dep.target = null// 模仿变动数据let data = {    foo: 'foo'}Object.defineProperty(data, 'foo', {    get() {        if (Dep.target) {            console.log(Dep.target)        }    }})// 模仿编译 {{foo}}// 1. 解析到template中须要foo属性的值const key = 'foo'// 2. 在foo属性对应的值渲染到页面之前,为Dep.target赋值Dep.target = () => {    console.log('察看foo的变动')}// 3. 获取foo属性的值,此时会触发get拦挡const value = data[key]// 4. 获取实现后,须要将Dep.target的值从新赋值null,这样下一轮解析的时候,可能存储新的观察者Dep.target = null
  1. 在闭包作用域内曾经蕴含了Dep对象,在set中通过此对象的notify办法触发更新,为什么还须要在get办法中,将依赖增加到Observer对象的实例属性dep中。

其实,这是为了不便在其余手动触发更新,因为defineReactive办法外部的dep对象是闭包作用域,在内部无奈间接拜访,只能通过赋值形式触发。

如果在Observer对象上保留一份,那么就能够通过data.__ob__.dep的形式拜访到,间接手动调用notify办法就能够触发更新,在Vue.set办法外部实现就能够这种触发更新形式。

Array

家喻户晓,Object.defineProperty是无奈监控到通过push,pop等办法扭转数组,此时,vuejs通过另外一种形式实现了数组响应式。该形式批改了数组原生的push,pop等办法,在新定义的办法中,通过调用数组对象的__ob__属性的notify办法,手动触发更新。

Observer构造函数中:

if (Array.isArray(value)) {    if (hasProto) {        // 反对__proto__,那么就通过obj.__proto__的形式批改原型        protoAugment(value, arrayMethods)    } else {        // 不反对,就将新定义的办法遍历增加到数组对象上,这样能够笼罩原型链上的原生办法        copyAugment(value, arrayMethods, arrayKeys)    }    // 遍历数组项,如果某项是对象,那么为该对象增加响应式    this.observeArray(value)}

其中arrayMethods就是从新定义的数组操作方法。

  • arrayMethods

定义在core/Observer/array.js文件中,该文件次要作了两件事件:

  1. 创立新的集成自Array.prototype的原型对象arrayMethods。
const arrayProto = Array.prototypeexport const arrayMethods = Object.create(arrayProto)
  1. 在新的原型对象上,增加自定义办法笼罩原生办法。
// 定义所有会触发更新的办法const methodsToPatch = [    'push',    'pop',    'shift',    'unshift',    'splice',    'sort',    'reverse']methodsToPatch.forEach(function (method) {    // 获取Array中原生的同名办法    const original = arrayProto[method]    // 通过Object.defineProperty为办法调用增加拦挡    def(arrayMethods, method, function mutator(...args) {        // 调用原生办法获取本该失去的后果        const result = original.apply(this, args)        const ob = this.__ob__        let inserted        // push,unshift,splice三个办法会向数组中插入新值,此处依据状况获取新插入的值        switch (method) {            case 'push':            case 'unshift':                inserted = args                break            case 'splice':                inserted = args.slice(2)                break        }        // 如果新插入的值是对象,那么须要为对象增加响应式,解决逻辑和data解决逻辑类似        if (inserted) ob.observeArray(inserted)        // 手动触发更新        ob.dep.notify()        return result    })})

从下面的解决逻辑能够看出,上面的数组操作能够触发自动更新:

// 批改数组项[].push(1)[].pop()[].unshift(1)[].shift()[].splice()// 批改数组项程序[].sort()[].reverse()

而上面的操作不能触发:

// 批改数组项[1, 2][0] = 3[1, 2].length = 0

Dep

在增加数据监听的过程中用到了Dep类,Dep类相当于观察者模式中的指标,用于存储所有的观察者和发生变化时调用观察者的update方进行更新。

export default class Dep {    // 以后须要增加的观察者    static target: ?Watcher;    // id,惟一标识    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)    }    // 调用观察者的addDep办法,将指标增加到每一个观察者中,观察者会调用addSub办法    depend() {        if (Dep.target) {            Dep.target.addDep(this)        }    }    // 将观察者排序,而后顺次调用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)        }        for (let i = 0, l = subs.length; i < l; i++) {            subs[i].update()        }    }}

Watcher

Watcher类是观察者模式中的观察者,当Dep触发变动的时候,会调用外部存储的所有Watcher实例的update办法进行更新操作。

在vuejs中,Watcher可大抵分为三种:Computed Watcher, 用户Watcher(侦听器)和渲染Watcher(触发Dom更新)。

Watcher类蕴含大量的实例成员,在构造函数中,次要逻辑如下:

constructor(    vm: Component,    expOrFn: string | Function,    cb: Function,    options ?: ? Object,    isRenderWatcher ?: boolean) {    // ... 依据参数为实例成员赋值    // 调用get办法    this.value = this.lazy        ? undefined        : this.get()}

在get办法中,获取初始值并将本身增加到Dep.target。

get() {    // 1. 和上面的popTarget绝对应,这里次要是为Dep.target赋值    // 因为存在组件之间的父子关系,所以在pushTarget中还会将以后对象寄存到队列中,不便解决实现子组件后持续解决父组件    pushTarget(this)    let value    const vm = this.vm    try {        // 2. 获取初始值,并触发get监听,Dep会收集该Watcher        value = this.getter.call(vm, vm)    } catch (e) {        if (this.user) {            handleError(e, vm, `getter for watcher "${this.expression}"`)        } else {            throw e        }    } finally {        // 实现deep深度监听        if (this.deep) {            traverse(value)        }        // 3. 将Dep.target值变为null        popTarget()        this.cleanupDeps()    }    return value}
addDep

addDep办法用于将以后Watcher实例增加到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办法,将Watcher实例增加到Dep中            dep.addSub(this)        }    }}
update

update次要解决两种状况:

  1. 如果是用户增加的监听器,在变动的时候会执行run办法。
  2. 如果是渲染Dom时增加的,在变动的时候会执行queueWatcher函数,在queueWatcher函数中,通过队列的形式批量执行更新。
update() {    if (this.lazy) {        this.dirty = true    } else if (this.sync) {        // 用户增加的监听器会执行run办法        this.run()    } else {        // 触发dom更新会执行此办法, 以队列形式执行update更新        queueWatcher(this)    }}
run

run办法次要用于在数据变动后,执行用户传入的回调函数。

run() {    if (this.active) {        // 1. 通过get办法获取变动后的值        const value = this.get()        if (            value !== this.value ||            isObject(value) ||            this.deep        ) {            // 2. 获取初始化时保留的值作为旧值            const oldValue = this.value            this.value = value            if (this.user) {                try {                    // 3. 调用用户定义的回调函数                    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)            }        }    }}

生成渲染Watcher

在查找编译入口那局部讲到了platforms/web/runtime/index.js文件定义了$mount办法,此办法用于首次渲染Dom。

Vue.prototype.$mount = function (  el?: string | Element,  hydrating?: boolean): Component {  el = el && inBrowser ? query(el) : undefined  return mountComponent(this, el, hydrating)}

其外部执行了mountComponent函数。

mountComponent

定义在core/instance/lifecycle.js文件中,该函数次要执行三块内容:

  1. 触发beforeMount,beforeUpdatemounted生命周期钩子函数。
  2. 定义updateComponent办法。
  3. 生成Watcher实例,传入updateComponent办法,此办法会在首次渲染和数据变动的时候被调用。
export function mountComponent(    vm: Component,    el: ?Element,    hydrating?: boolean): Component {    // ... 1. 触发生命周期钩子    // 2. 定义updateComponent办法    let updateComponent    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {        updateComponent = () => {            // ...            vm._update(vnode, hydrating)            // ...        }    } else {        updateComponent = () => {            vm._update(vm._render(), hydrating)        }    }    // 生成watcher实例    new Watcher(vm, updateComponent, noop, {        before() {            if (vm._isMounted && !vm._isDestroyed) {                callHook(vm, 'beforeUpdate')            }        }    }, true /* isRenderWatcher */)    hydrating = false    // ... 触发生命周期钩子    return vm}

_update, _render

_update, _render是Vue的实例办法, _render办法用于依据用户定义的render或者模板生成的render生成虚构Dom。_update办法依据传入的虚构Dom,执行patch,进行Dom比照更新。

总结

至此,响应式解决的整个闭环脉络曾经摸清。