Vue2源码解析☞ 2 ☞ 初始化

1,347 阅读8分钟

雨露同行风雨同舟.jpg

活着,最有意义的事情,就是不遗余力地提升自己的认知,拓展自己的认知边界。

搭建源码调试环境一节中,我们已经找到了Vue的构造函数,接下来开始探索Vue初始化的流程。

一个小测试

在精读源码之前,我们可以在一些重要的方法内打印一下日志,熟悉一下这些关键节点的执行顺序。(执行npm run dev后,源码变更后会自动生成新的vue.js,我们的测试html只需要刷新即可)

初始化流程.png

在初始化之前,Vue类的构建过程?

在此过程中,大部分都是原型方法和属性,意味着实例vm可以直接调用

注意事项:

1、以$为前缀的属性和方法,在调用_init原型方法的那一刻即可使用

2、以_为前缀的原型方法和属性,谨慎使用

3、本章旨在了解Vue为我们提供了哪些工具(用到时,深入研究,不必要在开始时花过多精力,后边遇到时会详细说明)

4、类方法和属性在new Vue()前后都可以使用,原型方法和属性只能在new Vue()后使用

定义构造函数

// src/core/instance/index.js
function Vue (options) {
  //形式上很简单,就是一个_init方法
  this._init(options)
}

挂载原型方法:_init

// src/core/instance/init.js
Vue.prototype._init = function (options?: Object) {  }

挂载与state相关的原型属性和原型方法

// src/core/instance/state.js
  const dataDef = {}
  dataDef.get = function () { return this._data }
  const propsDef = {}
  propsDef.get = function () { return this._props }
  Object.defineProperty(Vue.prototype, '$data', dataDef)
  Object.defineProperty(Vue.prototype, '$props', propsDef)
  Vue.prototype.$set = set
  Vue.prototype.$delete = del

  Vue.prototype.$watch = function (
    expOrFn: string | Function,
    cb: any,
    options?: Object
  ): Function {
    //略
  }

挂载与事件相关的原型方法

// src/core/instance/events.js
  const hookRE = /^hook:/
  Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {}

  Vue.prototype.$once = function (event: string, fn: Function): Component {}

  Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {}

  Vue.prototype.$emit = function (event: string): Component {}

挂载与生命周期相关的原型方法

// src/core/instance/lifecycle.js
  Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {}
  Vue.prototype.$forceUpdate = function () {}
  Vue.prototype.$destroy = function () {}

挂载与渲染相关的原型方法

  // install runtime convenience helpers
  installRenderHelpers(Vue.prototype)
  Vue.prototype.$nextTick = function (fn: Function) {}
  Vue.prototype._render = function (): VNode {}

挂载Vue类方法和类属性

// src/core/global-api/index.js
// config
  const configDef = {}
  configDef.get = () => config
  Object.defineProperty(Vue, 'config', configDef)
  Vue.util = {
    warn,
    extend,
    mergeOptions,
    defineReactive
  }

  Vue.set = set
  Vue.delete = del
  Vue.nextTick = nextTick

  // 2.6 explicit observable API
  Vue.observable = <T>(obj: T): T => {
    observe(obj)
    return obj
  }

  Vue.options = Object.create(null)
  ASSET_TYPES.forEach(type => {
    Vue.options[type + 's'] = Object.create(null)
  })

  Vue.options._base = Vue

  extend(Vue.options.components, builtInComponents)

  initUse(Vue)  //挂载类方法use,用于安装插件(特别特别重要)
  initMixin(Vue) //挂载类方法mixin,用于全局混入(在Vue3中被新特性取代)
  initExtend(Vue) //实现Vue.extend函数
  initAssetRegisters(Vue)//实现Vue.component, Vue.directive, Vue.filter函数

挂载平台相关的属性,挂载原型方法$mount

// src/platforms/web/runtime/index.js

// install platform specific utils
Vue.config.mustUseProp = mustUseProp
Vue.config.isReservedTag = isReservedTag
Vue.config.isReservedAttr = isReservedAttr
Vue.config.getTagNamespace = getTagNamespace
Vue.config.isUnknownElement = isUnknownElement

// install platform runtime directives & components
extend(Vue.options.directives, platformDirectives)
extend(Vue.options.components, platformComponents)

// install platform patch function
Vue.prototype.__patch__ = inBrowser ? patch : noop

console.log('挂载$mount方法')
// public mount method
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {}

拓展$mount方法

// src/platforms/web/entry-runtime-with-compiler.js
const mount = Vue.prototype.$mount //保存之前定义的$mount方法
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  //执行拓展内容
  return mount.call(this, el, hydrating) //执行最初定义的$mount方法
}

Vue的初始化过程(很重要哦!!!)

熟悉了初始化过程,就会对不同阶段挂载的实例属性了然于胸,了解Vue是如何处理options中的数据,将初始化流程抽象成一个模型,从此,当你看到用户编写的options选项,都可以在这个模型中演练。

前边我们提到过,Vue的构造函数中只调用了一个_init方法

执行_init方法

// src/core/instance/init.js
Vue.prototype._init = function (options?: Object) {
    const vm: Component = this //此刻,Vue的实例已经创建,只是雏形,但Vue的所有原型方法可以调用
    // a flag to avoid this being observed //(observe会在后面的响应式章节详细说明)
    vm._isVue = true
    // merge options
    if (options && options._isComponent) {// 在后面的Vue组件章节会详细说明
      // 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(//合并options
        resolveConstructorOptions(vm.constructor),//主要处理包含继承关系的实例()
        options || {},
        vm
      )
    }
    // expose real self
    vm._self = vm
    initLifecycle(vm) //初始化实例中与生命周期相关的属性
    initEvents(vm) //处理父组件传递的事件和回调
    initRender(vm) //初始化与渲染相关的实例属性
    callHook(vm, 'beforeCreate') //调用beforeCreate钩子,即执行beforeCreate中的代码(用户编写)
    initInjections(vm) // resolve injections before data/props 获取注入数据
    initState(vm) //初始化props、methods、data、computed、watch
    initProvide(vm) // resolve provide after data/props 提供数据注入
    callHook(vm, 'created') //执行钩子created中的代码(用户编写)
    
    if (vm.$options.el) {//DOM容器(通常是指定id的div)
      vm.$mount(vm.$options.el) //将虚拟DOM转换成真实DOM,然后插入到DOM容器内
    }
  }

initLifecycle:初始化与生命周期相关的实例属性

export function initLifecycle (vm: Component) {
  const options = vm.$options //将实例属性保存在本地,因为复杂数据保存在堆中,每次访问vm的实例属性,都会执行堆查找操作,通常多于两次访问一个变量时,将会保存在本地(即:用空间换时间的思想)
  // locate first non-abstract parent 定位第一个非抽象父元素
  let parent = options.parent
  if (parent && !options.abstract) {
    while (parent.$options.abstract && parent.$parent) {
      parent = parent.$parent
    }
    parent.$children.push(vm)
  }

  vm.$parent = parent //通过上面的定位逻辑,vue组件实例的$parent指的是第一个非抽象父元素(因为抽象元素不会被渲染,即不会转换成真实的dom,由此看来,抽象元素只是作为中间过渡之用,你可以把它看成是工具)
  vm.$root = parent ? parent.$root : vm //所有组件实例的$root都指向根组件实例

  vm.$children = [] //通过$parent,$children的初始化来看,此时的执行顺序:先父组件,后子组件
  vm.$refs = {} //通过初始化赋值,可以推断每一个vue组件的所有ref组合是一个对象
  
  //与生命周期相关的初始化,为什么会涉及父元素,子元素的初始化?
  //个人理解:从生到死,生皆有因(有父元素,特例:根元素),终必有果(有子元素,特例:叶子元素)

  //1、在vue中有三中类型的watcher:render watcher, computed watcher, $watch watcher
  //   render watcher: 与渲染有关的
  //   computed watcher: 用户编写的computed options选项
  //   $watch watcher: 用户编写的$watch或watch选项
  //   后续会有专门的章节专门讨论这三种watcher
  //2、_watcher中保存的是render watcher,在响应式机制章节和异步更新机制章节中会有详细说明
  //3、_watcher的初始化为什么属于生命周期的范畴,将_watcher理解为监护人(同“父子概念”一样理解)
  //4、拓展:在vue1版本中,一个变量对应一个watcher,在vue2中,一个vue组件实例对应一个watcher
  //5、在后面的initState中有一个实例属性_watchers,保存的是当前组件实例中所有的watcher
  vm._watcher = null 
  vm._inactive = null //内置组件keep-alive的实例是否处于唤醒状态(休眠与唤醒的切换,也可以理解为生命周期的一部分)
  vm._directInactive = false (暂未找到直接或间接的解释,后续补充,已备案)
  vm._isMounted = false 
  vm._isDestroyed = false
  vm._isBeingDestroyed = false
}

initEvents(vm):处理父组件传递的事件和回调

// src/core/instance/events.js
export function initEvents (vm: Component) {
  vm._events = Object.create(null)
  vm._hasHookEvent = false //1、vue实例中是否包含钩子事件,比如created,beforeMount等等;2、在$on方法中有对钩子事件的处理;3、在CallHook中会详细说明
  // init parent attached events
  const listeners = vm.$options._parentListeners //只有非根组件中才会有_parentListeners属性,相关内容会在嵌套vue组件章节有详细说明
  if (listeners) {
    updateComponentListeners(vm, listeners) //暂略
  }
}

initRender(vm):初始化与渲染相关的实例属性


export function initRender (vm: Component) {
  vm._vnode = null // the root of the child tree
  vm._staticTrees = null // v-once cached trees 后续补充说明
  const options = vm.$options
  
  
  //与插槽相关的内容,将在自定义组件中详细说明,在此略过
  const parentVnode = vm.$vnode = options._parentVnode // the placeholder node in parent tree
  const renderContext = parentVnode && parentVnode.context
  vm.$slots = resolveSlots(options._renderChildren, renderContext)
  vm.$scopedSlots = emptyObject
  
  
  // bind the createElement fn to this instance
  // so that we get proper render context inside it.
  // args order: tag, data, children, normalizationType, alwaysNormalize
  // internal version is used by render functions compiled from templates
  vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
  // normalization is always applied for the public version, used in
  // user-written render functions. 用户编写的渲染函数,渲染函数返回vnode,即虚拟dom
  vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)

  // $attrs & $listeners are exposed for easier HOC creation.
  // they need to be reactive so that HOCs using them are always updated
  const parentData = parentVnode && parentVnode.data
  //这两个属性会透传到子组件,即在自定义组件中可以访问这两个属性
  defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true)
  defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null, true)
}

CallHook(vm, 'beforeCreate'):执行beforeCreate钩子

执行options中,用户编写在beforeCreate中的代码

export function callHook (vm: Component, hook: string) {
  // #7573 disable dep collection when invoking lifecycle hooks
  pushTarget() //pushTarget的参数为undefined,导致Dep.target为undefined,依赖收集失败
  const handlers = vm.$options[hook] //hook就是beforeCreate, created等等
  const info = `${hook} hook`
  if (handlers) {
    for (let i = 0, j = handlers.length; i < j; i++) {
      invokeWithErrorHandling(handlers[i], vm, null, vm, info) //可以将此编码技巧用在项目中
    }
  }
  if (vm._hasHookEvent) {
    vm.$emit('hook:' + hook) //意味着我们可以手动执行钩子函数(例如:this.$emit('hook: mounted')),不过需要考虑依赖收集的影响(后续会讨论,已备案)
  }
  popTarget() //还原依赖收集的上下文
}

initInjections(vm): resolve injections before data/props 获取注入数据

export function initInjections (vm: Component) {
  const result = resolveInject(vm.$options.inject, vm) //解析用户编写的inject
  if (result) {
    toggleObserving(false) //导致的结果:如果result[key]是对象或数组,则不会递归地进行observe
    //也就是说,下面的defineReactive只会对inject的第一层属性进行响应式处理
    Object.keys(result).forEach(key => {
      defineReactive(vm, key, result[key]) //对属性的访问和设置拦截(此处利用的是ES5的API: defineProperty,Vue3中利用的是ES6的API: Proxy),将在响应式机制章节中详细说明
    })
    toggleObserving(true) //还原上下文
  }
}

initState(vm):初始化props、methods、data、computed、watch(划重点啦!!!)

export function initState (vm: Component) {
  vm._watchers = [] //保存的是当前vue实例中所有的watcher
  const opts = vm.$options
  if (opts.props) initProps(vm, opts.props) //初始化用户编写的props
  if (opts.methods) initMethods(vm, opts.methods) //初始化用户编写的methods
  if (opts.data) {
    initData(vm) //初始化用户编写的data
  } else {
    observe(vm._data = {}, true /* asRootData */) //当无data选项时所执行的默认处理
  }
  if (opts.computed) initComputed(vm, opts.computed) //初始化computed选项
  if (opts.watch && opts.watch !== nativeWatch) {
    // Firefox has a "watch" function on Object.prototype...
    // export const nativeWatch = ({}).watch
    // ({})操作:将字面量转换成Object的实例,因此可以访问watch的原型方法
    initWatch(vm, opts.watch) //初始化watch选项
  }
}

initProps: 初始化props

此处概念比较多,propsData、 props、vm._props、 propsOptions,后续会结合实例来分析其区别,此处只做大概了解。

function initProps (vm: Component, propsOptions: Object) {
  const propsData = vm.$options.propsData || {} //传递给子组件的props中属性对应的值
  const props = vm._props = {} //props中的属性
  // cache prop keys so that future props updates can iterate using Array
  // instead of dynamic object key enumeration.
  const keys = vm.$options._propKeys = []
  const isRoot = !vm.$parent
  // root instance props should be converted
  if (!isRoot) {
    toggleObserving(false) //如果不是根组件,则只对props做shalow reactive(浅层响应式)
  }
  for (const key in propsOptions) {//propsOptions是用户编写在props中的代码
    keys.push(key)
    // validateProp 获取key对应的value,如果使用default默认值时,
    // 对value做深层响应式处理,因为default的值是一个fresh copy(自己要亲自验证哦!!!)
    const value = validateProp(key, propsOptions, propsData, vm) 
    defineReactive(props, key, value) // 此处做的是浅层响应式处理
    // static props are already proxied on the component's prototype
    // during Vue.extend(). We only need to proxy props defined at
    // instantiation here.
    if (!(key in vm)) {//当vue实例上没有这个属性时,会进行代理处理,
    // 这也是为什么可以通过this直接访问props中属性的原因。
      proxy(vm, `_props`, key)
    }
  }
  toggleObserving(true)
}

initMethods:初始化methods

function initMethods (vm: Component, methods: Object) {
  const props = vm.$options.props
  for (const key in methods) {
    if (process.env.NODE_ENV !== 'production') {//开发模式
      if (typeof methods[key] !== 'function') {
        // 意味着methods中的项必须是函数
      }
      if (props && hasOwn(props, key)) {
        // 方法名不能是props中的属性名,由此看来,props的优先级比methods高
      }
      if ((key in vm) && isReserved(key)) {
        // 如果key是vm的属性,同时首字符是'$'或'_',则报错
        // isReserved:判断key的首字符是否是'$'或'_'
      }
    }
    // 此处为methods中的函数绑定上下文,这也是methods中的函数能通过this访问vue实例的原因
    vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm)
  }
}

initData: 初始化data

function initData (vm: Component) {
  let data = vm.$options.data // 用户编写的data选项
  // 如果data是函数,则会执行getData方法
  // 在getData方法中,本质上就一行代码:return data.call(vm, vm),为data函数绑定的上下文
  // 第二个参数告诉我们:data可以接受参数,使用这个参数和this本质上都是vue的实例vm
  //注意事项:1、如果data不是函数,那么我们得到的就是data的引用地址,当这个组件被多次使用时,
  //            一个实例中发生变化,其他实例对应的值也发生变化。
  //            (这也是我们在自定义组件中将data写成函数形式的原因,
  //            因为执行函数后,每次得到的数据地址都不同
  //            (在函数栈帧中,参数的传入,数据的返回,都会))
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : data || {}
  // isPlainObject: Object.prototype.toString.call(obj) === '[object Object]'
  if (!isPlainObject(data)) {
    data = {}
    //报错提示
  }
  // 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)) {
        //如果key在methods中已经使用,则会报错
      }
    }
    if (props && hasOwn(props, key)) {
      //开发模式下,data中使用的key在props中已经使用,则报错
    } else if (!isReserved(key)) {
      //如果key不是vue占用的标识符,则对_data中的属性进行代理,这也是可以用this访问data属性的原因
      proxy(vm, `_data`, key)
    }
  }
  // observe data 在此处对data进行深层响应式处理
  // 通过上述key的校验后,再做响应式处理,可以避免一些徒劳
  observe(data, true /* asRootData */)
}

initComputed:初始化computed 选项


const computedWatcherOptions = { lazy: true }

function initComputed (vm: Component, computed: Object) {
  // $flow-disable-line
  const watchers = vm._computedWatchers = Object.create(null) //挂载_computedWatchers属性
  // computed properties are just getters during SSR
  const isSSR = isServerRendering() //服务端渲染

  for (const key in computed) {
    const userDef = computed[key]
    const getter = typeof userDef === 'function' ? userDef : userDef.get

    if (!isSSR) {
      // create internal watcher for the computed property.
      //为computed中的每一个key创建一个watcher,具体watcher的操作,在响应式机制章节详细说明
      watchers[key] = new Watcher(
        vm,
        getter || noop,
        noop,
        computedWatcherOptions
      )
    }

    // component-defined computed properties are already defined on the
    // component prototype. We only need to define computed properties defined
    // at instantiation here.
    if (!(key in vm)) {
      //会将key对应的watcher缓存在vm的_computedWatchers中,具体详情,后面有专题讨论
      defineComputed(vm, key, userDef)
    } else if (process.env.NODE_ENV !== 'production') {//开发模式检测
      if (key in vm.$data) {
        warn(`The computed property "${key}" is already defined in data.`, vm)
      } else if (vm.$options.props && key in vm.$options.props) {
        warn(`The computed property "${key}" is already defined as a prop.`, vm)
      } else if (vm.$options.methods && key in vm.$options.methods) {
        warn(`The computed property "${key}" is already defined as a method.`, vm)
      }
    }
  }
}

initWatch:初始化watch

createWatcher:本质上执行了vm.$watch(expOrFn, handler, options)

function initWatch (vm: Component, watch: Object) {
  for (const key in watch) {
    const handler = watch[key]
    if (Array.isArray(handler)) {// 意味着一个key可以对应多个处理函数
      for (let i = 0; i < handler.length; i++) {
        createWatcher(vm, key, handler[i])
      }
    } else {
      createWatcher(vm, key, handler)
    }
  }
}

initProvide(vm): 提供数据注入

为什么provide初始化滞后与inject,后续补充

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

CallHook(vm, 'created'): 执行created钩子中的代码

callHook的相关逻辑,参考上面的callHook(vm, 'beforeCreate')

执行挂载

执行$mount扩展

通过下面的代码可知:当用户代码中同时包含render,template,el时,它们的优先级依次为:render、template、el

const mount = Vue.prototype.$mount //保存原有的$mount,先执行拓展部分,然后再执行原有的$mount
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && query(el)
  /* istanbul ignore if */
  if (el === document.body || el === document.documentElement) {
    // 挂载容器el不能是body或documentElement
    return this
  }

  const options = this.$options
  // resolve template/el and convert to render function
  if (!options.render) {//
    let template = options.template
    if (template) {
      if (typeof template === 'string') {
        if (template.charAt(0) === '#') {
          // 通过id获取template
          template = idToTemplate(template)
        }
      } else if (template.nodeType) {
        template = template.innerHTML
      } else {
        //异常情况
        return this
      }
    } else if (el) {
      template = getOuterHTML(el)
    }
    if (template) {
      // 将template转换成render
      const { render, staticRenderFns } = compileToFunctions(template, {
        outputSourceRange: process.env.NODE_ENV !== 'production',
        shouldDecodeNewlines,
        shouldDecodeNewlinesForHref,
        delimiters: options.delimiters,
        comments: options.comments
      }, this)
      options.render = render
      options.staticRenderFns = staticRenderFns
    }
  }
  //执行最初定义的$mount
  return mount.call(this, el, hydrating)
}

$mount方法中,首先获取挂载容器,然后执行mountComponent方法

Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && inBrowser ? query(el) : undefined //el真实存在,则获取真实的DOM元素
  return mountComponent(this, el, hydrating)
}
// src/core/instance/lifecycle.js
export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
  vm.$el = el //$el赋值,用户最早在beforeMount钩子可以访问
  if (!vm.$options.render) {//用户没有编写渲染函数,则进入该分支
    //当用户编写的代码中,既没有render函数,也没有template模板,也没有正确的el,
    // 在$mount拓展阶段就不会生成render,Vue就会提供一个默认的空节点渲染函数,挂载后就什么都不显示
    vm.$options.render = createEmptyVNode
    
  }
  callHook(vm, 'beforeMount') //执行用户编写在beforeMount中的代码

  let updateComponent
  //该函数会在创建watcher过程中执行,要想清晰地看清调用过程,在该函数内部打断点,查看调用堆栈即可
  updateComponent = () => {
    // vm._render():获取组件对应的vnode(虚拟DOM)
    // 将虚拟dom转换成真实DOM
    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
  // 创建一个render watcher,一个vue组件只有一个
  // 创建watcher时,会调用传入的updateComponent,
  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
}

在_update方法中,通过_vnode属性判断是否初次渲染,patch 其实就是patch方法,关于patch的详细逻辑,将在diff算法章节详细说明。

Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
    const vm: Component = this
    const prevEl = vm.$el
    const prevVnode = vm._vnode //_vnode在下面赋值,因此初次渲染时,该字段为空
    const restoreActiveInstance = setActiveInstance(vm)
    vm._vnode = vnode //将将要渲染的虚拟DOM保存起来
    // Vue.prototype.__patch__ is injected in entry points
    // based on the rendering backend used.
    // src/platforms/web/runtime/index.js
    // install platform patch function
    // Vue.prototype.__patch__ = inBrowser ? patch : noop
    if (!prevVnode) {
      // initial render
      vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
    } else {
      // updates
      vm.$el = vm.__patch__(prevVnode, vnode)
    }
    // 切换当前处于激活状态的vue实例
    restoreActiveInstance()
    // update __vue__ reference
    if (prevEl) {// prevEl是打补丁之前的$el,保存该值是因为打补丁后,$el会改变
      // 此操作是为了解除原$el对组件实例的引用
      prevEl.__vue__ = null
    }
    if (vm.$el) {//实现vm与$el的双向引用
      vm.$el.__vue__ = vm
    }
    // if parent is an HOC, update its $el as well
    // 关于$vnode的描述,后续补充
    if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
      vm.$parent.$el = vm.$el
    }
    // updated hook is called by the scheduler to ensure that children are
    // updated in a parent's updated hook.
  }