第三天学习内容
1. Vue.js 的虚拟 DOM
1.1 什么是虚拟 DOM
- 虚拟 DOM 是 Vue.js 和其他现代前端框架(如 React)使用的概念。它是一种在内存中表示 UI 结构的对象,而不是直接操作真实的 DOM 节点。
- 优点:通过虚拟 DOM,Vue 可以在更新 UI 时更高效地对比新旧节点,减少不必要的 DOM 操作,从而提升性能。
1.2 Vue2 中的虚拟 DOM 实现
- Vue2 中使用了一个轻量级的 VNode 类来表示虚拟 DOM 节点。每次组件重新渲染时,Vue 会生成新的 VNode 树并与旧的 VNode 树进行比较(称为 diffing),以决定哪些部分需要更新。
VNode 类定义:
class VNode {
constructor(tag, data, children, text, elm) {
this.tag = tag; // 节点标签名
this.data = data; // 节点数据
this.children = children; // 子节点
this.text = text; // 文本节点
this.elm = elm; // 对应的真实DOM节点
}
}
- Diff 算法:Vue2 的 Diff 算法是采用双端比较,尽量减少 DOM 的修改。它会逐层对比新旧节点,找到最小的更新路径。
1.3 Vue3 中的虚拟 DOM 改进
- Vue3 在虚拟 DOM 上做了大量优化,主要体现在对 VNode 的更高效处理和更快的 Diff 算法。
Vue3 中的 VNode:
function createVNode(type, props = null, children = null) {
const vnode = {
type,
props,
children,
el: null, // 与真实DOM的关系由 el 属性连接
key: props && props.key,
};
return vnode;
}
- Patch 函数:Vue3 中使用 patch 函数来处理新旧 VNode 树之间的差异,通过递归地比较新旧节点,实现最小更新。
2. Vue.js 的模板编译
2.1 模板编译的作用
- Vue.js 中的模板编译器将模板语法转换为渲染函数。渲染函数生成虚拟 DOM 树,最终渲染到真实 DOM 上。
2.2 编译流程
- 解析(Parsing):将模板字符串解析为抽象语法树(AST)。
- 优化(Optimize):静态标记优化,标记出静态节点,减少后续更新时的比较成本。
- 代码生成(Code Generation):将 AST 转化为渲染函数。
模板编译示例:
const template = `<div id="app">{{ message }}</div>`;
const render = compile(template);
// 转换为:
function render() {
return _c('div', { attrs: { id: 'app' } }, [_v(_s(message))]);
}
2.3 Vue2 与 Vue3 模板编译的区别
- Vue2 的模板编译是将模板转换为渲染函数,然后在每次数据更新时执行渲染函数以生成新的虚拟 DOM。
- Vue3 通过进一步优化编译过程,引入了 compiler 模块,将模板转换为更高效的渲染函数,使得运行时的性能更优。
3. Vue.js 的生命周期
3.1 Vue2 的生命周期
- Vue2 的生命周期钩子包括 beforeCreate, created, beforeMount, mounted, beforeUpdate, updated, beforeDestroy, destroyed。
- 这些钩子函数提供了在组件各个阶段执行代码的机会。
Vue2 生命周期图示:
Vue.component('example', {
beforeCreate() {
console.log('beforeCreate');
},
created() {
console.log('created');
},
// 其他钩子函数...
});
3.2 Vue3 的生命周期
- Vue3 的生命周期钩子名称稍有变化,例如 beforeDestroy 变成了 beforeUnmount,destroyed 变成了 unmounted。
- 同时,Vue3 还引入了基于 Composition API 的生命周期钩子函数,如 onMounted, onUpdated, onUnmounted。
Vue3 生命周期示例:
import { onMounted, onUnmounted } from 'vue';
export default {
setup() {
onMounted(() => {
console.log('Component mounted');
});
onUnmounted(() => {
console.log('Component unmounted');
});
}
};
4. Vue Router 与 Nuxt.js 的高级应用
4.1 Vue Router 的路由守卫
- 全局守卫:如 beforeEach, beforeResolve, afterEach,用于控制导航过程。
- 路由级别守卫:在路由配置中指定守卫。
- 组件内守卫:在组件内部通过 beforeRouteEnter, beforeRouteUpdate, beforeRouteLeave 实现路由控制。
全局守卫示例:
router.beforeEach((to, from, next) => {
if (to.meta.requiresAuth && !store.state.isAuthenticated) {
next('/login');
} else {
next();
}
});
4.2 Nuxt.js 的 SSR 与静态生成
- SSR(Server-Side Rendering):Nuxt.js 的核心功能之一,它将 Vue.js 应用在服务器端渲染,从而提升首屏加载速度和 SEO。
- 静态生成:Nuxt.js 通过 nuxt generate 命令将应用预渲染为静态页面,适合内容较少但访问量较大的应用。
Nuxt.js 简单示例:
// pages/index.vue
<template>
<div>
<h1>{{ title }}</h1>
</div>
</template>
<script>
export default {
async asyncData({ $axios }) {
const title = await $axios.$get('/api/title');
return { title };
}
};
</script>
5. 总结
第三天的学习内容涵盖了 Vue.js 的虚拟 DOM 和模板编译的深层理解,以及生命周期管理与 Vue Router/Nuxt.js 的高级应用。这些知识点对于构建高性能、高可维护性的 Vue 应用至关重要。
6.Diff算法扩展
6.1 Diff 算法的基本原理
Diff 算法的主要目标是找到最小的更新路径,也就是在新旧虚拟 DOM 树对比时,只更新那些真正需要更新的部分,尽可能减少 DOM 操作的次数和范围,从而提升性能。
6.2 Vue2 中的 Diff 算法
在 Vue2 中,Diff 算法基于双端比较策略,即同时从新旧节点的头部和尾部进行比较,以下是详细步骤:
- 同一层级的比较
Diff 算法只会比较同一层级的节点,不会跨层级对比,这样可以大大简化计算过程。 - 双端对比策略
- 头部对头部(sameVnode(oldStartVNode, newStartVNode)):先比较新旧虚拟 DOM 树的头部节点。如果相同,则直接复用旧节点,继续比较下一个头部节点。
- 尾部对尾部(sameVnode(oldEndVNode, newEndVNode)):如果头部节点不相同,则比较新旧虚拟 DOM 树的尾部节点。如果相同,复用旧节点,继续比较下一个尾部节点。
- 旧头部对新尾部(sameVnode(oldStartVNode, newEndVNode)):如果头尾都不相同,则将旧头部节点与新尾部节点比较,如果相同,进行相应的节点移动操作。
- 旧尾部对新头部(sameVnode(oldEndVNode, newStartVNode)):同理,比较旧尾部节点与新头部节点,相同则进行节点移动操作。
- 处理剩余节点
- 当头尾对比结束后,如果仍有剩余节点,需要根据情况进行插入或删除操作。
- Key 的作用
- 在 Diff 算法中,如果虚拟 DOM 节点设置了 key 属性,算法会优先通过 key 来匹配节点,而不只是比较节点的类型。key 的作用在于更精确地找到对应的旧节点,提高算法效率。
示例代码(简化版的 patch 函数):
function patch(oldVNode, newVNode) {
if (!oldVNode) {
// 如果 oldVNode 不存在,直接创建新节点
createElm(newVNode);
} else if (sameVnode(oldVNode, newVNode)) {
// 如果两个节点是相同节点,则进行精细化比较
patchVnode(oldVNode, newVNode);
} else {
// 如果两个节点不是相同节点,替换旧节点
const oldEl = oldVNode.elm;
const parentEl = oldEl.parentNode;
createElm(newVNode);
parentEl.insertBefore(newVNode.elm, oldEl);
parentEl.removeChild(oldEl);
}
}
6.3 Vue3 中的 Diff 算法优化
Vue3 对 Diff 算法进行了优化,主要体现在以下几个方面:
- 更高效的 Patch 流程
- Vue3 的 patch 函数进行了大量优化,在处理复杂结构时效率更高,尤其是针对静态节点和大型列表的更新。
- 静态提升(Static Hoisting)
- 在编译阶段,Vue3 会对模板中的静态内容进行提升,只创建一次静态节点,并在后续更新中直接复用,减少不必要的计算。
- Fragment 支持
- Vue3 支持 Fragment 作为根节点进行更新,这样可以避免不必要的包裹元素,提高渲染灵活性和性能。
- 更加智能的 Diff 策略
- Vue3 在处理长列表时,通过智能化的 Keyed Diff 策略进一步优化对比过程,减少 DOM 操作次数。它引入了一种更加先进的最长递增子序列(LIS,Longest Increasing Subsequence)算法,用于优化非同层次节点的移动操作。
示例代码(简化版的 Vue3 Patch 逻辑):
function patchKeyedChildren(c1, c2, container) {
// 新旧节点列表双端比较,依次处理节点移动或更新操作
let i = 0;
let e1 = c1.length - 1;
let e2 = c2.length - 1;
// 头部对比
while (i <= e1 && i <= e2) {
const n1 = c1[i];
const n2 = c2[i];
if (isSameVNode(n1, n2)) {
patch(n1, n2, container);
i++;
} else {
break;
}
}
// 尾部对比
while (e1 >= i && e2 >= i) {
const n1 = c1[e1];
const n2 = c2[e2];
if (isSameVNode(n1, n2)) {
patch(n1, n2, container);
e1--;
e2--;
} else {
break;
}
}
// 处理新增或删除节点
if (i > e1) {
const nextPos = e2 + 1;
const anchor = nextPos < c2.length ? c2[nextPos].el : null;
while (i <= e2) {
patch(null, c2[i], container, anchor);
i++;
}
} else if (i > e2) {
while (i <= e1) {
unmount(c1[i], container);
i++;
}
}
}
6.4 Diff 算法的优化策略
- 用 key
- 为节点设置唯一的 key 属性,特别是在长列表中。key 能帮助 Diff 算法更准确地找到对应的节点,减少不必要的操作。
- 避免过度更新
- 在设计组件时,尽量减少数据状态的频繁变动,避免在每个数据变动时都触发重新渲染。
- 合理使用静态节点
- 在模板中标记不需要更新的静态内容,可以避免 Diff 算法的过度计算。Vue3 通过编译阶段的静态提升,自动优化了这一点。