Jinuss's blog Jinuss's blog
首页
  • 源码合集

    • Leaflet源码分析
    • Openlayers源码合集
    • vue3源码
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • 学习
  • 实用技巧
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

东流

Web、WebGIS技术博客
首页
  • 源码合集

    • Leaflet源码分析
    • Openlayers源码合集
    • vue3源码
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • 学习
  • 实用技巧
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • reactivity响应式

  • runtime-core运行时核心模块

  • runtime-dom运行时DOM模块

    • createApp创建实例解析
      • 概览
      • 源码解析
        • createApp
        • ensureRenderer
        • patchProp
        • nodeOps
    • patchProp属性补丁解析
  • 5.18源码学习》
  • runtime-dom运行时DOM模块
东流
2025-09-15
目录

createApp创建实例解析

# 概览

在vue3项目中,通常是通过const app = createApp(根组件)来创建一个vue实例,app即为vue实例。本文主要介绍createApp这个API背后究竟 是做了那些工作。

# 源码解析

createApp入口是在packages\runtime-dom\src\index.ts中实现的,createApp需要结合vue3的渲染器使用,而渲染器是在packages\runtime-core\src\renderer.ts中实现的,本文主要介绍入口部分,关于渲染器需要几篇文章才能分析完。

# createApp

createApp的内部就是将参数传给渲染器的createApp方法,返回一个app实例,然后重写了mount方法,用于挂载DOM根节点。

其源码实现如下:

const createApp = (...args) => {
    // 通过渲染器返回app实例
    const app = ensureRenderer().createApp(...args);
    const { mount } = app;
    // 重写app实例的mount方法,用于挂载
    app.mount = (containerOrSelector) => {
        // containerOrSelector是一个css选择器,normalizeContainer是一个内部方法,会返回DOM容器 
        const container = normalizeContainer(containerOrSelector);
        // 若容器不存在,就返回
        if (!container) {
            return;
        }
        // 根节点,实际上就是参数args
        const component = app._component;
        // 判断若根节点不是一个参数,并且根节点上也不存在render和template,则将容器的子元素作为根节点的template
        if (!shared.isFunction(component) && !component.render && !component.template) {
            component.template = container.innerHTML;
        }
        // 若容器是一个元素节点,则将其内容清空
        if (container.nodeType === 1) {
            container.textContent = "";
        }
        // 调用渲染器的mount方法挂载,mount方法会返回根组件实例的代理对象
        const proxy = mount(container, false, resolveRootNamespace(container));
        // 判断若容器是DOM元素,则移除属性v-cloak,添加属性data-v-app
        if (container instanceof Element) {
            container.removeAttribute("v-cloak");
            container.setAttribute("data-v-app", "");
        }
        // 返回代理对象proxy
        return proxy;
    }
    // 最后返回应用实例app
    return app;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

# ensureRenderer

ensureRenderer就是调用runtimeCore的createRenderer创建一个渲染器,保存在renderer中,构造器的参数rendererOptions包括patchProp和nodeOps。

let renderer;
const rendererOptions =  shared.extend({ patchProp }, nodeOps);
function ensureRenderer() {
    return renderer || (renderer = runtimeCore.createRenderer(rendererOptions));
}
1
2
3
4
5

# patchProp

patchProp主要作用是根据虚拟 DOM 的变化来更新实际的 DOM 元素的属性,它负责处理添加、删除和更新 DOM 元素的属性,实现 Vue 组件的属性动态更新。

patchProp的源码实现如下:

const patchProp = (el, key, prevValue, nextValue, namespace, parentComponent) => {
    // el:目标DOM元素 key:属性名 preValue:旧值 nextValue:新值 namespace:命名空间  parentComponent:父组件实例 
    
    // isSVG表示是否是SVG元素
    const isSVG = namespace === 'svg';
    if (key === 'class') {
        // 处理class属性
        patchClass(el, nextValue, isSVG);
    } else if (key === "style") {
        // 处理style样式属性
        patchStyle(key, prevValue, nextValue);
    } else if (shared.isOn(key)) {
        // 处理事件监听器,isOn判断属性名即事件名称是否以on开头
        if (!shared.isModeListener(key)) {
          // isModeListener过滤掉onUpdate特殊事件 
          patchEvent(el, key, prevValue, nextValue, parentComponent);
        }
    } else if (key[0] === '.' ? (key = key.slice(1), true) : 
               key[0] === "^" ? (key = key.slice(1), false) : 
               shouldSetAsProp(el, key, nextValue, isSVG)) {
        // 处理DOM属性        
        patchDOMProp(el, key, nextValue);
        
        if (!el.tagName.includes("-") && (key === "value" || key === "checked" || key === "selected")) {
        // 处理特殊属性 value/checked/selected  
          patchAttr(el, key, nextValue, isSVG, parentComponent, key !== "value")
        }
    } else if (el._isVueCE && (/[A-Z]/.test(key) || !shared.isstring(nextValue))) {
        // 处理Web Components属性
        patchDOMProp(el, shared, camelize(key), nextValue, parentComponent, key)
    } else {
        // 处理其他属性
        if (key === 'true-value') {
            el._trueValue = nextValue;
        } else if (key === 'false-value') {
            el._falseValue = nextValue;
        }
        patchAttr(el, key, nextValue, isSVG)
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

# nodeOps

nodeOps就是一个对象,该对象包含一些操作Node节点的方法。

const svgNS = "http://www.w3.org/2000/svg";
const mathmlNS = "http://www.w3.org/1998/Math/MathML";
const doc = typeof document !== "undefined" ? document : null;
const templateContainer = doc && /* @__PURE__ */ doc.createElement("template");

const nodeOps = {
    // 将子节点插入到父节点的指定位置
    insert: (child, parent, anchor) => {
        parent.insertBefore(child, anchor || null);
    },
    // 从父节点中移除子节点
    remove: (child) => {
        const parent = child.parentNode;
        if (parent) {
            parent.removeChild(child)
        }
    },
    // 创建一个新的 DOM 元素节点
    createElement: (tag, namespace, is, props) => {
        const el = namespace === 'svg' ? doc.createElementNS(svgNS, tag) : namespace === 'mathml' ? doc.createElementNS(mathmlNS, tag) : is ? doc.createElement(tag, { is }) : doc.createElement(tag);
        if (tag === "select" && props && props.multiple != null) {
            el.setAttribute("multiple", props.multiple);
        }
        return el;
    },
    // 创建一个包含指定文本的文本节点
    createText: (text) => doc.createTextNode(text),
    // 创建一个注释节点
    createComment: () => doc.createComment(text),
    // 设置文本节点的文本内容
    setText: (node, text) => {
        node.nodeValue = text;
    },
    // 设置元素节点的文本内容
    setElementText: (el, text) => { el.textContent = text },
    // 获取节点的父节点
    parentNode: (node) => node.parentNode,
    // 获取节点的下一个兄弟节点
    nextSibling: (node) => node.nextSibling,
    // 查找匹配选择器的第一个元素
    querySelector: (selector) => doc.querySelector(selector),
    // 设置元素节点的id属性
    setScopedId: (el, id) => {
        el.setAttribute(id, "")
    },
    // 将静态内容插入到指定位置
    insertStaticContent(content, parent, anchor, namespace, start, end) {
        const before = anchor ? anchor.previousSibling : parent.lastChild;
        if (start && (start === end || start.nextSibling)) {
            while (true) {
                parent.insertBefore(start.cloneNode(true), anchor);
                if (start === end || !(start = start.nextSibling)) {
                    break;
                }
            }
        } else {
            templateContainer.innerHTML = unsafeToTrustedHTML(namespace === 'svg' ? `<svg>${content}</svg>` : namespace === "mathml" ? `<math>${content}</math>` : content
            );
            const template = templateContainer.content;
            if (namespace === 'svg' || namespace === 'mathml') {
                const wrapper = template.firstChild;
                while (wrapper.firstChild) {
                    template.appendChild(wrapper.firstChild)
                }
                template.removeChild(wrapper)
            }
            parent.insertBefore(template, anchor);
        }

        return [before ? before.nextSibling : parent.firstChild, anchor ? anchor.previousSibling : parent.lastChild]
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
编辑 (opens new window)
上次更新: 2025/09/16, 07:26:19
patch中的双端比较快速算法
patchProp属性补丁解析

← patch中的双端比较快速算法 patchProp属性补丁解析→

最近更新
01
patch中的双端比较快速算法
09-25
02
patchDOM元素的更新解析
09-25
03
patch中DOM元素的挂载解析
09-25
更多文章>
Theme by Vdoing | Copyright © 2024-2025 东流 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式