iis服务器助手广告广告
返回顶部
首页 > 资讯 > 前端开发 > JavaScript >一文了解Vue实例挂载的过程
  • 452
分享到

一文了解Vue实例挂载的过程

2024-04-02 19:04:59 452人浏览 薄情痞子
摘要

目录newVue()这个过程中究竟做了些什么?初始化数据initState(vm)看下initData再看下挂载方法是调用vm.$mountrender的作用主要是生成vnode总结

new Vue()这个过程中究竟做了些什么?

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

vue构建函数调用_init方法,但发现本文件中并没有此方法,但仔细可以看到文件下方定义了很多初始化方法

initMixin(Vue);     // 定义 _init
stateMixin(Vue);    // 定义 $set $get $delete $watch 等
eventsMixin(Vue);   // 定义事件  $on  $once $off $emit
lifecycleMixin(Vue);// 定义 _update  $forceUpdate  $destroy
renderMixin(Vue);   // 定义 _render 返回虚拟dom

首先可以看initMixin方法,发现该方法在Vue原型上定义了_init方法

Vue.prototype._init = function (options?: Object) {
    const vm: Component = this
    vm._uid = uid++
    let startTag, endTag
    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
    // 合并属性,判断初始化的是否是组件,这里合并主要是 mixins 或 extends 的方法
    if (options && options._isComponent) {
      initInternalComponent(vm, options)
    } else { // 合并vue属性
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      )
    }
    
    if (process.env.NODE_ENV !== 'production') {
      // 初始化proxy拦截器
      initProxy(vm)
    } else {
      vm._renderProxy = vm
    }
    // expose real self
    vm._self = vm
    // 初始化组件生命周期标志位
    initLifecycle(vm)
    // 初始化组件事件侦听
    initEvents(vm)
    // 初始化渲染方法
    initRender(vm)
    callHook(vm, 'beforeCreate')
    // 初始化依赖注入内容,在初始化data、props之前
    initInjections(vm) // resolve injections before data/props
    // 初始化props/data/method/watch/methods
    initState(vm)
    initProvide(vm) // resolve provide after data/props
    callHook(vm, 'created')
    
    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)
    }
  }

在 init() 中 会通过 callHook(vm,beforeCreate) 来执行 beforeCreate 生命周期函数然后通过initState(vm) 初始化 props 、 methods 、data 接着会通过callHook(vm, 'created') 来执行 created 生命周期函数 最后通过 vm.$mount(vm.$options.el) 来挂载元素

所以:
1. 在beforeCreate 生命周期函数中是无法访问 props和data 因为他们还没有被初始化
2. 同理在created函数中可以访问props,methods、data数据同时也是最早可以调用接口的生
   命周期函数,但是此时dom并未挂载 所以无法访问dom元素
3. 在mounted中 此时dom已经挂载成功 所以可以访问dom元素也是最早可以操作dom元素的生命周期函数

初始化数据 initState(vm)

export function initState (vm: Component) {
  // 初始化组件的watcher列表
  vm._watchers = []
  const opts = vm.$options
  // 初始化props
  if (opts.props) initProps(vm, opts.props)
  // 初始化methods方法
  if (opts.methods) initMethods(vm, opts.methods)
  if (opts.data) {
    // 初始化data  
    initData(vm)
  } else {
    observe(vm._data = {}, true )
  }
  if (opts.computed) initComputed(vm, opts.computed)
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)
  }
}

在initState中会先通过initProps 、 initMethods 、 initData 先后分别来初始化 相关数据 在这里会 初始化组件的watcher列表

看下 initData

function initData (vm: Component) {
  let data = vm.$options.data
  // 获取到组件上的data
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : data || {}
  if (!isPlainObject(data)) {
    data = {}
    process.env.NODE_ENV !== 'production' && warn(
      'data functions should return an object:\n' +
      'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
      vm
    )
  }
  // proxy data on instance
  const keys = Object.keys(data)
  const props = vm.$options.props
  const methods = vm.$options.methods
  let i = keys.length
  while (i--) {
    const key = keys[i]
    if (process.env.NODE_ENV !== 'production') {
      // 属性名不能与方法名重复
      if (methods && hasOwn(methods, key)) {
        warn(
          `Method "${key}" has already been defined as a data property.`,
          vm
        )
      }
    }
    // 属性名不能与state名称重复
    if (props && hasOwn(props, key)) {
      process.env.NODE_ENV !== 'production' && warn(
        `The data property "${key}" is already declared as a prop. ` +
        `Use prop default value instead.`,
        vm
      )
    } else if (!isReserved(key)) { // 验证key值的合法性
      // 将_data中的数据挂载到组件vm上,这样就可以通过this.xxx访问到组件上的数据
      proxy(vm, `_data`, key)
    }
  }
  // observe data
  // 响应式监听data是数据的变化
  observe(data, true )
}

需要注意的是:初始化data数据的时候 会校验 props中变量名称和data中的不能重复,在这里会通过observe劫持data的所有属性,如果监听到数据变化就通知订阅者watcher来更新数据,以此来实现数据双向绑定 ,这就是vue实现数据响应式的 发布订阅者模式 挖个坑自己实现该模式

再看下挂载方法是调用vm.$mount

Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  // 获取或查询元素
  el = el && query(el)
  
  // vue 不允许直接挂载到body或页面文档上
  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
    // 存在template模板,解析vue模板文件
    if (template) {
      if (typeof template === 'string') {
        if (template.charAt(0) === '#') {
          template = idToTemplate(template)
          
          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) {
      
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        mark('compile')
      }
      
      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

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

在调用vm.$mount方法时 会将template 解析为 抽象语法树 (ast tree) 再将抽象语法树 转换成render语法字符串 最终生成render方法 挂载到vm上后,会再次调用mount方法

Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && inBrowser ? query(el) : undefined
  // 渲染组件
  return mountComponent(this, el, hydrating)
}

调用mountComponent渲染组件

export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
  vm.$el = el
  // 如果没有获取解析的render函数,则会抛出警告
  // render是解析模板文件生成的
  if (!vm.$options.render) {
    vm.$options.render = createEmptyVNode
    if (process.env.NODE_ENV !== 'production') {
      
      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 {
        // 没有获取到vue的模板文件
        warn(
          'Failed to mount component: template or render function not defined.',
          vm
        )
      }
    }
  }
  // 执行beforeMount钩子
  callHook(vm, 'beforeMount')

  let updateComponent
  
  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)
      const vnode = vm._render()
      mark(endTag)
      measure(`vue ${name} render`, startTag, endTag)

      mark(startTag)
      vm._update(vnode, hydrating)
      mark(endTag)
      measure(`vue ${name} patch`, startTag, endTag)
    }
  } else {
    // 定义更新函数
    updateComponent = () => {
      // 实际调⽤是在lifeCycleMixin中定义的_update和renderMixin中定义的_render
      vm._update(vm._render(), hydrating)
    }
  }
  // 监听当前组件状态,当有数据变化时,更新组件
  new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted && !vm._isDestroyed) {
        // 数据更新引发的组件更新
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true )
  hydrating = false
  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')
  }
  return vm
}

此时会触发beforeMount钩子函数 ,定义updateComponent来渲染页面视图的方法,监听组件数据,一旦发生变化,触发beforeUpdate生命钩子 最后执行 callHook(vm, 'mounted') 钩子函数 完成挂载 updateComponent方法主要执行在vue初始化时声明的render,update方法

render的作用主要是生成vnode

// 定义vue 原型上的render方法
Vue.prototype._render = function (): VNode {
    const vm: Component = this
    // render函数来自于组件的option
    const { render, _parentVnode } = vm.$options

    if (_parentVnode) {
        vm.$scopedSlots = normalizeScopedSlots(
            _parentVnode.data.scopedSlots,
            vm.$slots,
            vm.$scopedSlots
        )
    }
    vm.$vnode = _parentVnode
    // render self
    let vnode
    try {
        currentRenderingInstance = vm
        // 调用render方法,自己的独特的render方法, 传入createElement参数,生成vNode
        vnode = render.call(vm._renderProxy, vm.$createElement)
    } catch (e) {
        handleError(e, vm, `render`)
        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
}

_update主要功能是调用patch,将vnode转换为真实DOM,并且更新到页面中

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) {
      // initial render
      // 执行具体的挂载逻辑
      vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false )
    } else {
      // updates
      vm.$el = vm.__patch__(prevVnode, vnode)
    }
    restoreActiveInstance()
    // update __vue__ reference
    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.
  }

总结

到此这篇关于Vue实例挂载过程的文章就介绍到这了,更多相关Vue实例挂载过程内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: 一文了解Vue实例挂载的过程

本文链接: https://www.lsjlt.com/news/141191.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 一文了解Vue实例挂载的过程
    目录newVue()这个过程中究竟做了些什么?初始化数据initState(vm)看下initData再看下挂载方法是调用vm.$mountrender的作用主要是生成vnode总结...
    99+
    2024-04-02
  • Vue实例挂载的方法是什么
    这篇文章主要介绍“Vue实例挂载的方法是什么”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Vue实例挂载的方法是什么”文章能帮助大家解决问题。一、思考我们都听过知其然知其所以然这句话。那么不知道大家...
    99+
    2023-06-26
  • Vue文件下载进度条的实现过程
    目录需求场景:实现原理:优化过程:下载方法的组件引入mixinVuex配置进度条最终效果图参考文章:需求场景: 1、大文件压缩过后依旧很大,接口返回response速度过慢,页面没有...
    99+
    2024-04-02
  • 一文了解mybatis的延迟加载
    目录1. 什么时候会创建代理对象2. 如何使用3.延迟加载的好处本文主要介绍下mybatis的延迟加载,从原理上介绍下怎么使用、有什么好处能规避什么问题。延迟加载一般用于级联查询(级...
    99+
    2024-04-02
  • 一文了解MySQL二级索引的查询过程
    目录前言联合索引总结前言 聚簇索引就是innodb默认创建的基于主键的索引结构,而且表里的数据就是直接放在聚簇索引里,作为叶节点的数据页: 基于主键的数据搜索:从聚簇索引的根节点开...
    99+
    2024-04-02
  • vue+django实现下载文件的示例
    目录一、概述二、django项目三、vue项目一、概述 在项目中,点击下载按钮,就可以下载文件。 传统的下载链接一般是get方式,这种链接是公开的,可以任意下载。 在实际项目,某些...
    99+
    2024-04-02
  • vue全局挂载实现APP全局弹窗的示例代码
    目录需求背景需求分析代码展示需求背景 app端对接网页端的客服系统,在用户实现网页客户系统发起询问时,app不论在哪个页面都需要弹窗提示 需求分析 这个需求分为两步,一个是负责双向...
    99+
    2024-04-02
  • 一文详解Canvas实现打飞字游戏过程示例
    目录正文一、游戏介绍二、效果预览三、实现思路1. 搭建页面结构2. 美化界面3. 编写JavaScript代码四、写在最后正文 打开游戏界面,看到一个画面简洁、却又富有挑战性的游戏...
    99+
    2023-05-14
    Canvas实现打飞字游戏 Canvas 游戏
  • MySQL存储过程图文实例讲解
    目录MySQL的存储过程MySQL存储过程的创建1、简单实例2、通过游标遍历结果集总结 MySQL的存储过程 存储过程是数据库的一个重要的功能,MySQL 5.0以前并不支...
    99+
    2024-04-02
  • 阿里云ECS实例的Linux挂载数据盘详解
    阿里云ECS实例是阿里云提供的一种云计算服务,用户可以根据自己的需求选择不同配置的ECS实例。在使用ECS实例的过程中,数据盘的挂载是非常重要的一环。本文将详细介绍阿里云ECS实例的Linux挂载数据盘的方法。 一、阿里云ECS实例的Lin...
    99+
    2023-11-13
    阿里 详解 实例
  • vue实现一个懒加载的树状表格实例
    目录一个懒加载的树状表格实例安装模板js代码 使用el-table懒加载树形表格时的注意点1、版本问题2、数据显示3、滚动条4、数据结构5、el-table的fixed导致...
    99+
    2024-04-02
  • vue下载excel文件的四种方法实例
    目录1、通过url下载2、通过 a 标签 download 属性结合 blob 构造函数下载3、通过 js-file-download 插件 4、使用fetch下载总结1、...
    99+
    2024-04-02
  • 一文了解axios和vue的整合操作
    目录前言一、axios是什么?1.定义2.原理3、主要特点二、axios的应用三、axios+vue的应用总结前言 前面学习了vue的本地应用操作,本文将会学习Vue的网络应用,介绍...
    99+
    2024-04-02
  • vue实现一个单文件组件的完整过程记录
    目录前言单文件组件 基本概念 简单的loader 解析组件内容 注册组件 获取脚本内容 Data URI和Object URI 动态导入 实现 行为层 兼容性问题及其他 总结前言 前...
    99+
    2024-04-02
  • 一文详解vue指令及其过滤器(附代码示例)
    本篇文章给大家带来了关于前端vue的相关知识,聊聊什么是内容渲染指令以及属性绑定指令等等,感兴趣的朋友,下面一起来看一下吧,希望对需要的朋友有所帮助!vue 指令与过滤器内容渲染指令内容渲染指令是用来辅助开发者渲染 DOM 元素的文本内容。...
    99+
    2023-05-14
    Vue
  • vue与django(drf)实现文件上传下载功能全过程
    目录文件上传功能上传后端部分上传前端部分(vue添加vue.js和node.js,设置eslint)文件下载功能下载后端部分下载前端部分总结文件上传功能 上传后端部分 (一)Mode...
    99+
    2023-02-23
    django文件上传下载 django文件上传 django配合vue
  • 详解Vue全局组件的挂载之实现弹窗组件
    目录vue组件挂载类型组件挂载代码示例1.vue.extend()方法2.render函数挂载vue组件挂载类型 vue中组件的挂载分为两种类型: vue.extend() rend...
    99+
    2022-11-13
    Vue组件挂载 弹窗 Vue组件挂载 Vue弹窗组件 Vue 弹窗
  • 一文详解Vue中过滤器filters的使用
    目录一、局部过滤器二、全局过滤器三、过滤器串联四、过滤器接收多个参数 Vue.js允许自定义过滤器,过滤器的作用可被用于一些常见的文本格式化(也就是修饰文本,但是文本内容不...
    99+
    2023-05-17
    Vue过滤器filters使用 Vue过滤器filters Vue过滤器
  • 一文带你详细了解Vue中的v-for
    目录v-forv-set重绘和回流虚拟DOMcomputed计算属性总结v-for 作用: 列表渲染,所在标签结构,按照数据数量,循环生成。指令写在谁身上,就循环创建谁 1.语法: ...
    99+
    2022-11-13
    vue的v-for的使用 vue的v-for循环 vue的v-for用法
  • Vue中点击url下载文件的案例详解
    目录代码实现使用注意封装自定义指令将url转成bold,在创建a标签下载blob 代码实现 在src 下面的 directive 文件夹下新建目录 downLoadUrl ...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作