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

为了分析 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日

相关文章

  • Javascript结合Vue实现对任意迷宫图片的自动寻路

    下面是”Javascript结合Vue实现对任意迷宫图片的自动寻路”的完整攻略: 1. 如何实现对任意迷宫图片的自动寻路 1.1 准备工作:模板结构 首先,我们需要准备好一个模板结构,用于容纳我们的代码逻辑、样式和UI交互。该模板结构包括以下几个文件和文件夹: index.html:主页面文件 script.js:主要的JavaScript代码文件 styl…

    Vue 2023年5月28日
    00
  • vue中如何下载文件导出保存到本地

    关于“Vue中如何下载文件导出保存到本地”的完整攻略,以下是步骤解释和代码示例: 步骤解释: 创建一个下载链接 我们可以通过创建一个 <a> 标签来实现文件下载,设置它的 href 属性指向要下载的文件路径,然后通过设置 download 属性来强制浏览器下载该文件。 通过axios请求服务器数据 使用 axios 可以轻松地向后端发送请求。比如…

    Vue 2023年5月27日
    00
  • Vue实现textarea固定输入行数与添加下划线样式的思路详解

    首先我们要明确需求:实现一个文本框,在输入文字达到固定的行数后(比如说4行),禁止继续输入,同时在每一行末尾添加下划线样式。 思路概述 我们可以通过Vue指令的方式来实现这一需求。具体而言,我们需要通过以下步骤来实现: 监听文本框输入事件,当输入框的文字超过指定行数时禁止继续输入; 在每一行末尾添加下划线样式; 实现步骤 1. 监听输入事件 我们在Vue的d…

    Vue 2023年5月27日
    00
  • 详解.NET Core中的数据保护组件

    详解.NET Core中的数据保护组件 什么是数据保护组件? 数据保护是.NET Core中的一种组件,用于保护应用程序中的敏感数据。在ASP.NET Core中,最常见的使用场景是保护cookie,其它应用场景还包括数据加密、命令行参数加密等等。数据保护组件使用类似于加密解密器的方式,将明文数据转换为不可逆的数据,从而保证数据的安全性。数据保护组件常见的加…

    Vue 2023年5月28日
    00
  • 详解.vue文件中style标签的几个标识符

    当我们使用 Vue 开发前端项目时,Vue 的单文件组件 .vue 文件中允许我们在 <template> 标签中定义模板、在 <script> 标签中定义 JS 代码、在 <style> 标签中定义 CSS 样式。对于 <style> 标签,Vue 允许我们使用一些特殊的标识符来扩展它们的功能和特性。 下面是…

    Vue 2023年5月28日
    00
  • 详解vuex commit保存数据技巧

    下面是详解vuex commit保存数据技巧的完整攻略。 简介 Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以预测的方式的改变应用状态。vuex 提供了 commit 方法用来操作 vuex 的 state 对象中的数据。 commit 方法 commit方法是vuex中的一个核心方法,它用来提…

    Vue 2023年5月27日
    00
  • 构建Vue大型应用的10个最佳实践(小结)

    当我们在构建大型Vue应用时,需要注意一些最佳实践,以确保应用程序的可维护性、可扩展性和性能。 以下是构建Vue大型应用的10个最佳实践: 组件化设计思想 Vue是一个组件化框架,充分利用它的能力可以将UI划分为独立的、可重用的组件。将业务逻辑和UI分离,使每个组件都可以独立开发、测试和维护。 例如,假设我们正在构建一个电子商务网站,并且需要显示各种商品列表…

    Vue 2023年5月27日
    00
  • Vue实现多标签选择器

    确定需求 首先,我们需要明确自己的需求,即实现一个多标签选择器,让用户能够方便地选择多个标签进行操作。在确定需求的时候,我们需要考虑如下问题: 该多标签选择器需要支持哪些操作,比如添加标签、删除标签、清空标签等等。 该多标签选择器是否需要支持搜索,用户可以搜索标签进行选择。 多标签选择器的样式应该如何设计,方便用户使用。 在确认好需求之后,我们就可以着手开始…

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