滨州网站开发公司,网页游戏不花钱的,金乡县住房与城乡建设局网站,哪里有专业的培训机构#x1f4dd;个人主页#xff1a;爱吃炫迈 #x1f48c;系列专栏#xff1a;Vue #x1f9d1;#x1f4bb;座右铭#xff1a;道阻且长#xff0c;行则将至#x1f497; 文章目录 编译优化diff算法优化提取动态节点 静态提升预字符串化缓存内联事件处理函数SSR优化 源… 个人主页爱吃炫迈 系列专栏Vue 座右铭道阻且长行则将至 文章目录 编译优化diff算法优化提取动态节点 静态提升预字符串化缓存内联事件处理函数SSR优化 源码体积响应式系统 编译优化
编译优化指的是编译器将模版编译成渲染函数的过程中尽可能多的提取关键信息并以此指导生成最优代码的过程。 编译优化的策略与具体实现是由框架的设计思路决定的不同的框架具有不同的设计思路因此编译优化的策略也不尽相同。 但优化的方向是基本一致的就是尽可能的区分静态内容和动态内容并根据不同的内容采用不同的优化策略。
diff算法优化
传统的Diff算法在对比两颗虚拟DOM树的时候总是要按照虚拟DOM的层级“一层一层”地遍历。 举个栗子
div idfoop classbar{{text}}/p
/div传统Diff在响应式数据text发生变化时会生成一棵新的虚拟DOM树传统的Diff算法对比新旧两颗虚拟DOM树的过程如下
对比div节点以及该节点的属性和子节点对比p节点以及该节点的属性和子节点对比p节点的文本子节点如果文本子节点的内容变了则更新之否则什么都不做
在上面这段模版中变得只有p标签的文本子节点的内容也就是说其实当响应式数据变化的时候最高效的更新方式就是直接设置p标签的文本内容但传统的Diff算法显然做不到这么高效存在很多无意义的对比操作如果能跳过这些无意义的操作性能会有很大的提升。这也就是vue3的编译优化的思路。
vue3的编译器可以分析出很多关键信息例如哪些节点的静态的哪些节点是动态的编译器会将编译时得到的关键信息“附着”在它生成的虚拟DOM上这些信息回通过虚拟DOM传递给渲染器。最终渲染器会根据这些关键信息执行“快捷路径”从而提升运行时的性能。
所以vue3在diff算法中增加了静态标记pathFlag这个标记表明这个节点是动态的会发生改变下次发生改变的时候直接找该地方进行比较。 举个例子
divdivfoo/divp{{ bar }}/p
/div传统的虚拟DOM描述上述模版
const vnode {tag: div,children: [{ tag: div, children: foo },{ tag: p, children: ctx.bar },],
};
上述模版中只有{{ bar }}是动态的内容但是虚拟DOM中没有任何标志能体现出节点的动态性。 但是经过编译优化后编译器会将它提取到的关键信息“附着”在虚拟DOM节点上
const vnode {tag: div,children: [{ tag: div, children: foo },{ tag: p, children: ctx.bar, patchFlag: 1 }, // 这是动态节点,],
};可以看到用于描述p标签的虚拟DOM拥有一个额外的属性patchFlag它的值是一个数字。只要虚拟DOM存在该属性我们就认为他是一个动态节点。这里的patchFlag就是所谓的补丁标志。
可以将补丁标志理解为一系列数字标记并根据数字值的不同赋予它不同的含义例如
const PatchFlags {TEXT 1,// 动态的文本节点CLASS 1 1, // 2 动态的 classSTYLE 1 2, // 4 动态的 stylePROPS 1 3, // 8 动态属性不包括类名和样式FULL_PROPS 1 4, // 16 动态 key当 key 变化时需要完整的 diff 算法做比较HYDRATE_EVENTS 1 5, // 32 表示带有事件监听器的节点STABLE_FRAGMENT 1 6, // 64 一个不会改变子节点顺序的 FragmentKEYED_FRAGMENT 1 7, // 128 带有 key 属性的 FragmentUNKEYED_FRAGMENT 1 8, // 256 子节点没有 key 的 FragmentNEED_PATCH 1 9, // 512DYNAMIC_SLOTS 1 10, // 动态 soltHOISTED -1, // 特殊标志是负整数表示永远不会用作 diffBAIL -2 // 一个特殊的标志指代差异算法
}有了PatchFlag这个信息就可以在虚拟DOM的创建阶段将它的动态子节点提取出来并存到该虚拟节点的dynamicChildren数组中
const vnode {tag: div,children: [{ tag: div, children: foo },{ tag: p, children: ctx.bar, patchFlag: patchFlags.TEXT }, // 这是动态节点,],// 数组dynamicChildren:[// p节点具有patchFlag属性所以是动态节点{ tag: p, children: ctx.bar, patchFlag: patchFlags.TEXT }
]
};上面的虚拟DOM对象与普通的虚拟DOM对象相比多了一个额外的dynamicChildren属性。vue3中将拥有这个属性的虚拟DOM对象称为“块”即Block。 所以一个Block的本质也是一个虚拟DOM对象只不过比普通的虚拟DOM对象多出了一个用于存储动态子节点的dynamicChildren属性。 现在有个问题就是如果不是Block的直接子节点还能够收集吗答案是yes 举例说明
divdivp{{ bar}}/p/div
/div最外层div标签对应的Block能够将p标签收集到它的dynamicChildren数组中
const vnode {tag: div,children: [{tag: div,children: [{ tag: p, children: ctx.bar, patchFlag: patchFlags.TEXT }],},],dynamicChildren: [// p节点具有patchFlag属性所以是动态节点{ tag: p, children: ctx.bar, patchFlag: patchFlags.TEXT },],
};
有了Block这个概念之后渲染器的更新操作将会以Block为维度。也就是说当渲染器在更新一个Block的时候会忽略虚拟DOM对象的children数组而是直接找到虚拟DOM对象的dynamicChildren数组并只更新该数组中的动态节点。 这样在更新的时候就实现了跳过静态内容只更新动态内容。同时由于动态节点中存在对应的补丁标志所以在更新动态节点的时候也能够做到靶向更新。例如当一个动态节点的pathFlag值得数字为1时我们知道他只存在动态的文本节点所以只需要更新它的文本内容即可。
提取动态节点
未完待续·······
静态提升
静态提升能够减少更新时西黄建虚拟DOM带来的性能开销和内存占用。 举个例子
divpstatic text/pp{{title}}/p
/div在没有静态提升的情况下它对应的渲染函数是
function render() {return (openBlock(),createBlock(div,null,[createVNode(p, null, static text),createVNode(p, null, ctx.title, 1 /* TEXT */)]))
}可以看到在这段虚拟 DOM 的描述中存在两个 p 标签一个是纯静态的而另一个拥有动态文本。 当响应式数据 title 的值发生变化时整个渲染函数会重新执行并产生新的虚拟 DOM 树。 这个过程 有一个明显的问题即纯静态的虚拟节点在更新时也会被重新创建一 次。很显然这是没有必要的所以我们需要想办法避免由此带来的性能开销。 而解决方案就是所谓的**“静态提升”**即把纯静态的节点提升到渲染函数之外如下面的代码所示
// 把静态节点提升到渲染函数之外const hoist1 createVNode(p, null, text)function render() {return (openBlock(), createBlock(div, null, [hoist1, // 静态节点引用createVNode(p, null, ctx.title, 1 /* TEXT */)]))
}可以看到当把纯静态的节点提升到渲染函数之外后在渲染函 数内只会持有对静态节点的引用。当响应式数据变化并使得渲染函 数重新执行时并不会重新创建静态的虚拟节点从而避免了额外的 性能开销。
有一个问题需要注意 静态提升是以树为单位的举例说明
divsectionpspanabc/span/p/section
/div
在上面这段模板中除了根节点的** div 标签会作为 Block 角色而不可被提升**之外整个 元素及其子代节点都会被提升。 如果我们把上面模板中的静态字符串 abc 换成动态绑定的 {{ abc }}那么整棵树都不会被提升。 虽然包含动态绑定的节点本身不会被提升但是该动态节点上任然可能存在纯静态的属性例如
divp foobar ab{{ text }}/p
/div在上面这段模板中p 标签存在动态绑定的文本内容因此整个节点都不会被静态提升。但该节点的所有 props 都是静态的因此在最终生成渲染函数时我们可以将纯静态的 props 提升到渲染函数之 外如下面的代码所示
// 静态提升的 props 对象
const hoistProp { foo: bar, a: b }function render(ctx) {return (openBlock(), createBlock(div, null, [createVNode(p, hoistProp, ctx.text)]))
}这样可以减少创建虚拟DOM产生的开销以及内存占用。
预字符串化
基于静态提升我们还可以进一步曹勇预字符串化的优化手段。预字符串是基于静态提升的一种优化策略。静态提升的虚拟节点或虚拟节点树本身是静态的那么就能将其预字符串化
divp/pp/p// ... 20 个 p 标签p/p
/div假设上面的模板中包含大量连续纯静态的标签节点当采用了静 态提升优化策略时其编译后的代码如下
cosnt hoist1 createVNode(p, null, null, PatchFlags.HOISTED)cosnt hoist2 createVNode(p, null, null, PatchFlags.HOISTED)// ... 20 个 hoistx 变量cosnt hoist20 createVNode(p, null, null, PatchFlags.HOISTED)render() {return (openBlock(), createBlock(div, null, [hoist1, hoist2, /* ...20 个变量 */, hoist20]))
}
预字符串化能够将这些静态节点序列化为字符串并生成一个 Static 类型的 VNode
const hoistStatic createStaticVNode(p/pp/pp/p...20 个...p/p)render() {return (openBlock(), createBlock(div, null, [hoistStatic]))
}这样有很多的好处
大块的静态内容可以通过 innerHTML 进行设置在性能上具有 一定优势。减少创建虚拟节点产生的性能开销。减少内存占用。
缓存内联事件处理函数
提到优化就不得不提对内联事件处理函数的缓存。缓存内联事 件处理函数可以避免不必要的更新。假设模板内容如下
Comp changea b /上面这段模板展示的是一个绑定了 change 事件的组件并且为 change 事件绑定的事件处理程序是一个内联语句。对于这样的模板 编译器会为其创建一个内联事件处理函数如下面的代码所示 function render(ctx) {return h(Comp, {// 内联事件处理函数onChange: () (ctx.a ctx.b)})}很显然每次重新渲染时即 render 函数重新执行时都会 为 Comp 组件创建一个全新的 props 对象。同时props 对象中 onChange 属性的值也会是全新的函数。这会导致渲染器对 Comp 组 件进行更新造成额外的性能开销。 为了避免这类无用的更新我们 需要对内联事件处理函数进行缓存如下面的代码所示
function render(ctx, cache) { return h(Comp, { // 将内联事件处理函数缓存到 cache 数组中 onChange: cache[0] || (cache[0] ($event) (ctx.a ctx.b)) }) } 渲染函数的第二个参数是一个数组 cache该数组来自组件实 例我们可以把内联事件处理函数添加到 cache 数组中。这样当渲染函数重新执行并创建新的虚拟 DOM 树时会优先读取缓存中的事件处理函数。这样无论执行多少次渲染函数props 对象中 onChange 属性的值始终不变于是就不会触发 Comp 组件更新了。
SSR优化
未完待续·······
源码体积
相比Vue2Vue3整体体积变小了除了移出一些不常用的API再重要的是Tree shanking 任何一个函数如ref、reavtived、computed等仅仅在用到的时候才打包没用到的模块都被摇掉打包的整体体积变小
import { computed, defineComponent, ref } from vue;
export default defineComponent({setup(props, context) {const age ref(18)let state reactive({name: test})const readOnlyAge computed(() age.value) // 19return {age,state,readOnlyAge}}
});响应式系统
vue2中采用 defineProperty来劫持整个对象然后进行深度遍历所有属性给每个属性添加getter和setter实现响应式 vue3采用proxy重写了响应式系统因为proxy可以对整个对象进行监听所以不需要深度遍历
可以监听动态属性的添加可以监听到数组的索引和数组length属性可以监听删除属性