阿卡不拉阿卡不拉
Vue3
阿卡的博客
Vue3
阿卡的博客
  • Vue3

    • 快速入门

      • 搭建工程 👌
      • 模板语法
      • 响应式基础
      • 响应式常用 API
      • 计算属性
      • 类与样式绑定
      • 条件和列表渲染
      • 事件处理
      • 表单处理
      • 生命周期
      • 侦听器
      • 组件介绍
      • Props
      • 自定义事件
      • 组件v-model
      • 插槽
      • 前端路由介绍
      • KeepAlive内置组件
      • 状态管理库
      • 组件库介绍
    • 深入本质

      • 虚拟DOM本质
      • 模板的本质
      • 组件树和虚拟DOM树
      • 数据拦截的本质
      • 响应式数据的本质
      • 响应式的本质
      • 响应式和组件渲染
      • 实现响应式系统 1
      • 实现响应式系统 2
      • 图解EFFECT
      • 实现响应式系统 3
      • 手写computed
      • 手写watch
      • 指令的本质
      • 插槽的本质
      • v-model的本质
      • setup 语法标签
      • 组件生命周期
      • keepalive 生命周期
      • keepalive的本质
      • key的本质
    • 细节补充

      • 【Vue】属性透传
      • 【Vue】依赖注入
      • 【Vue】组合式函数 👌
      • 【Vue】自定义指令
      • 【Vue】插件
      • 【Vue】Transition
      • 【Vue】TransitionGroup
      • 【Vue】Teleport
      • 【Vue】异步组件
      • 【Vue】Suspense
      • 【Router】路由模式
      • 【Router】路由零碎知识
      • 【Router】路由匹配语法
      • 【Router】路由组件传参
      • 【Router】内置组件和函数
      • 【Router】导航守卫
      • 【Router】过渡特效
      • 【Router】滚动行为
      • 【Router】动态路由
      • 【State】通信方式总结
      • 【State】Pinia 自定义插件
      • 【场景】封装树形组件
      • 【场景】自定义 ref 实现防抖
      • 【场景】懒加载
      • 【场景】虚拟列表
      • 【场景】虚拟列表优化
      • 【第三方库】VueUse
      • 【第三方库】vuedragable
      • 【第三方库】vue-drag-resize
      • 【第三方库】vue-chartjs
      • 【第三方库】vuelidate
      • 【第三方库】vue3-lazyload
      • 【场景】Websocket 聊天室
      • 【Vite】✨ 认识 Vite👌
      • 【Vite】配置文件 👌
      • 【Vite】✨ 依赖预构建 👌
      • 【Vite】构建生产版本 👌
      • 【Vite】环境变量与模式
      • 【Vite】CLI
      • 【Vite】Vite 插件
  • 笔面试

    • HTML

      • HTML 面试题汇总
      • 文档声明
      • 语义化
      • W3C 标准组织
      • SEO
      • iframe
      • 微格式
      • 替换元素
      • 页面可见性
    • CSS

      • CSS 面试题汇总
      • CSS 单位总结
      • 居中方式总结
      • 隐藏元素方式总结
      • 浮动
      • 定位总结
      • BFC
      • CSS 属性计算过程
      • CSS 层叠继承规则总结
      • @import 指令
      • CSS3 calc 函数
      • CSS3 媒体查询
      • 过渡和动画事件
      • 渐进增强和优雅降级
      • CSS3 变形
      • 渐进式渲染
      • CSS 渲染性能优化
      • 层叠上下文
      • CSS3 遮罩
    • JavaScript

      • JavaScript 面试题汇总
      • ✨ let、var、const 的区别
      • JS中的数据类型
      • 包装类型
      • 数据类型的转换
      • 运算符
      • ✨ 原型链
      • ✨ this 指向
      • ✨ 垃圾回收与内存泄漏
      • ✨ 执行栈和执行上下文
      • ✨ 作用域和作用域链
      • ✨ 闭包
      • DOM 事件的注册和移除
      • DOM 事件的传播机制
      • 阻止事件默认行为
      • 递归
      • ✨ 属性描述符
      • class 和构造函数区别
      • 浮点数精度问题
      • 严格模式
      • ✨ 函数防抖和节流
      • ✨ WeakSet 和 WeakMap
      • ✨ 深浅拷贝
      • 函数柯里化
      • Node 事件循环
      • 尺寸和位置
    • 浏览器

      • 浏览器面试题汇总
      • ✨ 浏览器的渲染流程
      • ✨ 资源提示关键词
      • 浏览器的组成部分
      • IndexedDB
      • ✨ File API
      • ✨ 浏览器缓存
      • ✨ 浏览器跨标签页通信
      • Web Worker
    • 网络

      • 网络面试题汇总
      • 五层网络模型 👌
      • 常见请求方法 👌
      • ✨cookie👌
      • 面试题
      • 加密
      • ✨JWT👌
      • ✨ 同源策略及跨域问题 👌
      • 文件上传
      • ✨ 输入 url 地址之后
      • 文件下载
      • ✨ session
      • ✨ TCP
      • ✨ CSRF 攻击
      • ✨XSS 攻击 👌
      • ✨ 网络性能优化
      • 断点续传
      • 域名和 DNS
      • SSL、TLS、HTTPS 的关系
      • ✨ HTTP 各版本差异 👌
      • HTTP 缓存协议
      • ✨ WebSocket
    • 工程化

      • CMJ 和 ESM
      • npx
      • ESLint
    • Vue2

      • Vue 面试题汇总相关
      • 组件通信方式总结
      • 虚拟 DOM
      • v-model
      • 数据响应式原理
      • diff
      • 生命周期详解
      • computed
      • 过滤器
      • 作用域插槽
      • 过度和动画
      • 优化
      • keep-alive
      • 长列表优化
      • 其他 API
      • 模式和环境变量
      • 更多配置
      • 更多命令
      • 嵌套路由
      • 路由切换动画
    • Vue3

      • ✨ Vue3 整体变化 👌
      • ✨ Vue3 响应式变化 👌
      • ✨ nextTick 实现原理 👌
      • 两道代码题 👌
      • Vue 运行机制
      • 渲染器核心功能
      • 事件绑定与更新

Vue 运行机制

面试题:介绍一下 Vue3 内部的运行机制是怎样的?

Vue3 整体可以分为几大核心模块:

  • 响应式系统
  • 编译器
  • 渲染器

如何描述 UI

思考 🤔:UI 涉及到的信息有哪些?

  1. DOM 元素
  2. 属性
  3. 事件
  4. 元素的层次结构

思考 🤔:如何在 JS 中描述这些信息?

考虑使用对象来描述上面的信息

<h1 id="title" @click="handler"><span>hello</span></h1>
const obj = {
    tag: "h1",
    props: {
        id: "title",
        onClick: handler,
    },
    children: [
        {
            tag: "span",
            children: "hello",
        },
    ],
};

虽然这种方式能够描述出来 UI,但是非常麻烦,因此 Vue 提供了模板的方式。

用户书写模板----> 编译器 ----> 渲染函数 ----> 渲染函数执行得到上面的 JS 对象(虚拟 DOM)

虽然大多数时候,模板比 JS 对象更加直观,但是偶尔有一些场景,JS 的方式更加灵活

<h1 v-if="level === 1"></h1>
<h2 v-else-if="level === 2"></h2>
<h3 v-else-if="level === 3"></h3>
<h4 v-else-if="level === 4"></h4>
<h5 v-else-if="level === 5"></h5>
<h6 v-else-if="level === 6"></h6>
let level = 1;
const title = {
    tag: `h${level}`,
};

编译器

主要负责将开发者所书写的模板转换为渲染函数。例如:

<template>
    <div>
        <h1 :id="someId">Hello</h1>
    </div>
</template>

编译后的结果为:

function render() {
    return h("div", [h("h1", { id: someId }, "Hello")]);
}

执行渲染函数,就会得到 JS 对象形式的 UI 表达。

整体来讲,整个编译过程如下图所示:

image-20231113095532166

可以看到,在编译器的内部,实际上又分为了:

  • 解析器:负责将模板解析为对应的模板 AST(抽象语法树)
  • 转换器:负责将模板 AST 转换为 JS AST
  • 生成器:将 JS AST 生成对应的 JS 代码(渲染函数)

Vue3 的编译器,除了最基本的编译以外,还做了很多的优化:

  1. 静态提升
  2. 预字符串化
  3. 缓存事件处理函数
  4. Block Tree
  5. PatchFlag

渲染器

执行渲染函数得到的就是虚拟 DOM,也就是像这样的 JS 对象,里面包含着 UI 的描述信息

<div>点击</div>
const vnode = {
    tag: "div",
    props: {
        onClick: () => alert("hello"),
    },
    children: "点击",
};

渲染器拿到这个虚拟 DOM 后,就会将其转换为真实的 DOM

image-20240901174218998

一个简易版渲染器的实现思路:

  1. 创建元素
  2. 为元素添加属性和事件
  3. 处理 children
function renderer(vnode, container) {
    // 1. 创建元素
    const el = document.createElement(vnode.tag);
    // 2. 遍历 props,为元素添加属性
    for (const key in vnode.props) {
        if (/^on/.test(key)) {
            // 如果 key 以 on 开头,说明它是事件
            el.addEventListener(
                key.substr(2).toLowerCase(), // 事件名称 onClick --->click
                vnode.props[key] // 事件处理函数
            );
        } else {
            // 不是事件则添加属性
            el.setAttribute(key, vnode.props[key]);
        }
    }
    // 3. 处理children
    if (typeof vnode.children === "string") {
        el.appendChild(document.createTextNode(vnode.children));
    } else if (Array.isArray(vnode.children)) {
        // 递归的调用 renderer
        vnode.children.forEach((child) => renderer(child, el));
    }

    container.appendChild(el);
}

组件的本质

组件本质就是一组 DOM 元素的封装。

假设函数代表一个组件:

// 这个函数就可以当作是一个组件
const MyComponent = function () {
    return {
        tag: "div",
        props: {
            onClick: () => alert("hello"),
        },
        children: "click me",
    };
};

vnode 的 tag 就不再局限于 html 元素,而是可以写作这个函数名:

const vnode = {
    tag: MyComponent,
};

渲染器需要新增针对这种 tag 类型的处理:

function renderer(vnode, container) {
    if (typeof vnode.tag === "string") {
        // 说明 vnode 描述的是标签元素
        mountElement(vnode, container);
    } else if (typeof vnode.tag === "function") {
        // 说明 vnode 描述的是组件
        mountComponent(vnode, container);
    }
}

组件也可以使用对象的形式:

const MyComponent = {
    render() {
        return {
            tag: "div",
            props: {
                onClick: () => alert("hello"),
            },
            children: "click me",
        };
    },
};
function renderer(vnode, container) {
    if (typeof vnode.tag === "string") {
        // 说明 vnode 描述的是标签元素
        mountElement(vnode, container);
    } else if (typeof vnode.tag === "object") {
        // 说明 vnode 描述的是组件
        mountComponent(vnode, container);
    }
}

响应式系统

总结:当模板编译成的渲染函数执行时,渲染函数内部用到的响应式数据会和渲染函数本身构成依赖关系,之后只要响应式数据发生变化,渲染函数就会重新执行。

面试题:介绍一下 Vue3 内部的运行机制是怎样的?

参考答案:

Vue 内部的运行机制大体遵循从模板到渲染函数再到虚拟 DOM,最后到真实 DOM 的这样一个流程在这个流程中主要有编译器、渲染器和响应式系统这三个核心模块。

首先在 js 中需要通过对象的形式来描述 UI,但是非常的麻烦,因此 Vue 为了方便用户描述 UI,采用了模板的形式。

当用户使用模板来描述 UI 的时候,内部的 编译器 会将其编译为渲染函数,渲染函数执行后会得到得到虚拟 DOM,并且这个过程中会确定响应式数据和渲染函数之间的依赖关系,之后响应式数据一变化,渲染函数就会重新执行。

之后虚拟 DOM 会被 渲染器 渲染为真实 DOM 元素。它的工作原理是,递归地遍历虚拟 DOM 对象,并调用原生 DOM API 来完成真实 DOM 的创建。渲染器的精髓在于后续的更新,它会通过 Diff 算法找出变更点,并且只会更新需要更新的内容。

编译器、渲染器、响应式系统都是 Vue 内部的核心模块,它们共同构成一个有机的整体,不同模块之间互相配合,进一步提升框架性能。

最近更新:: 2025/7/16 06:11
Contributors: AK
Prev
两道代码题 👌
Next
渲染器核心功能