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

分析导入入口

  • 在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 的定义
    • 另一个是 属性和原型方法,全局方法,静态方法的挂载

相关推荐

  1. Vue2梳理 import Vue

    2023-12-20 17:40:02       34 阅读
  2. Vue2梳理:关于vm.$mount的实现

    2023-12-20 17:40:02       28 阅读
  3. Vue2梳理:render函数的实现

    2023-12-20 17:40:02       28 阅读
  4. Vue2梳理:update的整体实现流程

    2023-12-20 17:40:02       28 阅读
  5. TCP连接中的TIME-WAIT和2MSL

    2023-12-20 17:40:02       21 阅读
  6. Vue3梳理目录结构及阅读方法

    2023-12-20 17:40:02       42 阅读

最近更新

  1. TCP协议是安全的吗?

    2023-12-20 17:40:02       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2023-12-20 17:40:02       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2023-12-20 17:40:02       19 阅读
  4. 通过文章id递归查询所有评论(xml)

    2023-12-20 17:40:02       20 阅读

热门阅读

  1. Linux: network:tcp: option: TCP_INFO

    2023-12-20 17:40:02       35 阅读
  2. react基于antd二次封装分页组件Pagination

    2023-12-20 17:40:02       38 阅读
  3. 使用DB1小波进行三层小波分解(Matlab实现)

    2023-12-20 17:40:02       39 阅读
  4. 基于软译码的Hamming信道编码误码率Matlab仿真

    2023-12-20 17:40:02       38 阅读
  5. 力扣 | 347. 前 K 个高频元素

    2023-12-20 17:40:02       35 阅读
  6. VUE2组件按需引用

    2023-12-20 17:40:02       40 阅读
  7. 2024 年 QA 自动化的语言是什么?

    2023-12-20 17:40:02       49 阅读
  8. vivado 用XDC约束IP和子模块

    2023-12-20 17:40:02       28 阅读
  9. 低信噪比环境下的GPS信号识别与捕获技术

    2023-12-20 17:40:02       32 阅读
  10. k8s的API资源对象CustomResourceDefinition(CRD)

    2023-12-20 17:40:02       54 阅读