Vue2源码梳理:在 import Vue 时干了啥

发布时间:2023年12月20日

分析导入入口

  • 在Web应用的 Vue.js 构建过程的 Runtime + Compiler 版本下,我们重点关注这个版本
  • 它的入口是 src/platforms/web/entry-runtime-with-compiler.js

1 ) entry-runtime-with-compiler.js 入口文件

  • 现在分析一下在 import Vue 的时候,都执行了哪些事情

  • entry-runtime-with-compiler.js

    /* @flow */
    
    import config from 'core/config'
    import { warn, cached } from 'core/util/index'
    import { mark, measure } from 'core/util/perf'
    
    import Vue from './runtime/index'
    import { query } from './util/index'
    import { compileToFunctions } from './compiler/index'
    import { shouldDecodeNewlines, shouldDecodeNewlinesForHref } from './util/compat'
    
    const idToTemplate = cached(id => {
      const el = query(id)
      return el && el.innerHTML
    })
    
    const mount = Vue.prototype.$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) {
        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
      // 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) === '#') {
              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')
          }
    
          const { render, staticRenderFns } = compileToFunctions(template, {
            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')
          }
        }
      }
      return mount.call(this, el, hydrating)
    }
    
    /**
     * Get outerHTML of elements, taking care
     * of SVG elements in IE as well.
     */
    function getOuterHTML (el: Element): string {
      if (el.outerHTML) {
        return el.outerHTML
      } else {
        const container = document.createElement('div')
        container.appendChild(el.cloneNode(true))
        return container.innerHTML
      }
    }
    
    Vue.compile = compileToFunctions
    
    export default Vue
    
  • 这里可以看到 export default Vue 最终导出 Vue 对象

  • 而一开始的 Vue 的来源:import Vue from './runtime/index'

  • 之后,在其原型上挂载 $mount 方法

2 ) runtime/index 文件

  • 我们看下 runtime/index 文件中定义的Vue, 具体位置: src/platforms/web/runtime/index.js
    import Vue from 'core/index'
    import config from 'core/config'
    import { extend, noop } from 'shared/util'
    import { mountComponent } from 'core/instance/lifecycle'
    import { devtools, inBrowser, isChrome } from 'core/util/index'
    
    import {
      query,
      mustUseProp,
      isReservedTag,
      isReservedAttr,
      getTagNamespace,
      isUnknownElement
    } from 'web/util/index'
    
    import { patch } from './patch'
    import platformDirectives from './directives/index'
    import platformComponents from './components/index'
    
    // 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
    
    // public mount method
    Vue.prototype.$mount = function (
      el?: string | Element,
      hydrating?: boolean
    ): Component {
      el = el && inBrowser ? query(el) : undefined
      return mountComponent(this, el, hydrating)
    }
    
    // ...
    
    export default Vue
    
    • 它最终也是 export default Vue, 而这里的 Vue 是从 core/index 而来
    • import 之后,它又定义了一些全局配置,挂载在 Vue.config 之上
    • 在 Vue 原型上又挂载了 __patch__ 方法 和 $mount 方法等

3 ) core/index 文件

  • 我们再进入 core/index,具体位置: src/core/index.js
    import Vue from './instance/index'
    import { initGlobalAPI } from './global-api/index'
    import { isServerRendering } from 'core/util/env'
    import { FunctionalRenderContext } from 'core/vdom/create-functional-component'
    
    initGlobalAPI(Vue)
    
    Object.defineProperty(Vue.prototype, '$isServer', {
      get: isServerRendering
    })
    
    Object.defineProperty(Vue.prototype, '$ssrContext', {
      get () {
        /* istanbul ignore next */
        return this.$vnode && this.$vnode.ssrContext
      }
    })
    
    // expose FunctionalRenderContext for ssr runtime helper installation
    Object.defineProperty(Vue, 'FunctionalRenderContext', {
      value: FunctionalRenderContext
    })
    
    Vue.version = '__VERSION__'
    
    export default Vue
    
    • 这里的 Vue 一开始 也是从其他地方导入而来的 import Vue from './instance/index'
    • 同时,initGlobalAPI(Vue) 这里初始化了一些 全局的 API
      • 这里定义了 Vue.config, 这里是全局config
      • 这里的 config 定义对应的在vue官网上相关的API文档可供参考
      • 还定义了 Vue.util 对象,里面有对应的方法,这里没有写在公共文档上
        • 这里不建议外部使用,因为里面的方法可能不稳定
      • 同时,这里是给 Vue 这个对象本身扩展全局的静态方法
        • 定义了 Vue.set, Vue.delete, Vue.nextTick, Vue.options 方法
        • Vue.options 用于合并方法,里面引用了 ASSET_TYPES
        • ASSET_TYPES 中又定义了 component, directive, filter 三个全局方法的枚举
      • Vue.options 上又挂载了 _base Vue.options._base = Vue
      • extend(Vue.options.components, builtInComponents) 这里的 builtInComponents 是内置组件
        • 里面只有 keep-alive 组件
      • 之后又调用了一系列的 init 方法
        • initUse(Vue) 创建了 vue.use 的全局方法
        • initMixin(Vue) 定义了一个全局的 mixin 方法
        • initExtend(Vue) 定义了 Vue.extend 方法
        • initAssetRegisters(Vue) 定义了上面ASSET_TYPES中枚举的全局 component, directive, filter 方法
      • 经过一些列初始化,我们才能在业务代码中使用这些代码
      • 可以进入 global-api/index 文件中查看,这里不再赘述, 参考 src/core/global-api/index.js
        export function initGlobalAPI (Vue: GlobalAPI) {
          // config
          const configDef = {}
          configDef.get = () => config
          if (process.env.NODE_ENV !== 'production') {
            configDef.set = () => {
              warn(
                'Do not replace the Vue.config object, set individual fields instead.'
              )
            }
          }
          Object.defineProperty(Vue, 'config', configDef)
        
          // exposed util methods.
          // NOTE: these are not considered part of the public API - avoid relying on
          // them unless you are aware of the risk.
          Vue.util = {
            warn,
            extend,
            mergeOptions,
            defineReactive
          }
        
          Vue.set = set
          Vue.delete = del
          Vue.nextTick = nextTick
        
          Vue.options = Object.create(null)
          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)
        
          initUse(Vue)
          initMixin(Vue)
          initExtend(Vue)
          initAssetRegisters(Vue)
        }
        

4)instance/index 文件

  • 再进入 instance/index 文件
    import { initMixin } from './init'
    import { stateMixin } from './state'
    import { renderMixin } from './render'
    import { eventsMixin } from './events'
    import { lifecycleMixin } from './lifecycle'
    import { warn } from '../util/index'
    
    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)
    }
    
    initMixin(Vue)
    stateMixin(Vue)
    eventsMixin(Vue)
    lifecycleMixin(Vue)
    renderMixin(Vue)
    
    export default Vue
    
    • 在这里,就找到了 Vue 对象真正的定义的位置
    • 最上面,进行环境的判断,并且限制了 必须通过 new 来实例化
    • 之后执行 _init() 方法
    • 之后,调用了 一系列的 Minxin 的方法
      • initMixin 源码中,在 Vue 的原型上挂了 _init 方法
      • stateMixin 源码中,也是在 Vue中挂载了一些方法,比如: $set, $delete, $watch
      • … 其他都类似
      • 也就是说,每个 Minxin 就是在原型上汇入一些方法
    • 这里通过 function 的方式来声明 Vue, 比用 Class 好处在可以方便拆分方法的挂载
      • 拆分到不同文件下的好处是,方便代码的管理,有利于代码的维护
    • 这是非常值得学习的代码重构方案

5 )总结

  • 所以,到现在我们知道 Vue 本身就是一个基于函数实现的类,类上挂载了很多方法和属性
  • 对于 Vue 的初始化过程,总结出两个步骤
    • 一个是 Vue的定义,也就是最里层的那个 Vue function 的定义
    • 另一个是 属性和原型方法,全局方法,静态方法的挂载
文章来源:https://blog.csdn.net/Tyro_java/article/details/135088339
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。