目前就职于新浪微博

0%

Vue源码解析

特别声明:本文未经许可禁止转载

开头

  • 不能免俗,我也要做Vue源码解析了
  • 本文基于Vue 2.5.18-beta.0版本进行分析
  • 源码讲解的第一行附了源码路径,可供查看,我会在我认为重要的地方加注释
  • 如果有疑问,可以留言或者关注我公众号并发送消息
  • 本文通俗易懂,不人云亦云,应该都能看得懂

Vue Compiler:编译Vue模版

  • 先来看一下,开发者开发时的Vue代码结构
    1
    2
    3
    4
    5
    <template>
    <div>
    <span>Hello World</span>
    </div>
    </template>
  • 以上代码,经过Vue compiler处理之后,template会变成
    1
    _c("div", [_c("span", [_vm._v("Hello World")])])
  • 其中,_c就是createElement,定义在
    1
    2
    // vue/src/core/instance/render.js
    vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
  • 看一下Vue compiler入口,可猜出该部分的功能
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // vue/src/compiler/index.js
    export const createCompiler = createCompilerCreator(function baseCompile (
    template: string,
    options: CompilerOptions
    ): CompiledResult {
    const ast = parse(template.trim(), options)
    if (options.optimize !== false) {
    optimize(ast, options)
    }
    const code = generate(ast, options)
    return {
    ast,
    render: code.render,
    staticRenderFns: code.staticRenderFns
    }
    })
  • 输入是模版string,输出是render函数,而这个render函数,将会被Vue-loader使用,将其声明到options中,后面Vue.prototype._render()函数中使用到到options.render(xx)函数,就是由此而来。另外Vue compiler还对某些Vue特性做了处理
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    Vue.prototype._render = function (): VNode {
    const vm: Component = this
    const { render, _parentVnode } = vm.$options // 1. 该render函数就是Vue compiler编译输出的

    if (_parentVnode) {
    vm.$scopedSlots = _parentVnode.data.scopedSlots || emptyObject
    }

    // set parent vnode. this allows render functions to have access
    // to the data on the placeholder node.
    vm.$vnode = _parentVnode
    // render self
    let vnode
    try {
    // 2、生成vnode,供后面做VDOM DIFF时使用
    vnode = render.call(vm._renderProxy, vm.$createElement)
    } catch (e) {

    handleError(e, vm, `render`)
    // return error render result,
    // or previous vnode to prevent render error causing blank component
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== 'production' && vm.$options.renderError) {
    try {
    vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e)
    } catch (e) {
    handleError(e, vm, `renderError`)
    vnode = vm._vnode
    }
    } else {
    vnode = vm._vnode
    }
    }
    // return empty vnode in case the render function errored out
    if (!(vnode instanceof VNode)) {
    if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) {
    warn(
    'Multiple root nodes returned from render function. Render function ' +
    'should return a single root node.',
    vm
    )
    }
    vnode = createEmptyVNode()
    }
    // set parent
    vnode.parent = _parentVnode
    return vnode
    }

Vue Instance第一次渲染(总计有12个重要注释)

  • 一切要从new Vue(options)开始
    1
    2
    3
    4
    5
    6
    var app = new Vue({
    el: '#app',
    data: {
    message: 'Hello Vue!'
    }
    })
  • 其实在new Vue(options)之前,需要先考虑Vue Constructor的初始化
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // vue/src/core/instance/index.js
    function Vue (options) {
    if (process.env.NODE_ENV !== 'production' &&
    !(this instanceof Vue)
    ) {
    warn('Vue is a constructor and should be called with the `new` keyword')
    }
    this._init(options) // 1. new Vue(options)时执行
    // _init方法即Vue.prototype._init
    }
    // 2. 以下方法调用,就是在初始化Vue Constructor,从方法名不难猜出其功能
    initMixin(Vue)
    stateMixin(Vue)
    eventsMixin(Vue)
    lifecycleMixin(Vue)
    renderMixin(Vue)

    export default Vue
  • this._init(options)方法中,初始化Vue Instance,我们来看看_init方法的定义
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    // vue/src/core/instance/init.js
    Vue.prototype._init = function (options?: Object) {
    // 3. 你可能奇怪javascript怎么会有类型声明,这是prop-types在编译时用到的,
    // 编译结束之后,就是纯碎的javascript代码了
    const vm: Component = this
    // a uid
    vm._uid = uid++

    let startTag, endTag
    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    startTag = `vue-perf-start:${vm._uid}`
    endTag = `vue-perf-end:${vm._uid}`
    mark(startTag)
    }

    // a flag to avoid this being observed
    vm._isVue = true
    // merge options
    if (options && options._isComponent) {
    // optimize internal component instantiation
    // since dynamic options merging is pretty slow, and none of the
    // internal component options needs special treatment.
    initInternalComponent(vm, options)
    } else {
    vm.$options = mergeOptions(
    resolveConstructorOptions(vm.constructor),
    options || {},
    vm
    )
    }
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== 'production') {
    initProxy(vm)
    } else {
    vm._renderProxy = vm
    }
    // expose real self
    vm._self = vm
    // 4、下面的以init开头的方法,都是根据options,给vm即this塞必要的属性方便开发时使用
    // 以call开头的方法,都是在触发Vue的声明周期
    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')

    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    vm._name = formatComponentName(vm, false)
    mark(endTag)
    measure(`vue ${vm._name} init`, startTag, endTag)
    }

    if (vm.$options.el) {
    vm.$mount(vm.$options.el) // 5、$mount方法,就是mountComponent方法,定义在 // vue/src/core/instance/lifecycle.js中
    }
    }
  • 执行$mount方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    // vue/src/core/instance/lifecycle.js
    export function mountComponent (
    vm: Component, // 6、vm即this、即Vue Instance
    el: ?Element, // 7、new Vue(options)时传入的el
    hydrating?: boolean
    ): Component {
    vm.$el = el
    if (!vm.$options.render) {
    vm.$options.render = createEmptyVNode
    if (process.env.NODE_ENV !== 'production') {
    /* istanbul ignore if */
    if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
    vm.$options.el || el) {
    warn(
    'You are using the runtime-only build of Vue where the template ' +
    'compiler is not available. Either pre-compile the templates into ' +
    'render functions, or use the compiler-included build.',
    vm
    )
    } else {
    warn(
    'Failed to mount component: template or render function not defined.',
    vm
    )
    }
    }
    }
    callHook(vm, 'beforeMount')

    let updateComponent
    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    updateComponent = () => {
    const name = vm._name
    const id = vm._uid
    const startTag = `vue-perf-start:${id}`
    const endTag = `vue-perf-end:${id}`

    mark(startTag)
    const vnode = vm._render()
    mark(endTag)
    measure(`vue ${name} render`, startTag, endTag)

    mark(startTag)
    vm._update(vnode, hydrating)
    mark(endTag)
    measure(`vue ${name} patch`, startTag, endTag)
    }
    } else {
    // 8、定义updateComponent方法传给Watcher,
    // 使用Watcher去watch options中定义的data,一旦data中的数据发生变动,则触发updateComponent
    updateComponent = () => {
    vm._update(vm._render(), hydrating)
    // 9、_render方法即Vue.prototype._render,定义在 // vue/src/core/instance/render.js,
    // 该方法调用了options中的render方法生成了vnode而vnode是VDOM的基础之一,而options中的render方法,则是在Vue Compile阶段
    // 根据开发者定义的Vue Template生成的
    // 10、_update方法即Vue.prototype._update,定义在 // vue/src/core/instance/lifecycle.js,
    该方法主要是执行patch方法,而patch方法第一个参数是preVnode,第二个参数是vnode。
    }
    }

    // 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

    // 11、new Watcher(xxx)时,将会在Watcher Constructor中调用传入的updateComponent方法,
    因此,当new Watcher(xxx)执行完毕,DOM操作已经执行完成,页面已经渲染出来了
    new Watcher(vm, updateComponent, noop, {
    before () {
    if (vm._isMounted) {
    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') // 12、第一次渲染完成,由此可以得出结论,Vue是以组件为单位进行watch的
    // 那么,如果合理的对Vue代码进行封装,封装成组件,则有可能有效的减少VDOM DIFF时的节点数量
    }
    return vm
    }

Vue VDOM:通过比对VDOM,计算出所需要的真实DOM操作(总计19处注释)

VDOM其实是一个定义,是为了与真实DOM相区分而起的名字,不要被吓到,说白了,其实就是用JavaScript的Object(没错就是Vnode)为节点,构成的一棵树,每个节点上有parent、children以及其他信息,这棵树尽可能的与真实的DOM树相匹配,在数据被更新时,生成新VDOM Tree,与旧的VDOM Tree相比对,通过DIFF算法,比对出,需要怎么样操作DOM,才能正确更新

  • 既然Vnode是基础,先看一下Vnode的数据结构

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    // vue/src/core/vdom/vnode.js
    export default class VNode {
    tag: string | void; // 1、如vue-compoment-keep-alive
    data: VNodeData | void; // 2、patch运行时用到的数据,比如hooks,keepAlive
    children: ?Array<VNode>;
    text: string | void;
    elm: Node | void; // 3、真实的DOM节点
    ns: string | void;
    context: Component | void; // rendered in this component's scope
    key: string | number | void;
    componentOptions: VNodeComponentOptions | void; // 4、组件的options
    componentInstance: Component | void; // component instance // 5、组件new完之后的Vue Instance
    parent: VNode | void; // component placeholder node

    // strictly internal
    raw: boolean; // contains raw HTML? (server only)
    isStatic: boolean; // hoisted static node
    isRootInsert: boolean; // necessary for enter transition check
    isComment: boolean; // empty comment placeholder?
    isCloned: boolean; // is a cloned node?
    isOnce: boolean; // is a v-once node?
    asyncFactory: Function | void; // async component factory function
    asyncMeta: Object | void;
    isAsyncPlaceholder: boolean;
    ssrContext: Object | void;
    fnContext: Component | void; // real context vm for functional nodes
    fnOptions: ?ComponentOptions; // for SSR caching
    devtoolsMeta: ?Object; // used to store functional render context for devtools
    fnScopeId: ?string; // functional scope id support

    constructor (
    tag?: string,
    data?: VNodeData,
    children?: ?Array<VNode>,
    text?: string,
    elm?: Node,
    context?: Component,
    componentOptions?: VNodeComponentOptions,
    asyncFactory?: Function
    ) {
    this.tag = tag
    this.data = data
    this.children = children
    this.text = text
    this.elm = elm
    this.ns = undefined
    this.context = context
    this.fnContext = undefined
    this.fnOptions = undefined
    this.fnScopeId = undefined
    this.key = data && data.key
    this.componentOptions = componentOptions
    this.componentInstance = undefined
    this.parent = undefined
    this.raw = false
    this.isStatic = false
    this.isRootInsert = true
    this.isComment = false
    this.isCloned = false
    this.isOnce = false
    this.asyncFactory = asyncFactory
    this.asyncMeta = undefined
    this.isAsyncPlaceholder = false
    }

    // DEPRECATED: alias for componentInstance for backwards compat.
    /* istanbul ignore next */
    get child (): Component | void {
    return this.componentInstance
    }
    }
  • 先解释一下Patch,Patch是Vue VDOM部分中的一个函数,该函数就是Vue.prototype.__patch__,而该函数其实就是在做DIFF,输入是oldVnode、vnode,输出是DIFF后,需要对真实DOM节点所做对DOM操作,比如createElemennt、insertBefore、appendChild等等,而驱动该函数运行的,是Watcher,而Watcher观察的,是data。因此,开发者只需要改变Vue options中的data,就能完成想要的更新

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    // vue/src/core/vdom/patch.js
    function patch (oldVnode, vnode, hydrating, removeOnly) {
    // 6、输入参数是oldVnode、vnode,就是对oldVnode和vnode做DIFF
    if (isUndef(vnode)) {
    if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
    return
    }

    let isInitialPatch = false
    const insertedVnodeQueue = []

    if (isUndef(oldVnode)) {
    // empty mount (likely as component), create new root element
    isInitialPatch = true
    createElm(vnode, insertedVnodeQueue)
    } else {
    const isRealElement = isDef(oldVnode.nodeType)
    if (!isRealElement && sameVnode(oldVnode, vnode)) {
    // patch existing root node

    // 7、开始做DIFF
    patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)
    } else {
    if (isRealElement) {
    // mounting to a real element
    // check if this is server-rendered content and if we can perform
    // a successful hydration.
    if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {
    oldVnode.removeAttribute(SSR_ATTR)
    hydrating = true
    }
    if (isTrue(hydrating)) {
    if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {
    invokeInsertHook(vnode, insertedVnodeQueue, true)
    return oldVnode
    } else if (process.env.NODE_ENV !== 'production') {
    warn(
    'The client-side rendered virtual DOM tree is not matching ' +
    'server-rendered content. This is likely caused by incorrect ' +
    'HTML markup, for example nesting block-level elements inside ' +
    '<p>, or missing <tbody>. Bailing hydration and performing ' +
    'full client-side render.'
    )
    }
    }
    // either not server-rendered, or hydration failed.
    // create an empty node and replace it
    oldVnode = emptyNodeAt(oldVnode)
    }

    // replacing existing element
    const oldElm = oldVnode.elm
    const parentElm = nodeOps.parentNode(oldElm)

    // create new node
    createElm(
    vnode,
    insertedVnodeQueue,
    // extremely rare edge case: do not insert if old element is in a
    // leaving transition. Only happens when combining transition +
    // keep-alive + HOCs. (#4590)
    oldElm._leaveCb ? null : parentElm,
    nodeOps.nextSibling(oldElm)
    )

    // update parent placeholder node element, recursively
    if (isDef(vnode.parent)) {
    let ancestor = vnode.parent
    const patchable = isPatchable(vnode)
    while (ancestor) {
    for (let i = 0; i < cbs.destroy.length; ++i) {
    cbs.destroy[i](ancestor)
    }
    ancestor.elm = vnode.elm
    if (patchable) {
    for (let i = 0; i < cbs.create.length; ++i) {
    cbs.create[i](emptyNode, ancestor)
    }
    // #6513
    // invoke insert hooks that may have been merged by create hooks.
    // e.g. for directives that uses the "inserted" hook.
    const insert = ancestor.data.hook.insert
    if (insert.merged) {
    // start at index 1 to avoid re-invoking component mounted hook
    for (let i = 1; i < insert.fns.length; i++) {
    insert.fns[i]()
    }
    }
    } else {
    registerRef(ancestor)
    }
    ancestor = ancestor.parent
    }
    }

    // destroy old node
    if (isDef(parentElm)) {
    removeVnodes(parentElm, [oldVnode], 0, 0)
    } else if (isDef(oldVnode.tag)) {
    invokeDestroyHook(oldVnode)
    }
    }
    }

    invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
    return vnode.elm
    }
  • 我们来看一下patchVnode函数。整个VDOM树,每个节点都有parent和children,而patchVnode其实在做的是:比对oldVnode’s children与vnode’s children

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    function patchVnode (
    oldVnode,
    vnode,
    insertedVnodeQueue,
    ownerArray,
    index,
    removeOnly
    ) {
    if (oldVnode === vnode) {
    return
    }

    if (isDef(vnode.elm) && isDef(ownerArray)) {
    // clone reused vnode
    vnode = ownerArray[index] = cloneVNode(vnode)
    }

    const elm = vnode.elm = oldVnode.elm

    if (isTrue(oldVnode.isAsyncPlaceholder)) {
    if (isDef(vnode.asyncFactory.resolved)) {
    hydrate(oldVnode.elm, vnode, insertedVnodeQueue)
    } else {
    vnode.isAsyncPlaceholder = true
    }
    return
    }

    // reuse element for static trees.
    // note we only do this if the vnode is cloned -
    // if the new node is not cloned it means the render functions have been
    // reset by the hot-reload-api and we need to do a proper re-render.
    if (isTrue(vnode.isStatic) &&
    isTrue(oldVnode.isStatic) &&
    vnode.key === oldVnode.key &&
    (isTrue(vnode.isCloned) || isTrue(vnode.isOnce))
    ) {
    vnode.componentInstance = oldVnode.componentInstance
    return
    }

    let i
    const data = vnode.data
    if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
    i(oldVnode, vnode)
    }

    const oldCh = oldVnode.children
    const ch = vnode.children
    if (isDef(data) && isPatchable(vnode)) {
    for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)
    if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode)
    }
    if (isUndef(vnode.text)) {
    // 8、如果children都存在,则比对children
    if (isDef(oldCh) && isDef(ch)) {
    if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)
    } else if (isDef(ch)) { // 9、如果只有vnode上存在children,则这些children都需被add
    if (process.env.NODE_ENV !== 'production') {
    checkDuplicateKeys(ch)
    }
    if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')
    addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
    } else if (isDef(oldCh)) { // 10、如果只有oldVnode上存在children,则这些children都需被删除
    removeVnodes(elm, oldCh, 0, oldCh.length - 1)
    } else if (isDef(oldVnode.text)) {
    nodeOps.setTextContent(elm, '') // 11、针对text 节点做单独处理
    }
    } else if (oldVnode.text !== vnode.text) { // 12、针对text节点做单独处理
    nodeOps.setTextContent(elm, vnode.text)
    }
    if (isDef(data)) {
    if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)
    }
    }
  • 在看updateChildren之前,先看一个函数sameVnode,该函数的用途是,判断节点a和节点b的真实DOM节点是否一致

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    function sameVnode (a, b) {
    return (
    a.key === b.key && ( // 13、这里会先判断key,因此设置一个合适的key的重要性不言而喻。
    // 当前后两次Patch更新时,如果key不变,那么就不会产生不必要的DOM操作
    (
    a.tag === b.tag &&
    a.isComment === b.isComment &&
    isDef(a.data) === isDef(b.data) &&
    !childrenIgnored(a) && !childrenIgnored(b) &&
    sameInputType(a, b)
    ) || (
    isTrue(a.isAsyncPlaceholder) &&
    a.asyncFactory === b.asyncFactory &&
    isUndef(b.asyncFactory.error)
    )
    )
    )
    }
  • 再看一下updateChildren函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    // vue/src/core/vdom/patch.js
    function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
    let oldStartIdx = 0
    let newStartIdx = 0
    let oldEndIdx = oldCh.length - 1
    let oldStartVnode = oldCh[0]
    let oldEndVnode = oldCh[oldEndIdx]
    let newEndIdx = newCh.length - 1
    let newStartVnode = newCh[0]
    let newEndVnode = newCh[newEndIdx]
    let oldKeyToIdx, idxInOld, vnodeToMove, refElm

    // removeOnly is a special flag used only by <transition-group>
    // to ensure removed elements stay in correct relative positions
    // during leaving transitions
    const canMove = !removeOnly

    if (process.env.NODE_ENV !== 'production') {
    checkDuplicateKeys(newCh)
    }
    // 14、假设old children是[A, B, C, D]
    // new children是[B, C, D]
    // 那么,根据下面的循环,DIFF后的DOM操作是 removeChild(A)
    // 其实,该函数的最终目的是:在尽量减少DOM操作的前提下,找出old children中能继续使用的真实DOM节点,
    // 并保证new children中的节点能够正确的更新
    while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
    if (isUndef(oldStartVnode)) {
    oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left
    } else if (isUndef(oldEndVnode)) {
    oldEndVnode = oldCh[--oldEndIdx]
    } else if (sameVnode(oldStartVnode, newStartVnode)) {
    patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
    oldStartVnode = oldCh[++oldStartIdx]
    newStartVnode = newCh[++newStartIdx]
    } else if (sameVnode(oldEndVnode, newEndVnode)) {
    patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
    oldEndVnode = oldCh[--oldEndIdx]
    newEndVnode = newCh[--newEndIdx]
    } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
    patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
    // 15、产生DOM操作insertBefore
    canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))
    oldStartVnode = oldCh[++oldStartIdx]
    newEndVnode = newCh[--newEndIdx]
    } else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
    patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
    canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
    oldEndVnode = oldCh[--oldEndIdx]
    newStartVnode = newCh[++newStartIdx]
    } else {
    if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
    // 16、如果定义了key,则时间复杂度为O(1),如果没有定义key,则时间复杂度为O(n)
    idxInOld = isDef(newStartVnode.key)
    ? oldKeyToIdx[newStartVnode.key]
    : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
    if (isUndef(idxInOld)) { // New element
    // 17、产生DOM操作appendChild or insertBefore
    createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
    } else {
    vnodeToMove = oldCh[idxInOld]
    if (sameVnode(vnodeToMove, newStartVnode)) {
    patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
    oldCh[idxInOld] = undefined
    canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
    } else {
    // same key but different element. treat as new element
    createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
    }
    }
    newStartVnode = newCh[++newStartIdx]
    }
    }
    if (oldStartIdx > oldEndIdx) {
    refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
    // 18、产生DOM操作
    addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
    } else if (newStartIdx > newEndIdx) {
    // 19、产生DOM操作
    removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
    }
    }
  • 总结一下整个过程

    • 在Vue.prototype._init()中,Watcher将会watch options data等关键位置
    • 开发者代码中更新了options data
    • Watcher watch到改变,触发updateComponent
    • 调用options中的render函数,生成新vnode
    • 紧接着调用patch,与oldVnode做对比,输出是:需要对真实DOM所做的操作
    • 更新完成

Vue Watcher:相当于大管家,运行时,触发各处的调用(总计18处注释)

  • 上面多次提到了Watcher,Watcher在Vue中的作用有很多,比如

    • 上面提到的options data,更新options data触发视图更新
    • computed特性,更新options data后,更新computed的属性绑定的视图
    • Vue中手动watch特性
    • Vuex的实现,也是借助于Watcher
  • 整个Watcher其实就是观察者模式,只不过,register是全自动的,不需要开发者关心,我们将重点关注是如何实现全自动register

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    // vue/src/core/observer/watcher.js
    export default class Watcher {
    vm: Component; // 1、Vue实例
    expression: string;
    cb: Function;
    id: number;
    deep: boolean;
    user: boolean;
    lazy: boolean;
    sync: boolean;
    dirty: boolean;
    active: boolean;
    deps: Array<Dep>;
    newDeps: Array<Dep>;
    depIds: SimpleSet;
    newDepIds: SimpleSet;
    before: ?Function;
    getter: Function; // 2、当监听到变动是,需要执行到函数
    value: any;

    constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: ?Object,
    isRenderWatcher?: boolean // 3、mountComponent函数执行时,会new Watcher(xxx)
    ) {
    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 // 4、expOrFn即mountComponent函数中到updateComponent函数
    } else {
    this.getter = parsePath(expOrFn)
    if (!this.getter) {
    this.getter = noop
    process.env.NODE_ENV !== 'production' && warn(
    `Failed watching path: "${expOrFn}" ` +
    'Watcher only accepts simple dot-delimited paths. ' +
    'For full control, use a function instead.',
    vm
    )
    }
    }
    this.value = this.lazy
    ? undefined
    : this.get() // 5、在Watcher构造函数中执行get()函数
    }

    /**
    * Evaluate the getter, and re-collect dependencies.
    */
    get () {
    pushTarget(this) // 6、此处是关键,将会使得 Dep.target === this
    let value
    const vm = this.vm
    try {
    value = this.getter.call(vm, vm)
    // 6、第一次执行updateComponent函数,
    // updateComponent会依次调用Vue.prototype._render()生成VDOM树、
    // Vue.prototype._update()进行patch、patch()、产生DOM操作
    } 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 watching
    if (this.deep) {
    traverse(value)
    }
    popTarget()
    this.cleanupDeps()
    }
    return value
    }

    /**
    * Add a dependency to this directive.
    */
    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) // 7、向Dep实例中添加 this ,
    // 当被defineReactive后的字段(比如data中当字段)被更新,
    // Dep类的notify()函数会被调用,紧接着会调用 this.update()
    }
    }
    }

    /**
    * 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
    }

    /**
    * Subscriber interface.
    * Will be called when a dependency changes.
    */
    update () {
    /* istanbul ignore else */
    if (this.lazy) {
    this.dirty = true
    } else if (this.sync) {
    this.run()
    } else {
    queueWatcher(this) // 8、将Watcher加入队列,
    // 这是Vue异步渲染的原理,
    // 队列中的Watcher将会在下一个tick中被调用this.run() 函数,
    // this.run() 最终会调用 this.getter() 函数,
    // 如果 this 是Vue组件实例对应的Watcher,
    // 那么,就会触发updateComponent被调用
    }
    }

    /**
    * Scheduler job interface.
    * Will be called by the scheduler.
    */
    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)
    }
    }
    }
    }

    /**
    * Evaluate the value of the watcher.
    * This only gets called for lazy watchers.
    */
    evaluate () {
    this.value = this.get()
    this.dirty = false
    }

    /**
    * Depend on all deps collected by this watcher.
    */
    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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    // vue/src/core/observer/dep.js
    export default class Dep {
    static target: ?Watcher; // 9、这是关键变量:Vue用该全局变量,
    // 来保存当前正在watch的Watcher实例是哪个,这是依赖收集的关键
    id: number;
    subs: Array<Watcher>; // 10、该属性表示所有正在watch该Dep实例的Watcher。
    // 一个Dep实例对应了一个data中的一个字段,
    // 当该字段被更新,将会触发Watcher运行this.run()

    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.addDep(this) // 11、该方法会调用Dep类的addSub方法,
    // 最终的结果是,将Dep.target保存的Watcher实例加入到Dep的subs数组中,
    // 后续更新字段触发Dep类的notify时,运行该Watcher实例
    }
    }

    notify () { // 12、当被defineReactive后的字段更新后,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() //13、执行Watcher实例中的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 // 14、该机制奏效的依据是:同一时刻,
    // 只会有一个Watcher在运行,而绝大多数情况下Watcher对应的就是Vue组件实例。
    // Vue的Patch更新机制,是以Vue组件为单位的
    const targetStack = []

    export function pushTarget (target: ?Watcher) {
    targetStack.push(target)
    Dep.target = target
    }

    export function popTarget () {
    targetStack.pop()
    Dep.target = targetStack[targetStack.length - 1]
    }
  • 接下来就是关键方法:defineReactive

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    export function defineReactive (
    obj: Object,
    key: string,
    val: any,
    customSetter?: ?Function,
    shallow?: boolean
    ) {
    const dep = new Dep() // 15、一个字段key,对应着一个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)
    // 16、Vue的关键点,使用了Object.defineProperty,
    // 借助get/set机制,在get时,自动收集依赖,在set时自动触发更新
    Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
    const value = getter ? getter.call(obj) : val
    if (Dep.target) { // 17、如果当前有Watcher正在执行,
    // 说明该字段key需要进行依赖收集,那么,收集依赖
    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
    /* 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() // 18、如果该字段key被更新,运行Watcher
    }
    })
    }

从产品角度进行总结

Vue的成功,有两个亮点可以学习:

  • 优化开发者开发体验,拉拢开发者,具体的表现有
    • 用户只需要写类似html的Vue template模版,入手容易
    • 全自动watch,同时又不降低性能
  • 技术创新
    • 使用DIFF算法进行Patch更新
    • 全自动Watch的设计
    • 跨平台支持,横向扩大开发者队伍
关注公众号:micsaycom