从vue2.6.11源码解读vue实例化过程

2023-11-21 01:40

本文主要是介绍从vue2.6.11源码解读vue实例化过程,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1.vue2.6.11文件结构

   vue文件结构下的详情:

distvue本身构建生成的文件
src
--compiler编译器
  --codegen键盘事件生成
     --event.js声明键盘事件和在Weex上生成具有绑定参数的处理程序代码
     --index.jscodegen入口文件,结合ASTElemen、键盘事件状态、插槽作用域确定规范(涉及:

sub-trees、v-once、v-if、v-for、key、pre、component、props、event handlers、v-model、inline-template、v-bind、v-on、data:finction、directives等等

)并生成渲染code

ASTElement树

   --directives指令
      --bind.jsv-bind语法糖的实现(结合ASTElement和ASTDirective)ast详解
      --index.js

1.暴露v-bind和v-model语法糖出去

2.引入并暴露shared/util.js 的noop方法(不执行任何操作,不留下无用的传输代码,严格检查函数调用 Arity

      --model.jsv-model语法糖的实现(结合ASTElement和ASTModifiers)ast详解
      --on.jsv-on语法糖的实现(结合ASTElement和ASTDirective)ast详解
  --parser解析器

    --entity-decoder.js通过创建div标签来解码传入的html内容
    --filter-parser.js过滤函数解析(filter允许用在两个地方,一个是双括号插值,一个是v-bind表达式后面,如果解析到这两种情况,执行parseFilters解析filter)解析思路
    --html-parser.jshtml解析器不检查此文件的类型,因为它主要是供应商代码。
     --index.jsparser入口文件,主要结合entity-decoder.js、filter-parser.js和text-parser.js生成ASTElement树
     --text-parser.js将text里的文本内容 和 {{}}插值内容一并存入TextParseResult类型的对象中并返回, 插值里的内容先进行过滤器解析
  --codeframe.jscode生成模板方法
  --create-compiler.js创建编译器
  --error-detector.js错误检测器(定义错误规则并检测)
  --helpers.js编译助手(给el添加、绑定属性、指令)
  --index.js创建编译器生成ast树且渲染
  --optimizer.js优化器

/目标:遍历生成的模板AST树

并检测纯静态的子树,即

永远不需要改变的DOM。

一旦我们检测到这些子树:

1.将它们提升为常数,这样我们就不再需要在每次重新渲染时为其创建新节点;

2、在修补过程中完全跳过它们。

  --to-function.js编译器将模板转换成函数
--core核心实现
  --components核心组件
     --index.jscomponents入口文件,引入KeepAlive并导出
     --keep-alive.jsKeepAlive(缓存组件)的实现
  --global-api核心接口
     --assets.jsVue组件资源创建资源注册方法
     --extend.jsVue.extend()扩展接口

每个实例构造函数(包括Vue)都有一个唯一的cid。这使我们能够创建包装的“子对象”构造函数”,并缓存它们。

对于PROP和计算属性,我们在上定义代理getter扩展时的Vue实例,在扩展原型上。为创建的每个实例调defineProperty。

允许进一步扩展/混合/插件使用

创建资产寄存器,以便扩展类

也可以拥有他们的私人资源。

启用递归自查找,在扩展时保留引用。在实例化时,可以检查Super的选项是否已更新。并缓存构造函数

     --index.js全局api入口文件
     --mixin.jsVue.mixin()合并接口通过调用../util/index中的mergeOptions来实现
     --use.jsVue.use()插件接口通过调用../util/index中的

toArray来实现

  --instance核心实例
    --render-helpers渲染助手
      --bind-dynamic-keys.js帮助处理v-bind和v-on中动态参数的动态键将vue模板(如:

<div id="app" :[key]="value">

)编译(如:

_c('div', { attrs: bindDynamicKeys({ "id": "app" }, [key, value]) })

       --bind-object-listeners.js对象侦听器处理v-on=’{}'到vnode对象 data上
      --bind-object-props.js

用于将v-bind=“object”合并到VNode数据中的运行时助手

      --check-keycodes.js

用于从配置中检查键代码的运行时帮助程序。作为Vue.prototype公开,将eventKeyName作为最后一个参数单独传递给向后兼容。

      --index.jsrender-helpers(入口文件)
      --render-list.js用于呈现v-for列表的运行时助手
      --render-slot.js插槽渲染助手
      --render-static.js渲染静态树运行助手
      --resolve-filter.jsfilters过滤器渲染助手
      --resolve-scoped-slots.js同resolve-slots.js作用一样,不同的是编译父组件模板时,会生成一个返回结果为VNode的函数。当子组件匹配到父组件传递作用域插槽函数时,调用该函数生成对应VNode
      --resolve-slots.js将children VNodes解析为插槽对象运行助手
    --events.js核心事件($on、$off、$emit、$once)接口通过'../vdom/helpers/index'的

updateListeners监听来实现

    --index.js核心实例(入口文件)接口:(导入)代理,状态,渲染,事件,生命周期,基础工具
    --init.js封装vue初始化(实例化)init接口
    --inject.jsprovide和inject接口的实现
    --lifecycle.js生命周期接口的实现
    --proxy.js核心代理接口(判断平台是否支持Proxy功能;注册Proxy的处理函数;初始化代理initProxy() 注册vm._renderProxy属性)
    --render.js核心渲染接口
    --state.js核心状态接口(初始化顺序:props属性,methods属性,data属性,computed属性watch属性)
  --observer监视器接口
    --array.js数组监控

数组方法:'push','pop','shift',

'unshift','splice',sort','reverse'

    --dep.js消息订阅器
    --index.js数据监控器observer
    --scheduler.js事件(队列)侦听器
    --traverse.js对象侦听

递归遍历对象以调用所有已转换的getter,使对象内的每个嵌套属性,作为“深度”依赖项收集。

    --watcher.js消息订阅

观察者解析表达式,收集依赖项,并在表达式值更改时激发回调。这用于$watch()api和指令。

  --util核心工具
    --debug.jsdebug工具
    --env.js环境工具
    --error.js错误工具(在处理错误程序时,停用deps跟踪避免无限渲染)
    --index.js入口文件(导入工具接口)
    --lang.js语言扩展工具
    --next-tick.jsnextTick工具微任务异步延迟包装器,nextTick行为利用了可以访问的微任务队列;添加空计时器“强制”刷新微任务队列;策略

Promise、MutationObserver、setImmediate、setTimeout

    --options.js选项覆盖(父值和子值的合并策略及转换为最终结果)
    --perf.js忽略工具
    --props.jsprops属性工具
  --vdom
    --helpers助手
      --extract-props.jsprop提取助手
      --get-first-component-child.js获取第一个组件
      --index.js入口文件
      --is-async-placeholder.js异步占位符判断
      --merge-hook.js合并钩子
      --normalize-children.js规范化子项
      --normalize-scoped-slots.js规范化作用域插槽
      --resolve-async-component.js解析异步组件
      --update-listeners.js更新侦听器
    --modules模块
      --directives.js指令
      --index.js入口文件
      --ref.jsref
    --create-component.js提供createComponent方法创建组件返回vnode
    --create-element.js提供createElement方法
    --create-functional-component.js

提供createFunctionalComponent方法

    --patch.js基于Snabbdom的虚拟DOM修补算法
    --vnode.js入口文件(提供VNode类)
  --config.js核心配置文件
  --index.js核心入口文件,引入和导出vue api接口
--platforms应用平台
  --webweb平台
    --compiler编译器
    --directives指令
      --html.js添加html元素及内容
      --index.js入口文件
      --model.jsmodel指令
      --text.jstext指令
    --modules模块
      --class.jsdom节点样式class解析
      --index.js入口文件
      --model.js表单v-model解析
      --style.jsdom节点样式style解析
      --index.js入口文件
      --options.js编译器选项
      --util.js标签及属性判断归类方法
    --runtime运行
      --components组件
        --transition-group.js提供列表项支持
        --transition.js提供单个元素/组件支持
    --directives指令
       --index.js入口文件
       --model.jsmodel指令
       --show.jsshow指令
    --modules模块
      --attrs.jsattrs属性
      --class.jsclass属性
      --dom-props.jsdom的props属性
      --events.js事件
      --index.js入口文件
      --style.js样式优先级判断及更新
      --transition.jsdom节点的过渡
    --class-util.js封装添加/移除与SVG兼容的样式class
    --index.js入口文件
    --node-ops.jsnode节点的方法集成封装
    --patch.js内置模块(最后应用指令模块)
    --transition-util.js过渡集成封装
    --server服务端
       --directives指令
       --index.js入口文件
       --model.jsmodel指令
       --show.jsshow指令
    --modules模块
       --attrs.jsattrs属性
       --class.jsclass属性
       --dom-props.jsdom的props属性
       --index.js入口文件
       --style.js样式优先级判断及更新
      --compiler.js重命名并暴露编译器方法
      --util.js标签属性及样式属性判断归类方法
    --util应用
      --attrs.jsweb保留的attrs属性编译处理
      --class.js样式class编译处理
      --compat.js检查当前浏览器是否在属性值内编码字符
      --element.jshtml dom元素编译处理
      --index.js入口文件
      --style.js样式style元素编译处理
    --entry-compiler.js条目编译器
   --entry-runtime-with-compiler.js带编译器的入口运行
    --entry-runtime.js编译器运行入口文件
    --entry-server-basic-renderer.js服务端基本渲染器
    --entry-server-renderer.js服务端渲染器
  --weexweex平台
    --compiler
      --directives
        --index.js入口文件
        --model.jsmodel指令
      --modules模块
        --recycle-list回收列表
          --component-root.js根组件
          --component.js组件
          --index.js入口文件
          --recycle-list.js回收列表
           --text.js文本解析器
          --v-bind.jsv-bind解析器
          --v-for.jsv-for解析器
          --v-if.jsv-if解析器
          --v-on.jsv-on解析器
          --v-once.jsv-once解析器
      --append.js追加
      --class.js样式class解析器
      --index.js入口文件
      --props.jsprops解析器
      --style.js样式style解析器
     --index.js入口文件
   --runtime
     --components
       --index.js入口文件
       --richtext.js富文本
       --transition-group.js过渡组
       --transition.js过渡
    --directives指令
      --index.js入口文件
    --modules模块
      --attrs.jsattrs属性编译处理
      --class.js样式class编译处理
      --event.js事件
      --index.js入口文件
      --style.js样式style元素编译处理
      --transition.js过渡编译处理
    --recycle-list回收列表
      --render-component-template.js渲染组件模板
      --virtual-component.js虚拟组建
    --index.js        入口文件
    --node-ops.jsnode节点的方法集成封装
    --patch.js内置模块(最后应用指令模块)
    --node-ops.js节点文本
   --util应用
      --element.jshtml dom元素编译处理
      --index.js入口文件
      --parser.js解析器
    --entry-compiler.js条目编译器
    --entry-framework.js入口框架
    --entry-runtime-factory.js函数构建包装用于为每个Weex实例生成Vue的新副本
--server支持服务器端渲染,所有服务器端渲染相关的逻辑
  --bundle-renderer
    --create-bundle-renderer.js创建结束渲染器
    --create-bundle-runner.js创建结束运行程序
    --source-map-support.js源地图支持
  --optimizing-compiler
    --codegen.js通过扩展默认codegen来进行SSR优化节点
    --index.js                        入口文件
    --modules.js模块
    --optimizer.js

在SSR中,vdom树只生成一次,从不修补,因此我们可以将大多数元素/树优化为纯字符串渲染函数。

*SSR优化器遍历AST树以检测可优化的元素和树。SSR优化的标准比静态树要宽松一些检测(设计用于客户端重新渲染)。在SSR中,我们只为组件/插槽/自定义指令。

    --runtime-helpers.js运行帮助程序
  --template-renderer
    --create-async-file-mapper.js创建异步映射器

创建映射器,映射服务器端渲染期间使用的组件在客户端构建中异步区块文件,以便我们可以内联它们直接在呈现的HTML中,以避免瀑布式请求。

    --index.js入口文件
    --parse-template.js分析模板
    --template-stream.js模板流
  --webpack-pluginwebpack插件
    --client.js客户端
    --server.js服务端
    --util.js应用
  --create-basic-renderer.js创建基本渲染器
  --create-renderer.js 创建渲染器
  --render-context.js渲染上下文
  --render-stream.js渲染流
  --render.js服务器渲染函数
  --util.js判断js、css文件并创建promise回调
  --write.js创建写入函数(会判断堆栈)
--sfcsingle-file components(单文件组件)简单说:转换单文件组件(*.vue) 解析成一个javascript对象
    --parser.js主要内容:将单个文件组件(*.vue)文件解析为SFC描述对象。
--shared共享
   --constants.js常量:数据渲染,组件,指令,过滤器,生命周期,错误机制和SSR服务端渲染(组件在服务器上呈现之前解析,异步)
  --util.js封装了些简单实用明确的js方法(检查是是否为js原始值,undefined判断,对象判断,两个值是否大致相等,值在数组中的第一个索引,等)
types一些ts文件
LICENSE作者尤雨溪及版权声明
package.jsonvue2配置文件
README.mdvue的一些赞助商

2.vue实例化过程

vue构造函数,源码位置:src\core\instance\index.js

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'/*** * @param {*} options 是用户传递过来的配置项,如data、methods等常用的方法*/
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)
}
// 定义 _init
initMixin(Vue)
// 定义 $set $get $delete $watch 等
stateMixin(Vue)
// 定义事件$on  $once $off $emit
eventsMixin(Vue)
// 定义 _update  $forceUpdate  $destroy
lifecycleMixin(Vue)
// 定义 _render 返回虚拟dom
renderMixin(Vue)export default Vue

initMixin方法,源码位置:src\core\instance\init.js

/* @flow */import config from '../config'
import { initProxy } from './proxy'
import { initState } from './state'
import { initRender } from './render'
import { initEvents } from './events'
import { mark, measure } from '../util/perf'
import { initLifecycle, callHook } from './lifecycle'
import { initProvide, initInjections } from './inject'
import { extend, mergeOptions, formatComponentName } from '../util/index'let uid = 0export function initMixin (Vue: Class<Component>) {Vue.prototype._init = function (options?: Object) {const vm: Component = this// a uidvm._uid = uid++let startTag, endTag/* istanbul ignore if */if (process.env.NODE_ENV !== 'production' && config.performance && mark) {startTag = `vue-perf-start:${vm._uid}`endTag = `vue-perf-end:${vm._uid}`mark(startTag)}// a flag to avoid this being observedvm._isVue = true// merge options// 合并属性,判断初始化的是否是组件,这里合并主要是 mixins 或 extends 的方法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 {// 合并vue属性vm.$options = mergeOptions(resolveConstructorOptions(vm.constructor),options || {},vm)}/* istanbul ignore else */if (process.env.NODE_ENV !== 'production') {// 初始化proxy拦截器initProxy(vm)} else {vm._renderProxy = vm}// expose real selfvm._self = vm// 初始化组件生命周期标志位initLifecycle(vm)// 初始化组件事件侦听initEvents(vm)// 初始化渲染方法initRender(vm)callHook(vm, 'beforeCreate')// 初始化依赖注入内容,在初始化data、props之前initInjections(vm) // resolve injections before data/props// 初始化props/data/method/watch/methodsinitState(vm)initProvide(vm) // resolve provide after data/propscallHook(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)}}
}

结论:可以看出,

1)在调用beforeCreate之前,数据初始化并未完成,像dataprops这些属性无法访问到

2)到了created的时候,数据已经初始化完成,能够访问dataprops这些属性,但这时候并未完成dom的挂载,因此无法访问到dom元素

3)挂载方法是调用vm.$mount方法

initState方法,完成props/methods/data/computed/watch的初始化,源码位置:src\core\instance\state.js

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)// 初始化data函数/对象if (opts.data) {initData(vm)} else {observe(vm._data = {}, true /* asRootData */)}// 初始化computed方法if (opts.computed) initComputed(vm, opts.computed)// 初始化watch 方法if (opts.watch && opts.watch !== nativeWatch) {initWatch(vm, opts.watch)}
}

结论: 可以看出,初始化顺序为:

props => methods => data => computed => watch

可以根据这个初始化顺序,我们可以定义一些策略,如:data直接调用methods方法,computed调用methods方法,而不会产生undefine

initData方法,源码位置:src\core\instance\state.js

function initData (vm: Component) {let data = vm.$options.data// 获取组件上的datadata = 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 instanceconst keys = Object.keys(data)const props = vm.$options.propsconst methods = vm.$options.methodslet i = keys.lengthwhile (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)}}// 属性名不能与props中的名称重复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)}}

结论:可以看出,

1)data定义的时候可选择函数形式或者对象形式(组件只能为函数形式)

2)data的属性名会在methods方法和props属性中判断是否重名

3)data会被响应式监听数据变化

vm.$mount挂载方法,源码位置:

web平台:src/platforms/web/runtime/index.js

weex平台:src/platform/weex/runtime/index.js

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

mountComponent渲染组件,源码位置src/core/instance/lifecycle.js

export function mountComponent (vm: Component,el: ?Element,hydrating?: boolean
): Component {vm.$el = el// 如果没有获取解析的render函数,则会抛出警告// render是解析模板文件生成的if (!vm.$options.render) {vm.$options.render = createEmptyVNodeif (process.env.NODE_ENV !== 'production') {/* istanbul ignore if */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 {warn('Failed to mount component: template or render function not defined.',vm)}}}// 执行beforeMount钩子callHook(vm, 'beforeMount')let updateComponent/* istanbul ignore if */if (process.env.NODE_ENV !== 'production' && config.performance && mark) {updateComponent = () => {const name = vm._nameconst id = vm._uidconst 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 = () => {vm._update(vm._render(), hydrating)}}// we set this to vm._watcher inside the watcher's constructor// since the watcher's initial patch may call $forceUpdate (e.g. inside child// component's mounted hook), which relies on vm._watcher being already defined// 监听当前组件状态,当有数据变化时,更新组件new Watcher(vm, updateComponent, noop, {before () {if (vm._isMounted && !vm._isDestroyed) {callHook(vm, 'beforeUpdate')}}}, true /* isRenderWatcher */)hydrating = false// manually mounted instance, call mounted on self// mounted is called for render-created child components in its inserted hook//手动装载实例,调用自行装载//在其插入的挂钩中为渲染创建的子组件调用mountedif (vm.$vnode == null) {vm._isMounted = truecallHook(vm, 'mounted')}return vm
}

结论:可以看出,

1) 会触发beforeCreate钩子
2) 定义updateComponent渲染页面视图的方法
3) 监听组件数据,一旦发生变化,触发beforeUpdate生命钩子
4) updateComponent方法主要执行在vue初始化时声明的render,update方法

render方法的主要作用是生成vnode, 源码位置:src/core/instance/render.js

// 定义vue 原型上的render方法Vue.prototype._render = function (): VNode {const vm: Component = this// render函数来自于组件的optionconst { render, _parentVnode } = vm.$optionsif (_parentVnode) {vm.$scopedSlots = normalizeScopedSlots(_parentVnode.data.scopedSlots,vm.$slots,vm.$scopedSlots)}// set parent vnode. this allows render functions to have access// to the data on the placeholder node.vm.$vnode = _parentVnode// render selflet vnodetry {// There's no need to maintain a stack because all render fns are called// separately from one another. Nested component's render fns are called// when parent component is patched.currentRenderingInstance = vm// 调用render方法,自己的独特的render方法, 传入createElement参数,生成vNodevnode = render.call(vm._renderProxy, vm.$createElement)} catch (e) {handleError(e, vm, `render`)// return error render result,// or previous vnode to prevent render error causing blank component/* istanbul ignore else */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 itif (Array.isArray(vnode) && vnode.length === 1) {vnode = vnode[0]}// return empty vnode in case the render function errored outif (!(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 parentvnode.parent = _parentVnodereturn vnode}

_update方法通过调用patch,将vnode转换为真实DOM,并且更新到页面中;源码位置:

src/core/instance/lifecycle.js

Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {const vm: Component = thisconst prevEl = vm.$elconst 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 /* removeOnly */)} else {// updatesvm.$el = vm.__patch__(prevVnode, vnode)}restoreActiveInstance()// update __vue__ referenceif (prevEl) {prevEl.__vue__ = null}if (vm.$el) {vm.$el.__vue__ = vm}// if parent is an HOC, update its $el as wellif (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.}

$mount在web平台具体实现(src/platform/web/entry-runtime-with-compiler.js)

const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (el?: string | Element,hydrating?: boolean
): Component {// 获取或查询元素el = el && query(el)/* istanbul ignore if */// 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 functionif (!options.render) {let template = options.template// 存在template模板,解析vue模板文件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')}// 将temmplate解析ast tree, 将ast tree转换成render语法字符串, 生成render方法const { render, staticRenderFns } = compileToFunctions(template, {outputSourceRange: process.env.NODE_ENV !== 'production',shouldDecodeNewlines,shouldDecodeNewlinesForHref,delimiters: options.delimiters,comments: options.comments}, this)options.render = renderoptions.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)
}

结论:可以看出,

1)不要将根元素放到body或者html上
2)可以在对象中定义template/render或者直接使用template、el表示元素选择器
3)最终都会解析成render函数,调用compileToFunctions,会将template解析成render函数
4)template解析步骤:
    将html文档片段解析成ast描述符
    将ast描述符解析成字符串
    生成render函数
    生成render函数,挂载到vm上后,会再次调用mount方法

initLifecycle方法初始化一些生命周期相关属性,源码位置:src/core/instance/lifecycle.js

export function initLifecycle(vm: Component) {// 初始化参数$options  const options = vm.$options// locate first non-abstract parent/*** 定位第一个“非抽象”的父组件* 抽象组件:<keep-alive>包裹动态组件时,会缓存不活动的组件实例,而不销毁。和<transition>相似* <keep-alive>是一个抽象组件:它自身不会渲染一个dom元素,也不会出现在父组件链中*/let parent = options.parentif (parent && !options.abstract) {// 持续找组件的父组件直到找到非抽象组件while (parent.$options.abstract && parent.$parent) {parent = parent.$parent}parent.$children.push(vm)}// 指定创建的实例的父实例,两者之间建立父子关系vm.$parent = parent// 当前组件树的根Vue实例;如果当前实例没有父实例,此实例将会是自己vm.$root = parent ? parent.$root : vm// 当前实例的直接子组件。但并不保证顺序,也不响应式vm.$children = []// 注册过ref的所有子组件vm.$refs = {}// 当前实例组件的watcher实例对象vm._watcher = null// keep-alive组件状态vm._inactive = null// keep-alive组件状态的属性vm._directInactive = false// 当前实例是否完成挂载vm._isMounted = false// 当前实例是否已被销毁vm._isDestroyed = false// 当前实例是否正在被销毁vm._isBeingDestroyed = false
}

结论:可以看出,

1)确定父子组件关系

2)初始化ref,watcher,ref属性,判断组件是否挂载、是否销毁和销毁中

stateMixin方法,源码位置:src/core/instance/state.js

export function stateMixin (Vue: Class<Component>) {// flow somehow has problems with directly declared definition object// when using Object.defineProperty, so we have to procedurally build up// the object here.const dataDef = {}dataDef.get = function () { return this._data }const propsDef = {}propsDef.get = function () { return this._props }if (process.env.NODE_ENV !== 'production') {dataDef.set = function () {warn('Avoid replacing instance root $data. ' +'Use nested data properties instead.',this)}propsDef.set = function () {warn(`$props is readonly.`, this)}}Object.defineProperty(Vue.prototype, '$data', dataDef)Object.defineProperty(Vue.prototype, '$props', propsDef)Vue.prototype.$set = setVue.prototype.$delete = delVue.prototype.$watch = function (expOrFn: string | Function,cb: any,options?: Object): Function {const vm: Component = thisif (isPlainObject(cb)) {return createWatcher(vm, expOrFn, cb, options)}options = options || {}options.user = trueconst watcher = new Watcher(vm, expOrFn, cb, options)if (options.immediate) {const info = `callback for immediate watcher "${watcher.expression}"`pushTarget()invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)popTarget()}return function unwatchFn () {watcher.teardown()}}
}

结论:可以看出,通过定义dataDef和propsDef,来设置响应式data和data和props的get和set方法,并给vue实例添加set、delete和watch方法

eventsMixin方法:源码位置:src/core/instance/events.js

export function eventsMixin (Vue: Class<Component>) {const hookRE = /^hook:/Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {const vm: Component = thisif (Array.isArray(event)) {for (let i = 0, l = event.length; i < l; i++) {vm.$on(event[i], fn)}} else {(vm._events[event] || (vm._events[event] = [])).push(fn)// optimize hook:event cost by using a boolean flag marked at registration// instead of a hash lookupif (hookRE.test(event)) {vm._hasHookEvent = true}}return vm}Vue.prototype.$once = function (event: string, fn: Function): Component {const vm: Component = thisfunction on () {vm.$off(event, on)fn.apply(vm, arguments)}on.fn = fnvm.$on(event, on)return vm}Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {const vm: Component = this// allif (!arguments.length) {vm._events = Object.create(null)return vm}// array of eventsif (Array.isArray(event)) {for (let i = 0, l = event.length; i < l; i++) {vm.$off(event[i], fn)}return vm}// specific eventconst cbs = vm._events[event]if (!cbs) {return vm}if (!fn) {vm._events[event] = nullreturn vm}// specific handlerlet cblet i = cbs.lengthwhile (i--) {cb = cbs[i]if (cb === fn || cb.fn === fn) {cbs.splice(i, 1)break}}return vm}Vue.prototype.$emit = function (event: string): Component {const vm: Component = thisif (process.env.NODE_ENV !== 'production') {const lowerCaseEvent = event.toLowerCase()if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) {tip(`Event "${lowerCaseEvent}" is emitted in component ` +`${formatComponentName(vm)} but the handler is registered for "${event}". ` +`Note that HTML attributes are case-insensitive and you cannot use ` +`v-on to listen to camelCase events when using in-DOM templates. ` +`You should probably use "${hyphenate(event)}" instead of "${event}".`)}}let cbs = vm._events[event]if (cbs) {cbs = cbs.length > 1 ? toArray(cbs) : cbsconst args = toArray(arguments, 1)const info = `event handler for "${event}"`for (let i = 0, l = cbs.length; i < l; i++) {invokeWithErrorHandling(cbs[i], vm, args, vm, info)}}return vm}
}

结论:可以看出,定义实现Vue实例的$on、$once、$off和$emit方法

lifecycleMixin方法,源码位置:src/core/instance/lifecycle.js

export function lifecycleMixin (Vue: Class<Component>) {Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {const vm: Component = thisconst prevEl = vm.$elconst prevVnode = vm._vnodeconst 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 /* removeOnly */)} else {// updatesvm.$el = vm.__patch__(prevVnode, vnode)}restoreActiveInstance()// update __vue__ referenceif (prevEl) {prevEl.__vue__ = null}if (vm.$el) {vm.$el.__vue__ = vm}// if parent is an HOC, update its $el as wellif (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.prototype.$forceUpdate = function () {const vm: Component = thisif (vm._watcher) {vm._watcher.update()}}Vue.prototype.$destroy = function () {const vm: Component = thisif (vm._isBeingDestroyed) {return}callHook(vm, 'beforeDestroy')vm._isBeingDestroyed = true// remove self from parentconst parent = vm.$parentif (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {remove(parent.$children, vm)}// teardown watchersif (vm._watcher) {vm._watcher.teardown()}let i = vm._watchers.lengthwhile (i--) {vm._watchers[i].teardown()}// remove reference from data ob// frozen object may not have observer.if (vm._data.__ob__) {vm._data.__ob__.vmCount--}// call the last hook...vm._isDestroyed = true// invoke destroy hooks on current rendered treevm.__patch__(vm._vnode, null)// fire destroyed hookcallHook(vm, 'destroyed')// turn off all instance listeners.vm.$off()// remove __vue__ referenceif (vm.$el) {vm.$el.__vue__ = null}// release circular reference (#6759)if (vm.$vnode) {vm.$vnode.parent = null}}
}

结论:可以看出,定义实现vue实例的_update,$forceUpdate,$destroy方法

renderMixin方法,源码位置:src/core/instance/render.js

export function renderMixin (Vue: Class<Component>) {// install runtime convenience helpers// 安装运行助手installRenderHelpers(Vue.prototype)// 定义$nextTickVue.prototype.$nextTick = function (fn: Function) {return nextTick(fn, this)}// 定义vue 原型上的render方法Vue.prototype._render = function (): VNode {const vm: Component = this// render函数来自于组件的optionconst { render, _parentVnode } = vm.$optionsif (_parentVnode) {vm.$scopedSlots = normalizeScopedSlots(_parentVnode.data.scopedSlots,vm.$slots,vm.$scopedSlots)}// set parent vnode. this allows render functions to have access// to the data on the placeholder node.// 设置父vnode。这允许渲染函数具有访问权限到占位符节点上的数据。vm.$vnode = _parentVnode// render selflet vnodetry {// There's no need to maintain a stack because all render fns are called// separately from one another. Nested component's render fns are called// when parent component is patched.currentRenderingInstance = vm// 调用render方法,自己的独特的render方法, 传入createElement参数,生成vNodevnode = render.call(vm._renderProxy, vm.$createElement)} catch (e) {handleError(e, vm, `render`)// return error render result,// or previous vnode to prevent render error causing blank component/* istanbul ignore else */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 itif (Array.isArray(vnode) && vnode.length === 1) {vnode = vnode[0]}// return empty vnode in case the render function errored outif (!(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 parentvnode.parent = _parentVnodereturn vnode}
}

结论:可以看出,定义实现vue实例的 _render 并返回返回虚拟dom

错误处理handleError方法,源码地址:src/core/util/error.js

export function handleError (err: Error, vm: any, info: string) {// Deactivate deps tracking while processing error handler to avoid possible infinite rendering.// See: https://github.com/vuejs/vuex/issues/1505// 在处理错误处理程序时停用deps跟踪,以避免可能的无限渲染。pushTarget()try {if (vm) {let cur = vmwhile ((cur = cur.$parent)) {// vue实例的errorCaptured生命周期const hooks = cur.$options.errorCapturedif (hooks) {for (let i = 0; i < hooks.length; i++) {try {const capture = hooks[i].call(cur, err, vm, info) === falseif (capture) return} catch (e) {globalHandleError(e, cur, 'errorCaptured hook')}}}}}globalHandleError(err, vm, info)} finally {popTarget()}
}
function globalHandleError (err, vm, info) {if (config.errorHandler) {try {return config.errorHandler.call(null, err, vm, info)} catch (e) {// if the user intentionally throws the original error in the handler,// do not log it twiceif (e !== err) {logError(e, null, 'config.errorHandler')}}}logError(err, vm, info)
}function logError (err, vm, info) {if (process.env.NODE_ENV !== 'production') {warn(`Error in ${info}: "${err.toString()}"`, vm)}/* istanbul ignore else */if ((inBrowser || inWeex) && typeof console !== 'undefined') {console.error(err)} else {throw err}
}

结论:可以看出,错误处理机制会触发vue实例的errorCaptured生命周期(捕获一个来自子孙组件的错误时被调用)

3.思考总结

        从vue实例化的过程中,可以看出其生命周期的调用过程,而不是简单意义上理解的8大生命周期;具体如下:

生命周期描述
beforeCreate组件实例被创建之初
created组件实例已经完全创建
beforeMount组件挂载之前
mounted组件挂载到实例上去之后
beforeUpdate组件数据发生变化,更新之前
updated组件数据更新之后
beforeDestroy组件实例销毁之前
destroyed组件实例销毁之后
activatedkeep-alive 缓存的组件激活时
deactivatedkeep-alive 缓存的组件停用时调用
errorCaptured捕获一个来自子孙组件的错误时被调用

这篇关于从vue2.6.11源码解读vue实例化过程的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/398924

相关文章

Vue3 的 shallowRef 和 shallowReactive:优化性能

大家对 Vue3 的 ref 和 reactive 都很熟悉,那么对 shallowRef 和 shallowReactive 是否了解呢? 在编程和数据结构中,“shallow”(浅层)通常指对数据结构的最外层进行操作,而不递归地处理其内部或嵌套的数据。这种处理方式关注的是数据结构的第一层属性或元素,而忽略更深层次的嵌套内容。 1. 浅层与深层的对比 1.1 浅层(Shallow) 定义

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

【 html+css 绚丽Loading 】000046 三才归元阵

前言:哈喽,大家好,今天给大家分享html+css 绚丽Loading!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏+关注哦 💕 目录 📚一、效果📚二、信息💡1.简介:💡2.外观描述:💡3.使用方式:💡4.战斗方式:💡5.提升:💡6.传说: 📚三、源代码,上代码,可以直接复制使用🎥效果🗂️目录✍️

作业提交过程之HDFSMapReduce

作业提交全过程详解 (1)作业提交 第1步:Client调用job.waitForCompletion方法,向整个集群提交MapReduce作业。 第2步:Client向RM申请一个作业id。 第3步:RM给Client返回该job资源的提交路径和作业id。 第4步:Client提交jar包、切片信息和配置文件到指定的资源提交路径。 第5步:Client提交完资源后,向RM申请运行MrAp

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

【机器学习】高斯过程的基本概念和应用领域以及在python中的实例

引言 高斯过程(Gaussian Process,简称GP)是一种概率模型,用于描述一组随机变量的联合概率分布,其中任何一个有限维度的子集都具有高斯分布 文章目录 引言一、高斯过程1.1 基本定义1.1.1 随机过程1.1.2 高斯分布 1.2 高斯过程的特性1.2.1 联合高斯性1.2.2 均值函数1.2.3 协方差函数(或核函数) 1.3 核函数1.4 高斯过程回归(Gauss

MCU7.keil中build产生的hex文件解读

1.hex文件大致解读 闲来无事,查看了MCU6.用keil新建项目的hex文件 用FlexHex打开 给我的第一印象是:经过软件的解释之后,发现这些数据排列地十分整齐 :02000F0080FE71:03000000020003F8:0C000300787FE4F6D8FD75810702000F3D:00000001FF 把解释后的数据当作十六进制来观察 1.每一行数据

Java ArrayList扩容机制 (源码解读)

结论:初始长度为10,若所需长度小于1.5倍原长度,则按照1.5倍扩容。若不够用则按照所需长度扩容。 一. 明确类内部重要变量含义         1:数组默认长度         2:这是一个共享的空数组实例,用于明确创建长度为0时的ArrayList ,比如通过 new ArrayList<>(0),ArrayList 内部的数组 elementData 会指向这个 EMPTY_EL