【源码库】在调用 createApp 时,Vue 为我们做了那些工作?

news2025/4/4 3:42:03

在使用Vue3时,我们需要使用createApp来创建一个应用实例,然后使用mount方法将应用挂载到某个DOM节点上。

那么在调用createApp时,Vue再背后做了些什么事情呢?今天就来扒一扒Vue3的源码,看看调用createApp发生了些什么。

大家好,这里是田八的【源码&库】系列,Vue3的源码阅读计划,Vue3的源码阅读计划不出意外每周一更,欢迎大家关注。

如果想一起交流的话,可以点击这里一起共同交流成长

系列章节:

  • 【源码&库】跟着 Vue3 学习前端模块化
  • 【源码&库】在调用 createApp 时,Vue 为我们做了那些工作?

寻找入口

在上一章中,我们我们已经将Vue3的源码下载下来了,并且已经知道如何编译源码了,先看一下Vue3的源码目录:

image.png

packages目录下的包就是Vue3的所有源码了,编译之后会在每个工程包下面生成一个dist目录,里面就是编译后的文件。

这里我框出了vue包,这个大家都熟悉,打开vue包下的package.json文件,可以看到unpkg字段指向了dist/vue.global.js文件,这个文件就是Vue3的全局版本,我们可以直接在浏览器中引入这个文件来使用Vue3

代码逻辑基本上都是相同的,用打包后的文件来分析源码,可以更加直观的看到源码的逻辑,因为Vue在设计的时候会考虑其他平台,如果直接通过源码来查看会有额外的心智负担。

具体如何使用每个打包后的文件,可以查看vue包下的README.md文件,如果只是想分析源码,且不想那么麻烦,可以直接使用dist/vue.global.js文件。

如果想了解Vue3的目录结构和模块划分可以使用vue.esm-bundler.js文件,这个文件是Vue3ESM版本,会通过import来引入其他模块,这样就可以直接看到Vue3的模块划分。

本系列就会通过vue.esm-bundler.js文件来分析Vue3的源码,并且会通过边分析边动手的方式来学习Vue3的源码。

image.png

使用

我们先来看一下Vue3的使用方式:

import {createApp} from 'vue'
import App from './App.vue'

const app = createApp(App)
app.mount('#app')

Vue3中,我们需要使用createApp来创建一个应用实例,然后使用mount方法将应用挂载到某个DOM节点上。

createApp是从vue包中导出的一个方法,它接收一个组件作为参数,然后返回一个应用实例。

入口 createApp

vuepackage.json可以看到,module字段指向了dist/vue.esm-bundler.js文件,这个文件是Vue3ESM版本,我们可以直接使用import来引入Vue3

createApp方法并不在这个包中,而是在runtime-dom包中,这个文件是直接全部导出runtime-dom包中的内容:

export * from '@vue/runtime-dom';

不用怀疑@vue/runtime-dom指向的就是runtime-dom包,使用esm版本就直接找xxx.esm-bundler.js文件,使用cjs版本就直接找xxx.cjs.js文件,后面不会再提到这个问题。

打开runtime-dom.esm-bundler.js文件,可以看到createApp方法:

import {  } from '@vue/runtime-core';
export * from '@vue/runtime-core';
import {  } from '@vue/shared';

// ... 省略n多代码

function createApp(...args) {
    // ...
}

export {createApp};

可以看到runtime-dom包中还引用了runtime-core包和shared包,现在找到入口文件了,在分析直接可以先搭建一个简单的代码分析和测试的环境,这样方便自己验证并且可以直接看到代码的执行结果。

demo环境可以直接在本地搭建,也可以使用codesandboxstackblitz等在线环境,这里使用codesandbox,后续demo的代码都会放在codesandbox上,文末会有链接。

当然大家也可以直接在本地搭建一个demo环境,这里就不再赘述了。

源码分析

上面的环境都准备好了之后就可以直接开始分析Vue3的源码了,我们先来看一下createApp方法的实现;

createApp

const createApp = (...args) => {
    const app = ensureRenderer().createApp(...args);

    const {mount} = app;
    app.mount = (containerOrSelector) => {
        // ...
    };

    return app;
}

createApp方法接收一个组件作为参数,然后调用ensureRenderer方法;

这个方法的作用是确保渲染器存在,如果不存在就创建一个渲染器,然后调用渲染器的createApp方法,这个方法的作用是创建一个应用实例,然后将这个应用实例返回,相当于一个单例模式。

let renderer;
const ensureRenderer = () => renderer || (renderer = createRenderer(rendererOptions));

这里的rendererOptions是一些渲染器的配置,主要的作用是用来操作DOM的,这里不做过多的介绍,后面会有专门的文章来介绍。

现在先简单的来认识一下rendererOptions,这个里面会有两个方法后面会用到:

const rendererOptions = {
    insert: (child, parent, anchor) => {
        parent.insertBefore(child, anchor || null);
    },
    createText: text => document.createTextNode(text),
}

现在我们先简单的动手实现一下createApp方法,新建一个runtime-dom.js文件,然后内容如下:

import { createRenderer } from "./runtime-core";

const createApp = (...args) => {
  const rendererOptions = {
    insert: (child, parent, anchor) => {
      parent.insertBefore(child, anchor || null);
    },
    createText: (text) => document.createTextNode(text)
  };

  const app = createRenderer(rendererOptions).createApp(...args);
  const { mount } = app;
  app.mount = (containerOrSelector) => {
    //...后面分析再补上
  };
  return app;
};

export { createApp };

现在可以看到我们在实现createApp方法的时候,直接调用了createRenderer方法,这个方法是创建渲染器的方法,这个方法的实现在runtime-core包中;

所以我们需要补上runtime-core包中的createRenderer方法的实现;

createRenderer

createRenderer源码实现如下:

function createRenderer(options) {
    return baseCreateRenderer(options);
}

// implementation
function baseCreateRenderer(options, createHydrationFns) {
    // 省略 n 多代码,都是函数定义,并会立即执行,暂时对结果不会有影响
    
    return {
        render,
        hydrate,
        createApp: createAppAPI(render, hydrate)
    };
}

createRenderer内部返回baseCreateRenderer方法的执行结果,这个方法的作用会返回renderhydratecreateApp三个方法;

而我们最后需要调用的createApp方法就是在这三个方法中的其中一个,而createApp方法的是通过createAppAPI方法创建的,同时剩下的两个方法renderhydrate也是在createAppAPI方法中被调用的,所以我们还需要看一下createAppAPI方法的实现;

createAppAPI

createAppAPI方法的实现如下:

function createAppContext() {
    return {
        app: null,
        config: {
            isNativeTag: NO,
            performance: false,
            globalProperties: {},
            optionMergeStrategies: {},
            errorHandler: undefined,
            warnHandler: undefined,
            compilerOptions: {}
        },
        mixins: [],
        components: {},
        directives: {},
        provides: Object.create(null),
        optionsCache: new WeakMap(),
        propsCache: new WeakMap(),
        emitsCache: new WeakMap()
    };
}
// 这个变量是用来统计创建的应用实例的个数
let uid$1 = 0;
function createAppAPI(render, hydrate) {
    // 返回一个函数,这里主要是通过闭包来缓存上面传入的参数
    return function createApp(rootComponent, rootProps = null) {
        // rootComponent 就是我们传入的根组件,这里会做一些校验
        
        // 如果传递的不是一个函数,那么就做一个浅拷贝
        if (!isFunction(rootComponent)) {
            rootComponent = Object.assign({}, rootComponent);
        }
        
        // rootProps 就是我们传入的根组件的 props,这个参数必须是一个对象
        if (rootProps != null && !isObject(rootProps)) {
            (process.env.NODE_ENV !== 'production') && warn(`root props passed to app.mount() must be an object.`);
            rootProps = null;
        }
        
        // 创建上下文对象,在上面定义,就是返回一个对象
        const context = createAppContext();
        
        // 通过 use 创建的插件都存在这里
        const installedPlugins = new Set();
        
        // 是否已经挂载
        let isMounted = false;
        
        // 创建 app 对象
        const app = (context.app = {
            _uid: uid$1++,
            _component: rootComponent,
            _props: rootProps,
            _container: null,
            _context: context,
            _instance: null,
            version,
            get config() {
                // ...
            },
            set config(v) {
                // ...
            },
            use(plugin, ...options) {
                // ...
            },
            mixin(mixin) {
                // ...
            },
            component(name, component) {
                // ...
            },
            directive(name, directive) {
                // ...
            },
            mount(rootContainer, isHydrate, isSVG) {
                // ...
            },
            unmount() {
                // ...
            },
            provide(key, value) {
                // ...
            }
        });
        
        // 返回 app 对象
        return app;
    };
}

看到这里,我们就可以知道,createApp方法的实现其实就是在createAppAPI方法中返回一个函数,这个函数就是createApp方法;

这个方法并没有多么特殊,就是返回了一堆对象,这些对象就是我们在使用createApp方法时,可以调用的方法;

这里可以看到我们常用的usemixincomponentdirectivemountunmountprovide等方法都是在app对象上的,也是通过这个函数制造并返回的;

现在我们继续完善我们的学习demo代码,现在新建一个runtime-core.js文件夹,然后把上面的代码复制进去;

但是我们不能全都都直接照搬,上面的对象这么多的属性我们只需要保留mount,因为还需要挂载才能看到效果,demo代码如下:

function createRenderer(options) {
    // 先省略 render 和 hydrate 方法的实现,后面会讲到
    
   return {
        render,
        hydrate,
        createApp: createAppAPI(render, hydrate)
    };
}

function createAppAPI(render, hydrate) {
    return function createApp(rootComponent, rootProps = null) {
        // 省略参数校验
        rootComponent = Object.assign({}, rootComponent);
        
        // 省略上下文的创建
        const context = {
            app: null
        }
        
        // 忽略其他函数的实现,只保留 mount 函数和私有变量
        let isMounted = false;
        const app = (context.app = {
            _uid: uid$1++,
            _component: rootComponent,
            _props: rootProps,
            _container: null,
            _context: context,
            _instance: null,
            mount(rootContainer, isHydrate, isSVG) {
                // ...
            },
        });
        
        return app;
    };
}

这样我们就完成了createApp函数的简化版实现,接下来我们就可以开始挂载了;

mount 挂载

上面我们已经学习到了createApp函数的实现,现在还需要通过mount方法来挂载我们的根组件,才能验证我们的demo代码是否正确;

我们在调用createApp方法时,会返回一个app对象,这个对象上有一个mount方法,我们需要通过这个方法来挂载我们的根组件;

在这之前,我们看到了createApp的实现中重写了mount方法,如下:

const createApp = (...args) => {
    // ...省略其他代码
    
    // 备份 mount 方法 
    const { mount } = app;
    
    // 重写 mount 方法
    app.mount = (containerOrSelector) => {
        // 获取挂载的容器
        const container = normalizeContainer(containerOrSelector);
        if (!container)
            return;
        
        // _component 指向的是 createApp 传入的根组件
        const component = app._component;
        
        // 验证根组件是否是一个对象,并且有 render 和 template 两个属性之一
        if (!isFunction(component) && !component.render && !component.template) {
            // __UNSAFE__
            // Reason: potential execution of JS expressions in in-DOM template.
            // The user must make sure the in-DOM template is trusted. If it's
            // rendered by the server, the template should not contain any user data.
            // 确保模板是可信的,因为模板可能会有 JS 表达式,具体可以翻译上面的注释
            component.template = container.innerHTML;
        }
        
        // clear content before mounting
        // 挂载前清空容器
        container.innerHTML = '';
        
        // 正式挂载
        const proxy = mount(container, false, container instanceof SVGElement);
        
        // 挂载完成
        if (container instanceof Element) {
            // 清除容器的 v-cloak 属性,这也就是我们经常看到的 v-cloak 的作用
            container.removeAttribute('v-cloak');
            
            // 设置容器的 data-v-app 属性
            container.setAttribute('data-v-app', '');
        }
        
        // 返回根组件的实例
        return proxy;
    };
    return app;
}

上面重写的mount方法中,其实最主要的做的是三件事:

  1. 获取挂载的容器
  2. 调用原本的mount方法挂载根组件
  3. 为容器设置vue的专属属性

现在到我们动手实现一个简易版的mount方法了;

// 备份 mount 方法 
const { mount } = app;

// 重写 mount 方法
app.mount = (containerOrSelector) => {
    // 获取挂载的容器
    const container = document.querySelector(containerOrSelector);
    if (!container)
        return;
    
    const component = app._component;
    container.innerHTML = '';
    
    // 正式挂载
    return mount(container, false, container instanceof SVGElement);
};

这里的挂载其实还是使用的是createApp函数中的mount方法,我们可以看到mount方法的实现如下:

function mount(rootContainer, isHydrate, isSVG) {
    // 判断是否已经挂载
    if (!isMounted) {
        // 这里的 #5571 是一个 issue 的 id,可以在 github 上搜索,这是一个在相同容器上重复挂载的问题,这里只做提示,不做处理
        // #5571
        if ((process.env.NODE_ENV !== 'production') && rootContainer.__vue_app__) {
            warn(`There is already an app instance mounted on the host container.\n` +
                ` If you want to mount another app on the same host container,` +
                ` you need to unmount the previous app by calling `app.unmount()` first.`);
        }
        
        // 通过在 createApp 中传递的参数来创建虚拟节点
        const vnode = createVNode(rootComponent, rootProps);
        
        // store app context on the root VNode.
        // this will be set on the root instance on initial mount.
        // 上面有注释,在根节点上挂载 app 上下文,这个上下文会在挂载时设置到根实例上
        vnode.appContext = context;
        
        // HMR root reload
        // 热更新
        if ((process.env.NODE_ENV !== 'production')) {
            context.reload = () => {
                render(cloneVNode(vnode), rootContainer, isSVG);
            };
        }
        
        // 通过其他的方式挂载,这里不一定指代的是服务端渲染,也可能是其他的方式
        // 这一块可以通过创建渲染器的源码可以看出,我们日常在客户端渲染,不会使用到这一块,这里只是做提示,不做具体的分析
        if (isHydrate && hydrate) {
            hydrate(vnode, rootContainer);
        }
        
        // 其他情况下,直接通过 render 函数挂载
        // render 函数在 createRenderer 中定义,传递到 createAppAPI 中,通过闭包缓存下来的
        else {
            render(vnode, rootContainer, isSVG);
        }
        
        // 挂载完成后,设置 isMounted 为 true
        isMounted = true;
        
        // 设置 app 实例的 _container 属性,指向挂载的容器
        app._container = rootContainer;
        
        // 挂载的容器上挂载 app 实例,也就是说我们可以通过容器找到 app 实例
        rootContainer.__vue_app__ = app;
        
        // 非生产环境默认开启 devtools,也可以通过全局配置来开启或关闭
        // __VUE_PROD_DEVTOOLS__ 可以通过自己使用的构建工具来配置,这里只做提示
        if ((process.env.NODE_ENV !== 'production') || __VUE_PROD_DEVTOOLS__) {
            app._instance = vnode.component;
            devtoolsInitApp(app, version);
        }
        
        // 返回 app 实例,这里不做具体的分析
        return getExposeProxy(vnode.component) || vnode.component.proxy;
    }
    
    // 如果已经挂载过则输出提示消息,在非生产环境下
    else if ((process.env.NODE_ENV !== 'production')) {
        warn(`App has already been mounted.\n` +
            `If you want to remount the same app, move your app creation logic ` +
            `into a factory function and create fresh app instances for each ` +
            `mount - e.g. `const createMyApp = () => createApp(App)``);
    }
}

通过上面的一通分析,其实挂载主要就是用的两个函数将内容渲染到容器中;

  1. createVNode 创建虚拟节点
  2. render 渲染虚拟节点

我们这里就实现一个简易版的mount函数,来模拟挂载过程,代码如下:

function mount(rootContainer, isHydrate) {
    // createApp 中传递的参数在我们这里肯定是一个对象,所以这里不做创建虚拟节点的操作,而是模拟一个虚拟节点
    const vnode = {
        type: rootComponent,
        children: [],
        component: null,
    }

    // 通过 render 函数渲染虚拟节点
    render(vnode, rootContainer);
    
    // 返回 app 实例
    return vnode.component
}

虚拟节点

虚拟节点在Vue中已经是非常常见的概念了,其实就是一个js对象,包含了dom的一些属性,比如tagpropschildren等等;

Vue3中维护了一套自己的虚拟节点,大概信息如下:

export interface VNode {
    __v_isVNode: true;
    __v_skip: true;
    type: VNodeTypes;
    props: VNodeProps | null;
    key: Key | null;
    ref: Ref<null> | null;
    scopeId: string | null;
    children: VNodeNormalizedChildren;
    component: ComponentInternalInstance | null;
    suspense: SuspenseBoundary | null;
    dirs: DirectiveBinding[] | null;
    transition: TransitionHooks<null> | null;
    el: RendererElement | null;
    anchor: RendererNode | null;
    target: RendererNode | null;
    targetAnchor: RendererNode | null;
    staticCount: number;
    shapeFlag: ShapeFlags;
    patchFlag: number;
    dynamicProps: string[] | null;
    dynamicChildren: VNode[] | null;
    appContext: AppContext | null;
}

完整的type信息太多,这里就只贴VNode的相关定义,而且这些在Vue的实现中也没有那么简单,这一章不做具体的分析,只是做一个简单的概念介绍;

render

render函数是在讲createRenderer的时候出现的,是在baseCreateRenderer中定义的,具体源码如下:

function baseCreateRenderer(options, createHydrationFns) {
    // ...
    
    // 创建 render 函数
    const render = (vnode, container, isSVG) => {
        // 如果 vnode 不存在,并且容器是发生过渲染,那么将执行卸载操作
        if (vnode == null) {
            // container._vnode 指向的是上一次渲染的 vnode,在这个函数的最后一行
            if (container._vnode) {
                unmount(container._vnode, null, null, true);
            }
        }
        
        // 执行 patch 操作,这里不做具体的分析,牵扯太大,后面会单独讲
        else {
            patch(container._vnode || null, vnode, container, null, null, null, isSVG);
        }
        
        // 刷新任务队列,通常指代的是各种回调函数,比如生命周期函数、watcher、nextTick 等等
        // 这里不做具体的分析,后面会单独讲
        flushPreFlushCbs();
        flushPostFlushCbs();
        
        // 记录 vnode,现在的 vnode 已经是上一次渲染的 vnode 了
        container._vnode = vnode;
    };
    
    // ...
    
    return {
        render,
        hydrate,
        createApp: createAppAPI(render, hydrate)
    };
}

render函数的主要作用就是将虚拟节点渲染到容器中,unmount函数用来卸载容器中的内容,patch函数用来更新容器中的内容;

现在来实现一个简易版的render函数:

const render = (vnode, container) => {
    
    patch(container._vnode || null, vnode, container);
    
    // 记录 vnode,现在的 vnode 已经是上一次渲染的 vnode 了
    container._vnode = vnode;
}

unmount函数不是我们这次主要学习的内容,所以这里不做具体的分析;

patch函数是Vue中最核心的函数,这次也不做具体的分析,后面会单独讲,但是要验证我们这次的学习成果,所以我们需要一个只有挂载功能的patch函数,这里我们就自己实现一个简单的patch函数;

patch

patch函数的主要作用就是将虚拟节点渲染到容器中,patch函数也是在baseCreateRenderer中定义的;

patch函数这次就不看了,因为内部的实现会牵扯到非常多的内容,这次只是它的出现只是走个过场,后面会单独讲;

我们这次的目的只是验证我们这次源码学习的成成果,所以我们只需要一个只有挂载功能的patch函数,这里我们就自己实现一个简单的patch函数;

// options 是在创建渲染器的时候传入的,还记得在 createApp 的实现中,我们传入了一个有 insert 和 createText 方法的对象吗?不记得可以往上翻翻
const { insert: hostInsert, createText: hostCreateText} = options;
// Note: functions inside this closure should use `const xxx = () => {}`
// style in order to prevent being inlined by minifiers.
/**
 * 简易版的实现,只是删除了一些不必要的逻辑
 * @param n1 上一次渲染的 vnode
 * @param n2 当前需要渲染的 vnode
 * @param container 容器
 * @param anchor 锚点, 用来标记插入的位置
 */
const patch = (n1, n2, container, anchor = null) => {
    // 上一次渲染的 vnode 和当前需要渲染的 vnode 是同一个 vnode,那么就不需要做任何操作
    if (n1 === n2) {
        return;
    }
    
    // 获取当前需要渲染的 vnode 的类型
    const { type } = n2;
    switch (type) {
        // 如果是文本节点,那么就直接创建文本节点,然后插入到容器中
        case Text:
            processText(n1, n2, container, anchor);
            break;
            
        // 还会有其他的类型,这里不做具体的分析,后面会单独讲
            
        // 其他的情况也会有很多种情况,这里统一当做是组件处理
        default:
            processComponent(n1, n2, container, anchor);
    }
};

patch函数的主要作用就是将虚拟节点正确的渲染到容器中,这里我们只实现了文本节点和组件的渲染,其他的类型的节点,后面会单独讲;

而我们在使用createApp的时候,通常会传入一个根组件,这个根组件就会走到processComponent函数中;

所以我们这里还需要实现了一个简单的processComponent函数;

const processComponent = (n1, n2, container, anchor) => {
   if (n1 == null) {
       mountComponent(n2, container, anchor);
   }
   // else {
   //     updateComponent(n1, n2, optimized);
   // }
};

processComponent函数也是定义在baseCreateRenderer中的,这里还是和patch函数一样,只是实现了一个简单的功能,后面会单独讲;

processComponent函数做了两件事,一个是挂载组件,一个是更新组件,这里我们只实现了挂载组件的功能;

挂载组件是通过mountComponent函数实现的,这个函数也是定义在baseCreateRenderer中的,但是我们这次就不再继续深入内部调用了,直接实现一个简易的:

const mountComponent = (initialVNode, container, anchor) => {
    // 通过调用组件的 render 方法,获取组件的 vnode
    const subTree = initialVNode.type.render.call(null);
    
    // 将组件的 vnode 渲染到容器中,直接调用 patch 函数
    patch(null, subTree, container, anchor);
};

这样我们就实现了一个简易版的挂载组件的功能,这里我们只是简单的调用了组件的render方法,render方法会返回一个vnode,然后调用patch函数将vnode渲染到容器中;

现在回头看看patch函数,还差一个processText函数没有实现,这个函数也是定义在baseCreateRenderer中的,这个比较简单,下面的代码就是实现的processText函数:

const processText = (n1, n2, container, anchor) => {
    if (n1 == null) {
        hostInsert((n2.el = hostCreateText(n2.children)), container, anchor);
    }
    // else {
    //     const el = (n2.el = n1.el);
    //     if (n2.children !== n1.children) {
    //         hostSetText(el, n2.children);
    //     }
    // }
};

我这里屏蔽掉了更新的操作,这里只管挂载,这里的hostInserthostCreateText函数就是在我们实现简易patch函数的时候,在patch函数实现的上面,通过解构赋值获取的,没印象可以回去看看;

验证

现在我们已经实现了一个简易版的createApp函数,并且我们可以通过createApp函数创建一个应用,然后通过mount方法将应用挂载到容器中;

我们可以通过下面的代码来验证一下:

import { createApp } from "./runtime-dom";

const app = createApp({
  render() {
    return {
      type: "Text",
      children: "hello world"
    };
  }
});

app.mount("#app");

源码在codesandbox上面,可以直接查看:https://codesandbox.io/s/gallant-sun-khjot0?file=/src/main.js

总结

我们通过阅读Vue3的源码,了解了Vue3createApp函数的实现,createApp函数是Vue3的入口函数,通过createApp函数我们可以创建一个应用;

createApp的实现是借助了createRenderer函数,createRenderer的实现就是包装了baseCreateRenderer

baseCreateRenderer函数是一个工厂函数,通过baseCreateRenderer函数我们可以创建一个渲染器;

baseCreateRenderer函数接收一个options对象,这个options对象中包含了一些渲染器的配置,比如insertcreateText等;

这些配置是在runtime-dom中实现的,runtime-dom中的createApp函数会将这些配置透传递给baseCreateRenderer函数,然后baseCreateRenderer函数会返回一个渲染器,这个渲染器中有一个函数就是createApp

createApp函数接收一个组件,然后返回一个应用,这个应用中有一个mount方法,这个mount方法就是用来将应用挂载到容器中的;

createApp中重写了mount方法,内部的实现是通过调用渲染器的mount方法;

这个mount方法是在baseCreateRenderer函数中实现的,baseCreateRenderer函数中的mount方法会调用patch函数;

patch函数内部会做很多的事情,虽然我们这里只实现了挂载的逻辑,但是也是粗窥了patch函数的内部一些逻辑;

最后我们实现了一个精简版的createApp函数,通过这个函数我们可以创建一个应用,然后通过mount方法将应用挂载到容器中,这个过程中我们也了解了Vue3的一些实现细节;

这次就到这里,下次我们会继续深入了解Vue3的源码,希望大家能够多多支持,谢谢大家!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/397136.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

八股文系列:Java虚拟机(JVM)

说一下 JVM 的主要组成部分及其作用&#xff1f; JVM包含两个子系统和两个组件&#xff0c;两个子系统为Class loader(类装载)、 Execution engine(执行引擎)&#xff1b;两个组件为Runtime data area(运行时数据 区)、Native Interface(本地接口)。 Class loader(类装载)&…

IDEA插件开发入门.02

前言许久没更新IDEA插件开发系列了。最近刚好在汇总日常开发中常见的代码“异味”&#xff0c;共享文档复制黏贴略显麻烦&#xff0c;所以想着是否可以搞一个IDEA插件来帮忙收集常见代码&#xff0c;毕竟IDEA作为后端程序员必备的开发工具&#xff0c;显然会方便很多。于是&…

【JavaEE初阶】第四节.文件操作 和 IO (上篇)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、文件 1.1 文件的概念 1.2 文件的路径二、 Java中文件系统操作 2.1 File类的属性 2.2 File类的构造方法 2.3 File类的方法 …

ArangoDB

介绍 ArangoDB 是一个原生的多模型开源数据库&#xff0c;具有灵活的文档、图形和键值数据模型。使用方便的类似 SQL 的查询语言或 JavaScript 扩展构建高性能应用程序。主要特点 在集群上安装 ArangoDB —— 安装简单灵活的数据建模&#xff1a;数据建模为键值对、文档或图表的…

企业信息化,电商商品详情API接口,数据返回值说明,商品详情,关键词搜索,价格监控,卖家买家订单等相关数据

都在说API&#xff0c;API到底是什么&#xff1f; 对于很多非IT人士而言&#xff0c;API ≈ 听不懂。 其实日常生活中&#xff0c;我们有很多类似API的场景&#xff0c;比如&#xff1a; 电脑需要调用手机里面的信息&#xff0c;这时候你会拿一根数据线将电脑手机连接起来&a…

[Tomcat]解决IDEA中的Tomcat中文乱码问题

目录 1、IDEA 2、VM options 3、IDEA启动程序的存放目录 4、Tomcat 写在前面&#xff1a;此方法亲测有效&#xff01;&#xff01;&#xff01; 1、IDEA 2、VM options 加上这两行&#xff1a; -Dfile.encodingUTF-8 -Dconsole.encodingUTF-8 3、IDEA启动程序的存放目录…

置信椭圆(误差椭圆)详解

文章目录Part.I 预备知识Chap.I 一些概念Chap.II 主成分分析Chap.III Matlab 函数 randnChap.IV Matlab 函数 pcaPart.II 置信椭圆的含义Chap.I 一个 Matlab 实例Sec.I 两个不相关变量的特征Sec.II 两个相关变量的特征Chap.II 变换阵 (解相关矩阵) 的求解ReferencePart.I 预备知…

配置VM虚拟机Centos7网络

配置VM虚拟机Centos7网络 第一步&#xff0c;进入虚拟机设置选中【网络适配器】选择【NAT模式】 第二步&#xff0c;进入windows【控制面板\网络和 Internet\网络连接】设置网络状态。 我们选择【VMnet8】 点击【属性】查看它的网络配置 2 .我们找到【Internet 协议版本 4(TCP…

一小时快速上手GoWeb开发之Gin框架

Go是一门正在快速增长的编程语言&#xff0c;专为构建简单、快速且可靠的软件而设计。golang提供的net/http库已经很好了&#xff0c;对于http的协议的实现非常好&#xff0c;基于此再造框架&#xff0c;也不会是难事&#xff0c;因此生态中出现了很多框架。 Gin&#xff1a;G…

【rust-grpc-proxy】在k8s中,自动注入代理到pod中,再不必为grpc调试而烦恼

目录前言原理sidecarwebhook实现安装k8s设置webhook使用尾语前言 rust-grpc-proxy 目前功能基本完善。是时候上环境开始应用了。 之前考虑是gateway模式或者sidecar模式。 思考良久之后&#xff0c;觉得两种模式都有使用场景&#xff0c;那就都支持。本次就带来sidecar模式的食…

实战小项目之视频监控(1-1)

实战小项目之视频监控&#xff08;1-1&#xff09; 目前常见的视频监控和视频直播都是使用了 RTMP 和 RTSP 流媒体传输协议等。 RTSP&#xff08;Real-Time Stream Protocol&#xff09;由 Real Networks 和 Netscape 共同提出的&#xff0c;基于文本的多媒体播放 控制协议。…

计算机科学导论笔记(五)

目录 七、操作系统 7.1 引言 7.1.1 操纵系统 7.1.2 自举过程 7.2 演化 7.3 组成部分 7.3.1 用户界面 7.3.2 内存管理器 7.3.3 进程管理器 7.3.4 设备管理器 7.3.5 文件管理器 7.4 操作系统 7.4.1 UNIX 7.4.2 Linux 7.4.3 Windows 七、操作系统 7.1 引言 计算机…

阿里云手机短信登录

阿里云短信服务介绍阿里云短信服务&#xff08;Short Message Service&#xff09;是广大企业客户快速触达手机用户所优选使用的通信能力。调用API或用群发助手&#xff0c;即可发送验证码、通知类和营销类短信&#xff1b;国内验证短信秒级触达&#xff0c;到达率最高可达99%&…

三维重建(单目、双目、多目、点云、SFM、SLAM)

1 相机几何与标定1.1 相机模型中的坐标系1.2 四种坐标系之间的转换1.3 相机内参1.4 相机标定2 单目三维重建2.1 NeuralRecon三维重建定义 在计算机视觉中, 三维重建是指根据单视图或者多视图的图像重建三维信息的过程. 由于单视频的信息不完全,因此三维重建需要利用经验知识. 而…

DJ1-2 操作系统引论

目录 一、操作系统的发展过程 1. 无操作系统 2. 单道批处理系统 3. 多道批处理系统 4. 分时操作系统 5. 实时系统 二、操作系统的基本特征 1. 并发性 2. 共享性 3. 虚拟性 4. 异步性 三、操作系统的主要功能 1. 处理机管理功能 2. 存储器管理功能 3. 设备管理功…

python pca/svd原理及应用

PCA的定义主成分分析&#xff0c;即Principal Component Analysis&#xff08;PCA&#xff09;&#xff0c;是多元统计中的重要内容&#xff0c;也广泛应用于机器学习和其它领域。它的主要作用是对高维数据进行降维。PCA把原先的n个特征用数目更少的k个特征取代&#xff0c;新特…

Elasticsearch使用——高级篇

1.数据聚合**聚合&#xff08;aggregations&#xff09;**可以让我们极其方便的实现对数据的统计、分析、运算。例如&#xff1a;什么品牌的手机最受欢迎&#xff1f;这些手机的平均价格、最高价格、最低价格&#xff1f;这些手机每月的销售情况如何&#xff1f;实现这些统计功…

【ns-3】添加nr(5G-LENA)模块

文章目录前言1. 下载5G-LENA源代码2. 配置并重新构建ns-3项目参考文献前言 本篇以ns-3.37为例介绍如何在ns-3中添加nr&#xff08;5G-LENA&#xff09;模块 [1]。5G-LENA是一个由Mobile Networks group CTTC&#xff08;Centre Tecnolgic de Telecomunicacions de Catalunya&a…

微信小程序开发学习笔记1——安心食疗

2.1小程序代码结构 目录结构 1.小程序的目录 pages:小程序的页面 页面路径 一个小程序页面由四个文件组成&#xff0c;分别是&#xff1a; xxx.js xxx.wxml xxx.json xxx.wxss 注意&#xff1a;为了方便开发者减少配置项&#xff0c;描述页面的四个文件必须具有相同的路径与文…

MySQL(二)视图、锁、存储过程、触发器、锁以及常用工具

MySQL进阶视图检查选项视图的更新存储过程存储过程基本语法变量系统变量用户自定义变量局部变量if判断参数casewhile循环repeat循环loop循环cursor游标handler条件处理程序存储函数触发器锁全局锁表级锁表锁元数据锁意向锁行级锁行锁间隙锁&临键锁InnoDB引擎逻辑存储结构事…