vue3编译做了哪些优化-ag真人官方网

vue3编译做了哪些优化-ag真人官方网

来源:php中文网 | 2022-12-19 18:04:15 |

本教程操作环境:windows7系统、vue3版,dell g3电脑。

本文主要来分析 vue3.0编译阶段做的优化,在 patch阶段是如何利用这些优化策略来减少比对次数。由于组件更新时依然需要遍历该组件的整个 vnode树,比如下面这个模板:


(资料图)

整个 diff 过程如图所示:

可以看到,因为这段代码中只有一个动态节点,所以这里有很多 diff 和遍历其实都是不需要的,这就会导致 vnode 的性能跟模版大小正相关,跟动态节点的数量无关,当一些组件的整个模版内只有少量动态节点时,这些遍历都是性能的浪费。对于上述例子,理想状态只需要 diff 这个绑定 message 动态节点的 p 标签即可。

vue.js 3.0通过编译阶段对静态模板的分析,编译生成了 block tree

block tree是一个将模板基于动态节点指令切割的嵌套区块,每个区块内部的节点结构是固定的,而且每个区块只需要以一个 array来追踪自身包含的动态节点。借助 block treevue.js 将 vnode 更新性能由与模版整体大小相关提升为与动态内容的数量相关,这是一个非常大的性能突破。

patchflag

由于 diff算法无法避免新旧虚拟 dom中无用的比较操作,vue.js 3.0引入了 patchflag,用来标记动态内容。在编译过程中会根据不同的属性类型打上不同的标识,从而实现了快速 diff算法。patchflags的所有枚举类型如下所示:

export const enum patchflags {  text = 1, // 动态文本节点  class = 1 << 1, // 动态class  style = 1 << 2, // 动态style  props = 1 << 3, // 除了class、style动态属性  full_props = 1 << 4, // 有key,需要完整diff  hydrate_events = 1 << 5, // 挂载过事件的  stable_fragment = 1 << 6, // 稳定序列,子节点顺序不会发生变化  keyed_fragment = 1 << 7, // 子节点有key的fragment  unkeyed_fragment = 1 << 8, // 子节点没有key的fragment  need_patch = 1 << 9, // 进行非props比较, ref比较  dynamic_slots = 1 << 10, // 动态插槽  dev_root_fragment = 1 << 11,   hoisted = -1, // 表示静态节点,内容变化,不比较儿子  bail = -2 // 表示diff算法应该结束}

block tree

左侧的 template经过编译后会生成右侧的 render函数,里面有 _openblock_createelementblock_todisplaystring_createelementvnode(createvnode) 等辅助函数。

let currentblock = nullfunction _openblock() {  currentblock = [] // 用一个数组来收集多个动态节点}function _createelementblock(type, props, children, patchflag) {  return setupblock(createvnode(type, props, children, patchflag));}export function createvnode(type, props, children = null, patchflag = 0) {  const vnode = {    type,    props,    children,    el: null, // 虚拟节点上对应的真实节点,后续diff算法    key: props?.["key"],    __v_isvnode: true,    shapeflag,    patchflag   };  ...  if (currentblock && vnode.patchflag > 0) {    currentblock.push(vnode);  }  return vnode;}function setupblock(vnode) {  vnode.dynamicchildren = currentblock;  currentblock = null;  return vnode;}function _todisplaystring(val) {  return isstring(val)    ? val    : val == null    ? ""    : isobject(val)    ? json.stringify(val)    : string(val);}

此时生成的 vnode 如下:

此时生成的虚拟节点多出一个 dynamicchildren属性,里面收集了动态节点 span

节点 diff 优化策略:

我们之前分析过,在 patch阶段更新节点元素的时候,会执行 patchelement函数,我们再来回顾一下它的实现:

const patchelement = (n1, n2) => { // 先复用节点、在比较属性、在比较儿子  let el = n2.el = n1.el;  let oldprops = n1.props || {}; // 对象  let newprops = n2.props || {}; // 对象  patchprops(oldprops, newprops, el);  if (n2.dynamicchildren) { // 只比较动态元素    patchblockchildren(n1, n2);  } else {    patchchildren(n1, n2, el); // 全量 diff  }}

我们在前面组件更新的章节分析过这个流程,在分析子节点更新的部分,当时并没有考虑到优化的场景,所以只分析了全量比对更新的场景。

而实际上,如果这个 vnode是一个 block vnode,那么我们不用去通过 patchchildren全量比对,只需要通过 patchblockchildren去比对并更新 block中的动态子节点即可。由此可以看出性能被大幅度提升,从 tree级别的比对,变成了线性结构比对。

我们来看一下它的实现:

const patchblockchildren = (n1, n2) => {  for (let i = 0; i < n2.dynamicchildren.length; i  ) {    patchelement(n1.dynamicchildren[i], n2.dynamicchildren[i])  }}

属性 diff 优化策略:

接下来我们看一下属性比对的优化策略:

const patchelement = (n1, n2) => { // 先复用节点、在比较属性、在比较儿子  let el = n2.el = n1.el;  let oldprops = n1.props || {}; // 对象  let newprops = n2.props || {}; // 对象  let { patchflag, dynamicchildren } = n2    if (patchflag > 0) {    if (patchflag & patchflags.full_props) { // 对所 props 都进行比较更新      patchprops(el, n2, oldprops, newprops, ...)    } else {      // 存在动态 class 属性时      if (patchflag & patchflags.class) {        if (oldprops.class !== newprops.class) {          hostpatchprop(el, "class", null, newprops.class, ...)        }      }      // 存在动态 style 属性时      if (patchflag & patchflags.style) {        hostpatchprop(el, "style", oldprops.style, newprops.style, ...)      }            // 针对除了 style、class 的 props      if (patchflag & patchflags.props) {        const propstoupdate = n2.dynamicprops!        for (let i = 0; i < propstoupdate.length; i  ) {          const key = propstoupdate[i]          const prev = oldprops[key]          const next = newprops[key]          if (next !== prev) {            hostpatchprop(el, key, prev, next, ...)          }        }      }      if (patchflag & patchflags.text) { // 存在动态文本        if (n1.children !== n2.children) {          hostsetelementtext(el, n2.children as string)        }      }     } else if (dynamicchildren == null) {      patchprops(el, n2, oldprops, newprops, ...)    }  }}function hostpatchprop(el, key, prevvalue, nextvalue) {  if (key === "class") { // 更新 class     patchclass(el, nextvalue)  } else if (key === "style") { // 更新 style    patchstyle(el, prevvalue, nextvalue)  } else if (/^on[^a-z]/.test(key)) {  // events  addeventlistener    patchevent(el, key, nextvalue);  } else { // 普通属性 el.setattribute    patchattr(el, key, nextvalue);  }}function patchclass(el, nextvalue) {  if (nextvalue == null) {    el.removeattribute("class"); // 如果不需要class直接移除  } else {    el.classname = nextvalue  }}function patchstyle(el, prevvalue, nextvalue = {}){  ...}function patchattr(el, key, nextvalue){  ...}

总结: vue3会充分利用 patchflagdynamicchildren做优化。如果确定只是某个局部的变动,比如 style改变,那么只会调用 hostpatchprop并传入对应的参数 style做特定的更新(靶向更新);如果有 dynamicchildren,会执行 patchblockchildren做对比更新,不会每次都对 props 和子节点进行全量的对比更新。图解如下:

静态提升

静态提升是将静态的节点或者属性提升出去,假设有以下模板:

hello {{name}}

编译生成的 render函数如下:

export function render(_ctx, _cache, $props, $setup, $data, $options) {  return (_openblock(), _createelementblock("div", null, [    _createelementvnode("span", null, "hello"),    _createelementvnode("span", {      a: "1",      b: "2"    }, _todisplaystring(_ctx.name), 1 /* text */),    _createelementvnode("a", null, [      _createelementvnode("span", null, _todisplaystring(_ctx.age), 1 /* text */)    ])  ]))}

我们把模板编译成 render函数是这个酱紫的,那么问题就是每次调用 render函数都要重新创建虚拟节点。

开启静态提升 hoiststatic选项后

const _hoisted_1 = /*#__pure__*/_createelementvnode("span", null, "hello", -1 /* hoisted */)const _hoisted_2 = {  a: "1",  b: "2"}export function render(_ctx, _cache, $props, $setup, $data, $options) {  return (_openblock(), _createelementblock("div", null, [    _hoisted_1,    _createelementvnode("span", _hoisted_2, _todisplaystring(_ctx.name), 1 /* text */),    _createelementvnode("a", null, [      _createelementvnode("span", null, _todisplaystring(_ctx.age), 1 /* text */)    ])  ]))}

预解析字符串化

静态提升的节点都是静态的,我们可以将提升出来的节点字符串化。 当连续静态节点超过 10个时,会将静态节点序列化为字符串。

假如有如下模板:

static static static static static static static static static static

开启静态提升 hoiststatic选项后

const _hoisted_1 = /*#__pure__*/_createstaticvnode("staticstaticstaticstaticstaticstaticstaticstaticstaticstatic", 10)const _hoisted_11 = [  _hoisted_1]export function render(_ctx, _cache, $props, $setup, $data, $options) {  return (_openblock(), _createelementblock("div", null, _hoisted_11))}

函数缓存

假如有如下模板:

编译后:

const _hoisted_1 = ["onclick"]export function render(_ctx, _cache, $props, $setup, $data, $options) {  return (_openblock(), _createelementblock("div", {    onclick: event => _ctx.v = event.target.value  }, null, 8 /* props */, _hoisted_1))}

每次调用 render的时候要创建新函数,开启函数缓存 cachehandlers选项后,函数会被缓存起来,后续可以直接使用

export function render(_ctx, _cache, $props, $setup, $data, $options) {  return (_openblock(), _createelementblock("div", {    onclick: _cache[0] || (_cache[0] = event => _ctx.v = event.target.value)  }))}

总结

以上几点即为 vuejs在编译阶段做的优化,基于上面几点,vuejspatch过程中极大地提高了性能。

以上就是vue3编译做了哪些优化的详细内容,更多请关注php中文网其它相关文章!

关键词:

ag真人官方网 ag真人官方网的版权所有.

联系网站:920 891 263@qq.com
网站地图