vue源码学习随笔一

前言

隔离在家没办法出去浪,那就挑战一下源码吧。本篇是我学习Vue源码的记录,可能有错漏,有不足,甚至半途而废,但是无论怎么样,既然开始学习了,那就留下一些痕迹。不足,错漏欢迎指正。

简单的介绍下我的研究对象,当前版本2.6.11。为了在编译期尽早发现由类型错误引起的bug,又不影响代码正常运行(不需要运行时动态检查类型)的情况下,Vue用Flow做静态类型检查,所以在阅读源码前最好先了解一下关于Flow的相关知识。Flow与TypeScript功能基本一致,熟悉了解TS的大致可以忽略。至于为什么选择Flow而不是TS,大致是由于作者重构时,Babel 和 ESLint 都有对应的Flow插件以支持语法,可以完全沿用现有的构建配置,改动成本较小。

Vue源码目录

Vue源码目录

阅读一个项目源码,尤其是Vue这类项目,我们很容易就不知道从哪里入手。既然这样,对于NPM托管的项目,都会有一个package.json文件,那我们就从它开始,这或许也是一种杀熟吧。

项目通常会配置script字段作为 NPM 的执行脚本,那我们就来看一看这部分吧。

//代码有点长,我们先截取一部分
"scripts": {
    "dev": "rollup -w -c scripts/config.js --environment TARGET:web-full-dev",
    "dev:cjs": "rollup -w -c scripts/config.js --environment TARGET:web-runtime-cjs-dev",
    "dev:esm": "rollup -w -c scripts/config.js --environment TARGET:web-runtime-esm",
    "dev:test": "karma start test/unit/karma.dev.config.js",
    "dev:ssr": "rollup -w -c scripts/config.js --environment TARGET:web-server-renderer",
}

由图可知,当我们运行npm run dev的时候,会执行scripts里面"dev",翻译一下就是用rollup打包JS模块,-w是watch,-c指定配置文件scripts/config.js,将TARGET设置为web-full-dev,那我们就看下TARGET在config.js文件中是如何设置的。

const builds = {
  // Runtime+compiler development build (Browser)
  'web-full-dev': {
    entry: resolve('web/entry-runtime-with-compiler.js'),
    dest: resolve('dist/vue.js'),
    format: 'umd',
    env: 'development',
    alias: { he: './entity-decoder' },
    banner
  }
}

从上图中,我们可以看出文件的入口是'web/entry-runtime-with-compiler.js'文件,然而,在我们寻找这个文件的时候却发现找不到相关目录。怎么办!怎么办!!还能怎么办,继续看源码发现:entry: resolve('web/entry-runtime-with-compiler.js'),顺藤摸瓜研究resolve方法。(resolve:scripts/config.js)

const aliases = require('./alias')
const resolve = p => {
  const base = p.split('/')[0]
  if (aliases[base]) {
    return path.resolve(aliases[base], p.slice(base.length + 1))
  } else {
    return path.resolve(__dirname, '../', p)
  }
}

从resolve方法中,我们发现并打开了alias.js

const path = require('path')
const resolve = p => path.resolve(__dirname, '../', p)
module.exports = {
  vue: resolve('src/platforms/web/entry-runtime-with-compiler'),
  compiler: resolve('src/compiler'),
  core: resolve('src/core'),
  shared: resolve('src/shared'),
  web: resolve('src/platforms/web'),
  weex: resolve('src/platforms/weex'),
  server: resolve('src/server'),
  sfc: resolve('src/sfc')
}

在这里,我们找到了web的路径,查看entry-runtime-with-compiler.js,我们终于找到了Vue的路径。

import Vue from './runtime/index'

查看文件,发现并不是我们所想的那样,但是我们在这个文件中发现了另一个Vue的引入

import Vue from 'core/index'

我们继续查看core/index.js

import Vue from './instance/index' //引入Vue实例
import { initGlobalAPI } from './global-api/index'
import { isServerRendering } from 'core/util/env'
import { FunctionalRenderContext } from 'core/vdom/create-functional-component'

initGlobalAPI(Vue)  //初始化全局接口

/*   以下主要是给Vue添加属性或方法的
*    Vue.prototype.$isServer,Vue.prototype.$ssrContext,Vue.FunctionalRenderContext
*    用ES5的Object.defineProperty()定义只读的(无set)且不可枚举(enumurable默认false)的属性或方法
*/
Object.defineProperty(Vue.prototype, '$isServer', {
//$isServer属性代理了core/util/env中的isServerRendering
  get: isServerRendering
})

Object.defineProperty(Vue.prototype, '$ssrContext', {
  get () {
    return this.$vnode && this.$vnode.ssrContext
  }
})

Object.defineProperty(Vue, 'FunctionalRenderContext', {
  value: FunctionalRenderContext
})
//Vue版本
Vue.version = '__VERSION__'
//导出Vue
export default Vue

在这里,我们终于看到了期待已久的Vue核心代码。这篇js中最重要的是:import Vue from './instance/index' (引入Vue实例)和initGlobalAPI(Vue)  (初始化全局接口)。下面我们也将着重描述这两点,这也将是本篇的重点。

1、引入Vue实例:src/core/instance/index

import { initMixin } from './init'
//构造函数,当new Vue(options) 会自动执行这个函数
function Vue (options) {
//如果不是生产环境,且不是通过关键字new来创建对象,就在控制台打印一个warning
//由此可知Vue只能通过 new 关键字初始化,然后调用 this._init 方法
  if (process.env.NODE_ENV !== 'production' &&
    !(this instanceof Vue)
  ) {
    warn('Vue is a constructor and should be called with the `new` keyword')
  }
//调用_init()方法
  this._init(options)
}
//方法实例化
initMixin(Vue)// 在Vue的原型上添加了_init方法。在执行new Vue()的时候,this._init(options)被执行
stateMixin(Vue)// 在vue的原型上定义了属性: $data、$props,方法:$set、$delete、$watch
eventsMixin(Vue)// 在原型上添加了四个方法: $on $once $off $emit
lifecycleMixin(Vue)// 在Vue.prototye上添加了三个方法:_update $forceUpdate $destory
renderMixin(Vue)// 在原型上添加了方法:$nextTick _render _o _n _s _l _t _q _i _m _f _k _b _v _e _u _g _d _p
//导出Vue
export default Vue
  • this._init()方法:src/core/instance/init.js

import ...

let uid = 0

export function initMixin (Vue: Class<Component>) {
  Vue.prototype._init = function (options?: Object) {
    const vm: Component = this
    // a uid
    vm._uid = uid++

    let startTag, endTag
    //if 语句块,在istanbul.js计算覆盖率的时候会被忽略
    /* 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)
    }

    //避免被观察到标志
    vm._isVue = true
    // merge options
    if (options && options._isComponent) {
      // 优化内部组件实例化
      // 因为动态选项合并非常慢,而且没有内部组件选项需要特殊处理
      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
   //初始化生命周期、事件中心、渲染等等
    initLifecycle(vm)
    initEvents(vm)
    initRender(vm)
    callHook(vm, 'beforeCreate')
    initInjections(vm) // resolve injections before data/props
    initState(vm) //对props、methods、data、computed和wathcer等属性做了初始化操作
    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)
    }
//检测是否有el属性,如果有,则调用vm.$mount方法挂载vm,挂载的目标就是把模板渲染成最终的DOM
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}
  • $mount:src/platform/web/entry-runtime-with-compiler.js

import Vue from './runtime/index'
//mount缓存了原型上的$mount方法
const mount = Vue.prototype.$mount
//重新定义$mount方法
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && query(el)
//Vue 不能挂载在body、documentElement这样的根节点上
  /* istanbul ignore if */
  if (el === document.body || el === document.documentElement) {
    process.env.NODE_ENV !== 'production' && warn(
      `Do not mount Vue to <html> or <body> - mount to normal elements instead.`
    )
    return this
  }

  const options = this.$options
  //如果没有render方法,则会把el或者template字符串转换成render方法
  if (!options.render) {
    let template = options.template
    if (template) {
      if (typeof template === 'string') {
        if (template.charAt(0) === '#') {
          template = idToTemplate(template)
          /* istanbul ignore if */
          if (process.env.NODE_ENV !== 'production' && !template) {
            warn(
              `Template element not found or is empty: ${options.template}`,
              this
            )
          }
        }
      } else if (template.nodeType) {
        template = template.innerHTML
      } else {
        if (process.env.NODE_ENV !== 'production') {
          warn('invalid template option:' + template, this)
        }
        return this
      }
    } else if (el) {
      template = getOuterHTML(el)
    }
    if (template) {
      /* istanbul ignore if */
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        mark('compile')
      }
    //调用compileToFunctions将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

      /* istanbul ignore if */
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        mark('compile end')
        measure(`vue ${this._name} compile`, 'compile', 'compile end')
      }
    }
  }
//调用缓存了原型上$mount方法的mount挂载
  return mount.call(this, el, hydrating)
}

备注:在 Vue 2.0 版本中,所有Vue组件的渲染最终都需要render方法

  • mount缓存的原型上的$mount方法:src/platform/web/runtime/index.js

import { mountComponent } from 'core/instance/lifecycle'
Vue.prototype.$mount = function (
  el?: string | Element, //el:挂载的元素,可以是字符串,也可以是DOM对像
  hydrating?: boolean   //hydrating:服务端渲染相关,在浏览器环境下不需要传第二个参数
): Component {
//el是字符串且在浏览器环境下会调用query()方法转换成DOM对象
  el = el && inBrowser ? query(el) : undefined
//调用mountComponent方法
  return mountComponent(this, el, hydrating)
}
  • mountComponent:src/core/instance/lifecycle.js

function mountComponent (
  vm: Component,
  el: ?Element,
  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)
    //调用vm._render方法生成虚拟Node
      const vnode = vm._render()
      mark(endTag)
      measure(`vue ${name} render`, startTag, endTag)

      mark(startTag)
    //调用vm._update更新DOM
      vm._update(vnode, hydrating)
      mark(endTag)
      measure(`vue ${name} patch`, startTag, endTag)
    }
  } else {
    updateComponent = () => {
      vm._update(vm._render(), hydrating)
    }
  }

  //实例化Watcher,在它的回调函数中会调用updateComponent()方法
 //Watcher在初始化的时候会执行回调函数,或者是当vm实例监测到数据发生变化的时候执行回调函数
  new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true /* isRenderWatcher */)
  hydrating = false

  //vm.$vnode:Vue实例的父虚拟Node,当它为Null时,节点是根节点
  if (vm.$vnode == null) {
    //vm._isMounted为true,表示这个实例已经挂载
    vm._isMounted = true
    //执行mounted钩子函数
    callHook(vm, 'mounted')
  }
  return vm
}
  • _render:src/core/instance/render.js

Vue.prototype._render = function (): VNode {
    const vm: Component = this
    const { render, _parentVnode } = vm.$options

    if (_parentVnode) {
      vm.$scopedSlots = normalizeScopedSlots(
        _parentVnode.data.scopedSlots,
        vm.$slots,
        vm.$scopedSlots
      )
    }

    // 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 {
      // There's no need to maintain a stack because all render fns are called
      // separately from one another. Nested component's render fns are called
      // when parent component is patched.
      currentRenderingInstance = vm
      //调用render()方法
      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
      }
    } finally {
      currentRenderingInstance = null
    }
    // if the returned array contains only a single node, allow it
    if (Array.isArray(vnode) && vnode.length === 1) {
      vnode = vnode[0]
    }
    // 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
  }

render()函数中的可以看出createElement方法就是vm.$createElement方法

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

  /* istanbul ignore else */
  if (process.env.NODE_ENV !== 'production') {
    defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, () => {
      !isUpdatingChildComponent && warn(`$attrs is readonly.`, vm)
    }, true)
    defineReactive(vm, '$listeners', options._parentListeners || emptyObject, () => {
      !isUpdatingChildComponent && warn(`$listeners is readonly.`, vm)
    }, true)
  } else {
    defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true)
    defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null, true)
  }
}

由此可以看出:vm._render()是通过执行createElement()方法,返回vnode

  • createElement:src/core/vdom/create-elemenet.js

export function createElement (
  context: Component,
  tag: any,
  data: any,
  children: any,
  normalizationType: any,
  alwaysNormalize: boolean
): VNode | Array<VNode> {
  if (Array.isArray(data) || isPrimitive(data)) {
    normalizationType = children
    children = data
    data = undefined
  }
  if (isTrue(alwaysNormalize)) {
    normalizationType = ALWAYS_NORMALIZE
  }
//createElement()方法实际上是对_createElement()方法的封装,它允许传入的参数更加灵活。最后调用_createElement()
  return _createElement(context, tag, data, children, normalizationType)
}

_createElement

export function _createElement (
  context: Component, //VNode 的上下文环境
  tag?: string | Class<Component> | Function | Object, //标签
  data?: VNodeData, //VNode 的数据
  children?: any, //当前 VNode 的子节点
  normalizationType?: number //子节点规范的类型
): VNode | Array<VNode> {
  if (isDef(data) && isDef((data: any).__ob__)) {
    process.env.NODE_ENV !== 'production' && warn(
      `Avoid using observed data object as vnode data: ${JSON.stringify(data)}\n` +
      'Always create fresh vnode data objects in each render!',
      context
    )
    return createEmptyVNode()
  }
  // object syntax in v-bind
  if (isDef(data) && isDef(data.is)) {
    tag = data.is
  }
  if (!tag) {
    // in case of component :is set to falsy value
    return createEmptyVNode()
  }
  // warn against non-primitive key
  if (process.env.NODE_ENV !== 'production' &&
    isDef(data) && isDef(data.key) && !isPrimitive(data.key)
  ) {
    if (!__WEEX__ || !('@binding' in data.key)) {
      warn(
        'Avoid using non-primitive value as key, ' +
        'use string/number value instead.',
        context
      )
    }
  }
  // support single function children as default scoped slot
  if (Array.isArray(children) &&
    typeof children[0] === 'function'
  ) {
    data = data || {}
    data.scopedSlots = { default: children[0] }
    children.length = 0
  }
//根据normalizationType的类型,将children规范成不同的VNode类型。
  if (normalizationType === ALWAYS_NORMALIZE) {
    children = normalizeChildren(children)
  } else if (normalizationType === SIMPLE_NORMALIZE) {
    children = simpleNormalizeChildren(children)
  }
//创建VNode 
  let vnode, ns
//如果tag是string类型
  if (typeof tag === 'string') {
    let Ctor
    ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
    //如果是内置的一些节点,则直接创建一个普通VNode
    if (config.isReservedTag(tag)) {
      // platform built-in elements
      if (process.env.NODE_ENV !== 'production' && isDef(data) && isDef(data.nativeOn)) {
        warn(
          `The .native modifier for v-on is only valid on components but it was used on <${tag}>.`,
          context
        )
      }
      vnode = new VNode(
        config.parsePlatformTagName(tag), data, children,
        undefined, undefined, context
      )
    } else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) { //如是已注册的组件名,则通过createComponent()创建组件类型的VNode
      // component
      vnode = createComponent(Ctor, data, context, children, tag)
    } else {
      //创建未知的VNode标签
      // unknown or unlisted namespaced elements
      // check at runtime because it may get assigned a namespace when its
      // parent normalizes children
      vnode = new VNode(
        tag, data, children,
        undefined, undefined, context
      )
    }
  } else {
//如果tag是Component类型,则直接调用createComponent创建组件类型的VNode节点
    // direct component options / constructor
    vnode = createComponent(tag, data, context, children)
  }
  if (Array.isArray(vnode)) {
    return vnode
  } else if (isDef(vnode)) {
    if (isDef(ns)) applyNS(vnode, ns)
    if (isDef(data)) registerDeepBindings(data)
    return vnode
  } else {
    return createEmptyVNode()
  }
}
  • _update:src/core/instance/lifecycle.js

 Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
    const vm: Component = this
    const prevEl = vm.$el
    const prevVnode = vm._vnode
    const restoreActiveInstance = setActiveInstance(vm)
    vm._vnode = vnode
    // Vue.prototype.__patch__ is injected in entry points
    // based on the rendering backend used.
    if (!prevVnode) {
      // 首次渲染
      vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
    } else {
      // 更新渲染
      vm.$el = vm.__patch__(prevVnode, vnode)
    }
    restoreActiveInstance()
    // 更新 __vue__ 
    if (prevEl) {
      prevEl.__vue__ = null
    }
    if (vm.$el) {
      vm.$el.__vue__ = vm
    }
    // if parent is an HOC, update its $el as well
    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.
  }

由上可以看出_update方法的核心就是调用 vm.__patch__ 方法。这个方法在不同平台上的定义是不同的,此处我们取web平台上的定义,它在 src/platforms/web/runtime/index.js 

/*
*    判断当前环境是否在浏览器环境中,是否存在Window对象
*    如果是在server环境下,就是一个空函数,因为在服务端渲染中,没有真实浏览器的DOM环境,所以不需要把VNode最终转换成DOM
*    这样做是为了方便跨平台的处理
*/
Vue.prototype.__patch__ = inBrowser ? patch : noop

由上可知,在浏览器环境下__patch__调用了patch方法。path:src/platforms/web/runtime/patch.js

//path=createPatchFunction()方法的返回值
//nodeOps:封装了一系列DOM操作的方法
//modules:定义了一些模块的钩子函数的实现
export const patch: Function = createPatchFunction({ nodeOps, modules })

createPatchFunction:src/core/vdom/patch.js

const hooks = ['create', 'activate', 'update', 'remove', 'destroy']

export function createPatchFunction (backend) {
  let i, j
  const cbs = {}

  const { modules, nodeOps } = backend

  for (i = 0; i < hooks.length; ++i) {
    cbs[hooks[i]] = []
    for (j = 0; j < modules.length; ++j) {
      if (isDef(modules[j][hooks[i]])) {
        cbs[hooks[i]].push(modules[j][hooks[i]])
      }
    }
  }

  // Vue定义了一系列的辅助方法...
 //返回patch方法
  return function patch (oldVnode, vnode, hydrating, removeOnly) {
/*
*    oldVnode:旧的VNode节点,可以不存在或是一个DOM对象
*    vnode:执行_render后返回的VNode的节点
*    hydrating:是否服务端渲染,非服务端渲染情况下为false
*    removeOnly:给transition-group用,非服务端渲染情况下为false
*/
    if (isUndef(vnode)) {//当前VNode未定义、老的VNode定义了,调用销毁钩子
      if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
      return
    }

    let isInitialPatch = false
    const insertedVnodeQueue = []

    if (isUndef(oldVnode)) {//老的VNode未定义,初始化。
      // empty mount (likely as component), create new root element
      isInitialPatch = true
      createElm(vnode, insertedVnodeQueue)
    } else {//当前VNode和老VNode都定义了,执行更新操作
      const isRealElement = isDef(oldVnode.nodeType)
      if (!isRealElement && sameVnode(oldVnode, vnode)) {
        // 修改已有根节点 patch existing root node
        patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly)
      } else {
        //已有真实DOM元素,处理oldVnode
        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是服务端渲染的元素,hydrating记为true
            oldVnode.removeAttribute(SSR_ATTR)
            hydrating = true
          }
          if (isTrue(hydrating)) {
            if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {
              //调用insert钩子函数
              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.'
              )
            }
          }
          //如果不是服务端渲染或者合并到真实DOM失败,则创建一个空的oldVnode节点替换它
          oldVnode = emptyNodeAt(oldVnode)
        }
        //替换已有元素
        const oldElm = oldVnode.elm
        const parentElm = nodeOps.parentNode(oldElm)

        // 创建新节点
        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)
        )

        // 递归更新父级占位节点元素
        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
          }
        }

        //销毁旧节点
        if (isDef(parentElm)) {
          removeVnodes(parentElm, [oldVnode], 0, 0)
        } else if (isDef(oldVnode.tag)) {
          invokeDestroyHook(oldVnode)
        }
      }
    }
    //调用insert钩子
    invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
    return vnode.elm
  }
}

2、initGlobalAPI:初始化全局接口

//主要作用就是在Vue上扩展一些全局定义的方法,在Vue的官方文档中的关于全局API的内容都在这
import ...
export function initGlobalAPI (Vue: GlobalAPI) {
  const configDef = {}
  configDef.get = () => config
//在非生产环境下修改了配置文件config里面的内容会提示警告
  if (process.env.NODE_ENV !== 'production') {
    configDef.set = () => {
      warn(
        'Do not replace the Vue.config object, set individual fields instead.'
      )
    }
  }
//定义config 属性, 监听变化
  Object.defineProperty(Vue, 'config', configDef)
//Vue.util暴露的方法最好不要依赖,因为它可能经常会发生变化,是不稳定的。
  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)
//给vue 创建 ASSET_TYPES 的 空对象
  ASSET_TYPES.forEach(type => {
    Vue.options[type + 's'] = Object.create(null)
  })

  // this is used to identify the "base" constructor to extend all plain-object
  // components with in Weex's multi-instance scenarios.
  Vue.options._base = Vue

  extend(Vue.options.components, builtInComponents)
//Vue的静态方法
  initUse(Vue) //Vue.use()
  initMixin(Vue)
  initExtend(Vue)
  initAssetRegisters(Vue)
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值