当前位置: 首页 > news >正文

小程序做网站百度公司官方网站

小程序做网站,百度公司官方网站,哈尔滨网站建设nsstd,网站权重有时降Hello,各位小伙伴,接下来的一段时间里,我会把我的课程《Vue.js 3.0 核心源码解析》中问题的答案陆续在我的公众号发布,由于课程的问题大多数都是开放性的问题,所以我的答案也不一定是标准的,仅供你参考喔。…

Hello,各位小伙伴,接下来的一段时间里,我会把我的课程《Vue.js 3.0 核心源码解析》中问题的答案陆续在我的公众号发布,由于课程的问题大多数都是开放性的问题,所以我的答案也不一定是标准的,仅供你参考喔。

本期的问题:Block 数组是一维的,但是动态的子节点可能有嵌套关系,patchBlockChildren 内部也是递归执行了 patch 函数,那么在整个更新的过程中,会出现子节点重复更新的情况吗,为什么?

这道题是和 Vue.js 模板编译优化相关的问题,在回答问题之前,我们先来看 Vue.js 3.0 的编译优化主要做了什么。

编译优化

我们知道,通过数据劫持和依赖收集,Vue.js 2.x 的数据更新并触发重新渲染的粒度是组件级的:

50fb910840dccf33876ee1b638e1f971.png

虽然 Vue.js 能保证触发更新的组件最小化,但在单个组件内部依然需要遍历该组件的整个 vnode 树,举个例子,比如我们要更新这个组件:

<template>
  <div id="content">
    <p class="text">static textp>
    <p class="text">static textp>
    <p class="text">{{message}}p>
    <p class="text">static textp>
    <p class="text">static textp>
  div>
template>

整个 diff 过程如图所示:

8109a13c20dd48e10591f6f2b975aa8e.png

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

而对于上述例子,理想状态只需要 diff 这个绑定 message 动态节点的 p 标签即可。

Vue.js 3.0 做到了,它通过编译阶段对静态模板的分析,编译生成了 Block TreeBlock Tree 是一个将模版基于动态节点指令切割的嵌套区块,每个区块内部的节点结构是固定的,而且每个区块只需要以一个 Array 来追踪自身包含的动态节点。借助 Block Tree,Vue.js 将 vnode 更新性能由与模版整体大小相关提升为与动态内容的数量相关,这是一个非常大的性能突破。

编译生成 Block Tree

那么,Vue.js 在编译阶段会把哪些节点编译生成 Block Tree 呢?

我们先来看一个简单的例子,有如下模板:

<div class="app">
  <hello v-if="flag">hello>
  <div v-else>
    <p>hello {{ msg + test }}p>
    <p>staticp>
    <p>staticp>
  div>
div>

我们借助 Vue.js 提供的模板导出工具平台,编译上述模板结果如下:

import { resolveComponent as _resolveComponent, createVNode as _createVNode, openBlock as _openBlock, createBlock as _createBlock, createCommentVNode as _createCommentVNode, toDisplayString as _toDisplayString } from "vue"

const _hoisted_1 = { class: "app" }
const _hoisted_2 = { key: 1 }
const _hoisted_3 = /*#__PURE__*/_createVNode("p", null, "static", -1 /* HOISTED */)
const _hoisted_4 = /*#__PURE__*/_createVNode("p", null, "static", -1 /* HOISTED */)

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  const _component_hello = _resolveComponent("hello")

  return (_openBlock(), _createBlock("div", _hoisted_1, [
    (_ctx.flag)
      ? (_openBlock(), _createBlock(_component_hello, { key: 0 }))
      : (_openBlock(), _createBlock("div", _hoisted_2, [
          _createVNode("p", null, "hello " + _toDisplayString(_ctx.msg + _ctx.test), 1 /* TEXT */),
          _hoisted_3,
          _hoisted_4
        ]))
  ]))
}

通过编译后的结果我们可以看到,根节点创建了一个 Block,很好理解,因为整个组件至少需要构建一个 Block

此外 v-if 节点也在不同的分支创建了 Block,这是因为同一时间,v-if 只会命中一个分支,而不同分支下面的动态的节点肯定是不同的,所以需要分别创建 Block 维护。

运行时的构造 Block Tree

接下来,我们来看 openBlock 的实现:

const blockStack = []
let currentBlock = null
function openBlock(disableTracking = false) {
  blockStack.push((currentBlock = disableTracking ? null : []));
}

Vue.js 3.0 在运行时设计了一个 blockStackcurrentBlock,其中 blockStack 表示一个 Block Tree,因为要考虑嵌套 Block 的情况,而 currentBlock 表示当前的 Block

openBlock 的实现很简单,往当前 blockStack push 一个新的 Block,作为 currentBlock

设计 Block 的目的主要就是收集动态的 vnode 的节点,这样才能在 patch 阶段只比对这些动态 vnode 节点,避免不必要的静态节点的比对,优化了性能。

那么动态 vnode 节点是什么时候被收集的呢?其实是在 createVNode 阶段,我们来回顾一下它的实现:

function createVNode(type, props = null,children = null, patchFlag = 0, dynamicProps = null, isBlockNode = false) {
  // 处理 props 相关逻辑,标准化 class 和 style
  // 对 vnode 类型信息编码 
  // 创建 vnode 对象
  // 标准化子节点,把不同数据类型的 children 转成数组或者文本类型。
  // 添加动态 vnode 节点到 currentBlock 中
  if (shouldTrack > 0 &&
    !isBlockNode &&
    currentBlock &&
    patchFlag !== 32 /* HYDRATE_EVENTS */ &&
    (patchFlag > 0 ||
      shapeFlag & 128 /* SUSPENSE */ ||
      shapeFlag & 64 /* TELEPORT */ ||
      shapeFlag & 4 /* STATEFUL_COMPONENT */ ||
      shapeFlag & 2 /* FUNCTIONAL_COMPONENT */)) {
    currentBlock.push(vnode);
  }
  return vnode
}

createVNode 函数的最后判断 vnode 是不是一个动态节点,如果是则把它添加到 currentBlock 中,这就是动态 vnode 节点的收集过程。

我们接着来看 createBlock 的实现:

function createBlock(type, props, children, patchFlag, dynamicProps) {
  // 创建 vnode
  const vnode = createVNode(type, props, children, patchFlag, dynamicProps, true /* isBlock: 阻止这个 block 收集自身 */)
  // 在 vnode 上保留当前 Block 收集的动态子节点
  vnode.dynamicChildren = currentBlock || EMPTY_ARR
  closeBlock()
  // 节点本身作为父 Block 收集的子节点
  if (shouldTrack > 0 && currentBlock) {
    currentBlock.push(vnode)
  }
  return vnode
}

function closeBlock() {
  // 当前 Block 恢复到父 Block
  blockStack.pop()
  currentBlock = blockStack[blockStack.length - 1] || null
}

createBlock 内部,首先会执行 createVNode 创建一个 vnode 节点,注意最后一个参数是 true,这表明它是一个 Block node,所以就不会把自身当作一个动态 vnode 收集到 currentBlock 中。

接着把收集动态子节点的 currentBlock 保留到当前的 Block vnodedynamicChildren 中,为后续 patch 过程访问这些动态子节点所用。

最后把当前 Block 恢复到父 Block,如果父 Block 存在的话,则把当前这个 Block node 作为动态节点添加到父 Block 中,这样就构成了 Block Tree 的树形结构。

你可能会好奇,为什么要设计 openBlockcreateBlock 两个函数呢?比如下面这个函数:

function render() {
  return (openBlock(),createBlock('div', null, [/*...*/]))
}

为什么不把 openBlockcreateBlock 放在一个函数中执行呢,像下面这样:

function render() {
  return (createBlock('div', null, [/*...*/]))
}
function createBlock(type, props, children, patchFlag, dynamicProps) {
  openBlock()
  // 创建 vnode
  const vnode = createVNode(type, props, children, patchFlag, dynamicProps, true)
  vnode.dynamicChildren = currentBlock || EMPTY_ARR
  closeBlock()
  // ...  
  return vnode
}

这样是不行的,其中原因其实很简单,createBlock 函数的第三个参数是 children,这些 children 中的元素也是经过 createVNode 创建的,显然一个函数的调用需要先去执行参数的计算,也就是优先去创建子节点的 vnode,然后才会执行父节点的 createBlock 或者是 createVNode

所以在父节点的 createBlock 函数执行前,子节点就已经通过 createVNode 创建了对应的 vnode ,如果把 openBlock 的逻辑放在了 createBlock 中,就相当于在子节点创建后才创建 currentBlock,这样就不能正确地收集子节点中的动态 vnode 了。

再回到 createBlock 函数内部,这个时候你要明白动态子节点已经被收集到 currentBlock 中了。

patch 阶段的性能优化

Block Tree 的构造过程我们搞清楚了,那么接下来我们就来看它在 patch 阶段具体是如何工作的。

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

const patchElement = (n1, n2, parentComponent, parentSuspense, isSVG, optimized) => {
  const el = (n2.el = n1.el)
  const oldProps = (n1 && n1.props) || EMPTY_OBJ
  const newProps = n2.props || EMPTY_OBJ
  // 更新 props
  patchProps(el, n2, oldProps, newProps, parentComponent, parentSuspense, isSVG)
  const areChildrenSVG = isSVG && n2.type !== 'foreignObject'
  // 更新子节点
  if (n2.dynamicChildren) {
    patchBlockChildren(n1.dynamicChildren, n2.dynamicChildren, currentContainer, parentComponent, parentSuspense, isSVG);
  }
  else if (!optimized) {
    patchChildren(n1, n2, currentContainer, currentAnchor, parentComponent, parentSuspense, isSVG);
  }
}

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

而实际上,如果这个 vnode 是一个 Block vnode,那么在优化的场景下,我们更新它的子节点不用通过 patchChildren 全量比对,只需要通过 patchBlockChildren 去比对并更新 Block 中的动态子节点即可。

我们来看一下它的实现:

const patchBlockChildren = (oldChildren, newChildren, fallbackContainer, parentComponent, parentSuspense, isSVG) => {
  for (let i = 0; i     const oldVNode = oldChildren[i]
    const newVNode = newChildren[i]
    // 确定待更新节点的容器
    const container =
      // 对于 Fragment,我们需要提供正确的父容器
      oldVNode.type === Fragment ||
      // 在不同节点的情况下,将有一个替换节点,我们也需要正确的父容器
      !isSameVNodeType(oldVNode, newVNode) ||
      // 组件的情况,我们也需要提供一个父容器
      oldVNode.shapeFlag & 6 /* COMPONENT */
        ? hostParentNode(oldVNode.el)
        :
        // 在其他情况下,父容器实际上并没有被使用,所以这里只传递 Block 元素即可
        fallbackContainer
    patch(oldVNode, newVNode, container, null, parentComponent, parentSuspense, isSVG, true)
  }
}

patchBlockChildren 的实现很简单,遍历新的动态子节点数组,拿到对应的新旧动态子节点,并执行 patch 更新子节点即可。

这样一来,更新的复杂度就变成和动态节点的数量正相关,而不与模板大小正相关,如果一个模板的动静比越低,那么性能优化的效果就越明显。

问题解答

我们从编译阶段到运行时了解了整个 Block Tree 的实现,接下来回到问题本身:Block 数组是一维的,但是动态的子节点可能有嵌套关系,patchBlockChildren 内部也是递归执行了 patch 函数,那么在整个更新的过程中,会出现子节点重复更新的情况吗,为什么?

其实你了解了整个 Block Tree 的构造过程,回答这个问题并不难,首先这个题有迷惑性,Block 数组是一维的没错,patchBlockChildren 的时候,会遍历所有的动态节点执行 patch,动态节点有两种情况,要么是普通的动态 vnode,要么是嵌套的 Block vnode

如果是普通的动态 vnode,再次执行 patch 的时候还会执行到 patchElement,这个时候 vnode.dynamicChildrennull,并且由于 optimizetrue,所以压根不会执行 patchChildren 去更新子节点。言下之意,在这种优化的场景下,普通的动态 vnode 执行 patchElement 只会更新自身的 props,而不会更新它的子节点,所以即使动态 vnode 出现嵌套也没有关系。

这么做其实是非常好理解的,因为动态 vnode 的所有动态子节点已经被收集到 currentBlock 中了,遍历 currentBlock(也就是前面提到的 dynamicChildren) 的时候就会完成他们的更新。

那么,如果更新的节点是一个 Block vnode 的话,那么很简单,Block vnode 是有 dynamicChildren 的,递归执行 patchBlockChildren 即可,通过递归的方式,就可以完成组件下所有动态节点的更新了。

总结

综上,我们再次复习了 Vue.js 3.0 基于 Block Tree 的模板编译优化的实现原理,当然 Vue.js 在编译时的优化不止于此,还有静态提升等其它优化手段,不过 Block Tree 的设计可谓是相当惊艳了。

而网上那些无脑吹用 JSX 写 Vue.js 3.0 项目的人,是否认真研究过 Vue.js 3.0 模板编译带来运行时的性能提升呢?

相比模板的直观,JSX 更加灵活,各有各的使用场景,没有必要一味地吹捧其中一个甚至踩另一个,适合的才是最好的。

我出这个题主要是希望你能做到以下两点:

  1. 从编译到运行时阶段全方位彻底了解 Block Tree 的实现原理。

  2. 搞清楚 patch 过程在优化场景和非优化场景的异同。

要记住,分析和思考的过程远比答案重要。

交流讨论

欢迎关注公众号「前端试炼」,公众号平时会分享一些实用或者有意思的东西,发现代码之美。专注深度和最佳实践,希望打造一个高质量的公众号。

fa19c7b657c7e0c944cbfd2d99dd1ff0.png

公众号后台回复「小炼」加我微信,带你飞。

如果觉得这篇文章还不错,来个【分享、点赞、在看】三连吧,让更多的人也看到~

f577473a2aabfca2194878a2320bcaab.gif

http://www.lbrq.cn/news/2565919.html

相关文章:

  • 福州做网站的个体户电话查询知了seo
  • 工业产品设计流程图百度竞价优化
  • 互联网网站模块广州优化疫情防控举措
  • 利用php做网站教程公司网站建设步骤
  • 网站制作公司武汉网络营销策划案范本
  • 三好街做网站的公司信息流优化师面试常见问题
  • 商业网站备案流程近三年成功的营销案例
  • 做问答的网站线上营销
  • 新疆网站建设介绍谷歌搜索引擎免费入口2022
  • 淘宝哪些做网站关键词排名的有用吗谷歌浏览器怎么下载
  • seo站长工具查询武汉网站排名提升
  • 做二手设备的网站成人再就业培训班
  • 招聘网站做销售营销网站建设都是专业技术人员
  • 武汉专业网站制作设计怎么注册个人网站
  • 做网站需要营业执照嘛外贸网站推广怎么做
  • 门户网站建设厂商名录谷歌代运营
  • 做视频网站容易收录吗郑州网站建设方案
  • 织梦 公司网站模板代做网页设计平台
  • 网站做信息流全网网络营销
  • 人大门户网站建设方案推广产品的软文怎么写
  • 绍兴网站建设模板网站百度浏览器官方下载
  • 专门做海外服装购的网站有哪些网站策划书案例
  • 大淘客网站建设nba赛季排名
  • 好便宜建站网络推广渠道公司
  • 个人网站设计内容和要求百度云
  • 邢台12345网站域名访问网站怎么进入
  • 西安企业网站日本产品和韩国产品哪个好
  • 厦门建设局网站技司学校百度推广官网入口
  • 杭州网站的特点百度一下手机版网页
  • 微网站建设包括哪些香水推广软文
  • Java高性能编程实践指南
  • 梯度下降的基本原理
  • Java与Kotlin中“==“、“====“区别
  • 安装 docker compose v2版 笔记250731
  • 【抄袭】思科交换机DAI(动态ARP监控)配置测试
  • 可计算存储(Computational Storage)与DPU(Data Processing Unit)的技术特点对比及实际应用场景分析