vue源码入口文件分析(推荐)

yizhihongxing

为了分析 Vue 的源码,我们需要先从入口文件开始。Vue 的入口文件在 src/platform/web/entry-runtime-with-compiler.js 中。

1. 入口文件的基本结构

入口文件主要做了以下几个事情:

  1. 定义了 Vue 构造函数。
  2. 重写了 Vue.prototype._init 方法。
  3. 定义了 $mount 方法。
  4. 扩展了 Vue 构造函数的静态属性和方法。

接下来,我们将逐条解析入口文件的代码。

首先是 import 部分:

import Vue from './runtime/index'
import { query } from './util/index'
import { compileToFunctions } from './compiler/index'
import { shouldDecodeNewlines } from './util/compat'
import { shouldDecodeNewlinesForHref } from './util/compat'

其中,import Vue from './runtime/index' 引入了 Vue 的 runtime-only 版本,该版本中没有模板编译器,因此会快一些,但需要在编译时指定渲染函数。接着,引入了一些工具函数和编译相关的方法,可以理解成 Vue 的底层实现。

const mount = Vue.prototype.$mount

Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && query(el)

  /* istanbul ignore if */
  // 处理不能挂载到 html 或 body 元素的情况
  if (el === document.body || el === document.documentElement) {
    if (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)
}

接下来,是重写 Vue.prototype._init 方法的代码:

Vue.prototype._init = function (options?: Object) {
  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-init:${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
  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)
  }
}

该方法会在创建 Vue 实例时被调用,它主要做了以下几个事情:

  1. 给 Vue 实例分配唯一的 _uid 属性。
  2. 在非生产模式下,打印出实例初始化的开始和结束标记。
  3. 判断是否是 Vue 实例对象,并赋值 _isVue 属性为 true
  4. 合并 options。
  5. 在非生产环境下,初始化 Proxy。
  6. 给实例对象赋值 _self 属性。
  7. 初始化生命周期。
  8. 初始化事件。
  9. 初始化渲染。
  10. 调用 beforeCreate 钩子函数。
  11. 解析注入。
  12. 初始化状态。
  13. 暴露出正确的 this
  14. 解析 Provider。
  15. 调用 created 钩子函数。
  16. 调用 $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) {
    if (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)
}

$mount 方法主要做了以下几个事情:

  1. 检查是否已经传入参数 el,如果已经传入,则调用 query 方法获取对应的 DOM 元素。
  2. 检查 el 是否为 body 或 html 标签,如果是则在非生产环境下打印出警告并且返回 Vue 实例对象。
  3. 获取 Vue 实例对象的 options 属性。
  4. 如果没有 options.render 方法,则检查是否有 options.template 或 el 属性。
  5. 如果存在 template 属性,则将 template 编译成 render 和 staticRenderFns 函数,并分别挂载到实例对象的 options.render 和 options.staticRenderFns 上。
  6. 调用 $mount 方法的原始实现。

最后,是扩展 Vue 构造函数的静态属性和方法:

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

// devtools global hook
/* istanbul ignore next */
if (inBrowser) {
  setTimeout(() => {
    if (config.devtools) {
      if (devtools) {
        devtools.emit('init', Vue)
      } else {
        console[console.info ? 'info' : 'log'](
          'Download the Vue Devtools extension for a better development experience:\n' +
          'https://github.com/vuejs/vue-devtools'
        )
      }
    }
    if (process.env.NODE_ENV !== 'production' && config.productionTip !== false && typeof console !== 'undefined') {
      console[console.info ? 'info' : 'log'](
        `You are running Vue in development mode.\n` +
        `Make sure to turn on production mode when deploying for production.\n` +
        `See more tips at https://vuejs.org/guide/deployment.html`
      )
    }
  }, 0)
}

export default Vue

其中 patch 函数是在 Vue 的运行时版本中导入的,它是将虚拟 DOM 转换成真实 DOM 的核心方法。接着,Vue 在全局注册了一些与平台相关的指令和组件,包括 v-model 和 transition。然后,将浏览器和服务器端的 $mount 方法的不同实现都绑定到 Vue.prototype 上。

最后就是一个调试工具 devtools 和开发模式的提示信息。在浏览器环境下,如果开启了 Vue Devtools,会触发 devtools 的 init 事件,并将 Vue 对象作为参数传递给它,以启用 devtools 的功能。如果开发环境下,Vue 将根据是否关闭了 productionTip 显示不同的提示信息。

2. 示例说明

示例1

在实例化 Vue 对象时,两个字符串类型的参数来初始化该 Vue 实例,其代码如下:

const app = new Vue({
  el: '#app',
  template: '<h1>Hello, {{ name }}!</h1>',
  data() {
    return {
      name: 'Vue'
    }
  }
})

此时,Vue 会自动执行编译过程,将 template 属性编译为渲染函数,并将该渲染函数赋值给 options.render。最后,调用 $mount 方法,对 DOM 节点进行渲染。

示例2

在 Vue 实例对象创建完成之后,手动调用 $mount 方法进行DOM渲染。

const app = new Vue({
  data() {
    return {
      message: 'Hello Vue!'
    }
  }
})

app.$mount('#app')

在该代码中,我们创建了一个 Vue 实例,但是没有传入 el 和 template 参数,因此在创建时被挂载的 DOM 将为空。然后我们手动调用 $mount 方法来将 Vue 实例挂载到 DOM 上。这样,Vue 通过 $mount 方法内部处理,将渲染函数生成真实的 DOM 元素,并且将该 DOM 元素插入到 #app 中。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:vue源码入口文件分析(推荐) - Python技术站

(0)
上一篇 2023年5月28日
下一篇 2023年5月28日

相关文章

  • vue axios用法教程详解

    Vue Axios用法教程详解 Vue.js是一个流行的JavaScript框架,用于构建交互式Web应用程序。Axios是一种常用的基于Promise的HTTP客户端,用于通过RESTful API发送HTTP请求。 本教程将详细介绍Vue Axios的用法,包括如何安装、设置和使用。 安装 安装Axios最简单的方法是使用npm,在命令行中运行以下命令:…

    Vue 2023年5月28日
    00
  • 详解vue-cli项目在IE浏览器打开报错解决方法

    当使用vue-cli创建的项目在IE浏览器中打开时,可能会遇到Object doesn’t support property or method ‘assign’或Promise未定义等错误。这是因为IE浏览器不支持ES6(ES2015)新特性,而vue-cli默认使用的是ES6语法并使用了一些ES6新特性,这对于IE浏览器来说是无法正确识别的。 下面是解决…

    Vue 2023年5月27日
    00
  • Vue如何使用Dayjs计算常用日期详解

    下面是使用Vue以及Dayjs计算常用日期的攻略详解: Dayjs是什么? Dayjs是一个轻量级、没有依赖的JavaScript日期处理库,它具有Moment.js的许多相同的特性,如轻量级、格式化、解析、时间戳、时间段、相对时间等。 如何在 Vue 项目中使用 Dayjs? 1.安装 dayjs npm install dayjs 2.在 Vue 项目中…

    Vue 2023年5月28日
    00
  • 关于Node.js中的JXcore打包示例

    下面就来详细讲解“关于Node.js中的JXcore打包示例”的完整攻略。 Node.js中的JXcore打包示例 简介 JXcore是一种基于Node.js的开源项目,主要用于将Node.js项目转化为独立的应用程序,支持Node.js的所有模块和API。使用JXcore可以将原本需要使用Node.js命令行执行的代码打包成二进制文件,方便部署和使用。 安…

    Vue 2023年5月28日
    00
  • 如何构建 vue-ssr 项目的方法步骤

    如何构建 Vue SSR 项目的方法步骤 Vue SSR,即 Vue.js 服务器端渲染,能够提高网站的首屏渲染速度,对于 SEO 也有很大的帮助。下面是构建 Vue SSR 项目的完整攻略: 安装依赖和插件 首先需要在项目中安装 vue-server-renderer,命令如下: npm install vue-server-renderer –save…

    Vue 2023年5月27日
    00
  • 浅谈vuex为什么不建议在action中修改state

    下面为您详细讲解“浅谈vuex为什么不建议在action中修改state”的攻略。 什么是Vuex Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。Vuex 基于 Vue.js 组件树的基础之上提供了一个全局的状态管理机制。 什么是Action Act…

    Vue 2023年5月28日
    00
  • vue 自动生成swagger接口请求文件的方法

    下面是详细讲解 “Vue 自动生成 Swagger 接口请求文件的方法” 的完整攻略。 什么是 Swagger? Swagger 是一种用于编写 RESTful API 接口文档的工具,它可以生成 API 文档、客户端 SDK 和服务器代码。目前,Swagger 已经成为了 API 文档编写的事实标准。 Vue 自动生成 Swagger 接口请求文件的方法 …

    Vue 2023年5月28日
    00
  • vue实现折线图 可按时间查询

    关于“vue实现折线图 可按时间查询”的过程,我可以提供以下完整攻略: 步骤一:准备数据 首先,我们需要准备一组数据来作为折线图的展示依据。可以使用Mock.js模拟数据,例如: { "result": [ { "date": "2022-01-01", "value": 10 …

    Vue 2023年5月28日
    00
合作推广
合作推广
分享本页
返回顶部