【Vue3】源码探索之旅:compiler-core之parseChildren函数(二)

简言

parseChildren函数是在baseParse函数中作为createRoot函数的子节点参数传入的,今天来探索下parseChildren函数。
parseChildren在 compiler-core/src/parse.ts文件内
在这里插入图片描述

parseChildren

这个函数就是用来解析模板字符串内容的 ,里面有个while循环以栈的形式不断读取模板字符串进行解析,直到解析完毕,然后进行空白处理并返回对应的节点数据。

传参:

  • context ---- 解析上下文(createParserContext函数根据模板字符串和配置得到的)。
  • mode ---- 文本模式 。vue将模板字符串分为5个类型(见下面:DATA代表普通的元素标签,RCDATA代表textarea标签,RAWTEXT代表文本)。
  • ancestors — 祖先节点,类型为元素节点数组。

文本模式:

export const enum TextModes {
  //          | Elements | Entities | End sign              | Inside of
  DATA, //    | ✔        | ✔        | End tags of ancestors |
  RCDATA, //  | ✘        | ✔        | End tag of the parent | <textarea>
  RAWTEXT, // | ✘        | ✘        | End tag of the parent | <style>,<script>
  CDATA,
  ATTRIBUTE_VALUE
}

源码

function parseChildren(
  context: ParserContext,
  mode: TextModes,
  ancestors: ElementNode[]
): TemplateChildNode[] {
  const parent = last(ancestors)
  const ns = parent ? parent.ns : Namespaces.HTML
  const nodes: TemplateChildNode[] = []

  while (!isEnd(context, mode, ancestors)) {
    __TEST__ && assert(context.source.length > 0)
    const s = context.source
    let node: TemplateChildNode | TemplateChildNode[] | undefined = undefined

    if (mode === TextModes.DATA || mode === TextModes.RCDATA) {
      if (!context.inVPre && startsWith(s, context.options.delimiters[0])) {
        // '{{'
        node = parseInterpolation(context, mode)
      } else if (mode === TextModes.DATA && s[0] === '<') {
        // https://html.spec.whatwg.org/multipage/parsing.html#tag-open-state
        if (s.length === 1) {
          emitError(context, ErrorCodes.EOF_BEFORE_TAG_NAME, 1)
        } else if (s[1] === '!') {
          // https://html.spec.whatwg.org/multipage/parsing.html#markup-declaration-open-state
          if (startsWith(s, '<!--')) {
            node = parseComment(context)
          } else if (startsWith(s, '<!DOCTYPE')) {
            // Ignore DOCTYPE by a limitation.
            node = parseBogusComment(context)
          } else if (startsWith(s, '<![CDATA[')) {
            if (ns !== Namespaces.HTML) {
              node = parseCDATA(context, ancestors)
            } else {
              emitError(context, ErrorCodes.CDATA_IN_HTML_CONTENT)
              node = parseBogusComment(context)
            }
          } else {
            emitError(context, ErrorCodes.INCORRECTLY_OPENED_COMMENT)
            node = parseBogusComment(context)
          }
        } else if (s[1] === '/') {
          // https://html.spec.whatwg.org/multipage/parsing.html#end-tag-open-state
          if (s.length === 2) {
            emitError(context, ErrorCodes.EOF_BEFORE_TAG_NAME, 2)
          } else if (s[2] === '>') {
            emitError(context, ErrorCodes.MISSING_END_TAG_NAME, 2)
            advanceBy(context, 3)
            continue
          } else if (/[a-z]/i.test(s[2])) {
            emitError(context, ErrorCodes.X_INVALID_END_TAG)
            parseTag(context, TagType.End, parent)
            continue
          } else {
            emitError(
              context,
              ErrorCodes.INVALID_FIRST_CHARACTER_OF_TAG_NAME,
              2
            )
            node = parseBogusComment(context)
          }
        } else if (/[a-z]/i.test(s[1])) {
          node = parseElement(context, ancestors)

          // 2.x <template> with no directive compat
          if (
            __COMPAT__ &&
            isCompatEnabled(
              CompilerDeprecationTypes.COMPILER_NATIVE_TEMPLATE,
              context
            ) &&
            node &&
            node.tag === 'template' &&
            !node.props.some(
              p =>
                p.type === NodeTypes.DIRECTIVE &&
                isSpecialTemplateDirective(p.name)
            )
          ) {
            __DEV__ &&
              warnDeprecation(
                CompilerDeprecationTypes.COMPILER_NATIVE_TEMPLATE,
                context,
                node.loc
              )
            node = node.children
          }
        } else if (s[1] === '?') {
          emitError(
            context,
            ErrorCodes.UNEXPECTED_QUESTION_MARK_INSTEAD_OF_TAG_NAME,
            1
          )
          node = parseBogusComment(context)
        } else {
          emitError(context, ErrorCodes.INVALID_FIRST_CHARACTER_OF_TAG_NAME, 1)
        }
      }
    }
    if (!node) {
      node = parseText(context, mode)
    }

    if (isArray(node)) {
      for (let i = 0; i < node.length; i++) {
        pushNode(nodes, node[i])
      }
    } else {
      pushNode(nodes, node)
    }
  }

  // Whitespace handling strategy like v2
  let removedWhitespace = false
  if (mode !== TextModes.RAWTEXT && mode !== TextModes.RCDATA) {
    const shouldCondense = context.options.whitespace !== 'preserve'
    for (let i = 0; i < nodes.length; i++) {
      const node = nodes[i]
      if (node.type === NodeTypes.TEXT) {
        if (!context.inPre) {
          if (!/[^\t\r\n\f ]/.test(node.content)) {
            const prev = nodes[i - 1]
            const next = nodes[i + 1]
            // Remove if:
            // - the whitespace is the first or last node, or:
            // - (condense mode) the whitespace is between twos comments, or:
            // - (condense mode) the whitespace is between comment and element, or:
            // - (condense mode) the whitespace is between two elements AND contains newline
            if (
              !prev ||
              !next ||
              (shouldCondense &&
                ((prev.type === NodeTypes.COMMENT &&
                  next.type === NodeTypes.COMMENT) ||
                  (prev.type === NodeTypes.COMMENT &&
                    next.type === NodeTypes.ELEMENT) ||
                  (prev.type === NodeTypes.ELEMENT &&
                    next.type === NodeTypes.COMMENT) ||
                  (prev.type === NodeTypes.ELEMENT &&
                    next.type === NodeTypes.ELEMENT &&
                    /[\r\n]/.test(node.content))))
            ) {
              removedWhitespace = true
              nodes[i] = null as any
            } else {
              // Otherwise, the whitespace is condensed into a single space
              node.content = ' '
            }
          } else if (shouldCondense) {
            // in condense mode, consecutive whitespaces in text are condensed
            // down to a single space.
            node.content = node.content.replace(/[\t\r\n\f ]+/g, ' ')
          }
        } else {
          // #6410 normalize windows newlines in <pre>:
          // in SSR, browsers normalize server-rendered \r\n into a single \n
          // in the DOM
          node.content = node.content.replace(/\r\n/g, '\n')
        }
      }
      // Remove comment nodes if desired by configuration.
      else if (node.type === NodeTypes.COMMENT && !context.options.comments) {
        removedWhitespace = true
        nodes[i] = null as any
      }
    }
    if (context.inPre && parent && context.options.isPreTag(parent.tag)) {
      // remove leading newline per html spec
      // https://html.spec.whatwg.org/multipage/grouping-content.html#the-pre-element
      const first = nodes[0]
      if (first && first.type === NodeTypes.TEXT) {
        first.content = first.content.replace(/^\r?\n/, '')
      }
    }
  }

  return removedWhitespace ? nodes.filter(Boolean) : nodes
}

下面的流程只描述大致的流程信息,若有误请联系作者改正。

流程:

  • 创建nodes数组用来承载解析的数据,进入while循环。
  • 执行isEnd函数判断是否读取完毕,是则跳出while循环。
  • 创建node用于承载此次循环解析的数据,并获取当前读取的内容。
  • TextModes.DATA和TextModes.RCDATA下进行特定的分类解析。
  • 若是是否是以{{开头,parseInterpolation函数进行文本插值解析。
  • 若是TextModes.DATA下且以<开头,进行标签解析,进入标签类型的解析。
  • 若以<开头且下一个字符是!,说明是注释标签、文档声明标签、<![CDATA[]]>标签(忽略xml的转义);分别进行对应的解析。
  • 若以<开头且下一个字符是/,有可能是闭合标签,则进行parseTag函数标签解析。
  • 若以<开头且下一个字符是a-z任意字符(不分大小写),进行parseElement函数解析。
  • 若node还没值,执行parseText函数按文本进行解析。
  • 将node的值添加到nodes中,执行第2步
  • 将nodes值按照配置的空白策略进行空白处理
  • 返回nodes。

使用到的一些函数

只挑选用到的一些函数进行分析。

isEnd

判断是否读取完毕。
参考:

  • startsWith函数是 以什么开头。
  • startsWithEndTagOpen 是否是完整的相匹配的闭合标签。
    true的情况:
  • TextModes.DATA下以</开头且是匹配的闭合标签,返回true
  • TextModes.RCDATA(textarea)和TextModes.RAWTEXT(文本)下,存在父节点,且匹配父节点的闭合标签,返回true。
  • TextModes.CDATA(xml中的忽略转义的写法)下,以]]>开头,返回true。
  • s 为假值,返回true
function isEnd(
  context: ParserContext,
  mode: TextModes,
  ancestors: ElementNode[]
): boolean {
  const s = context.source

  switch (mode) {
    case TextModes.DATA:
      if (startsWith(s, '</')) {
        // TODO: probably bad performance
        for (let i = ancestors.length - 1; i >= 0; --i) {
          if (startsWithEndTagOpen(s, ancestors[i].tag)) {
            return true
          }
        }
      }
      break

    case TextModes.RCDATA:
    case TextModes.RAWTEXT: {
      const parent = last(ancestors)
      if (parent && startsWithEndTagOpen(s, parent.tag)) {
        return true
      }
      break
    }

    case TextModes.CDATA:
      if (startsWith(s, ']]>')) {
        return true
      }
      break
  }

  return !s
}

parseInterpolation

解析文本插值内容,也就是{{表达式}}这种,最后返回文本插值类型的结构数据节点。
参考:

  • parseTextData ---- 处理 {{表达式}}内的表达式。
  • getCursor — 获取当前读取位置
  • advanceBy — 改变上下文中的source
  • advancePositionWithMutation ---- 修正读取位置信息描述的函数。

文本插值大家都知道,它是vue的数据在模板显示的主要方式之一,里面可以填表达式或者静态字符串,所以{{}}里面的内容使用parseTextData函数解析,这个函数要返回文本字符串,要么返回一个解析好后的html实体。

function parseInterpolation(
  context: ParserContext,
  mode: TextModes
): InterpolationNode | undefined {
  const [open, close] = context.options.delimiters
  __TEST__ && assert(startsWith(context.source, open))

  const closeIndex = context.source.indexOf(close, open.length)
  if (closeIndex === -1) {
    emitError(context, ErrorCodes.X_MISSING_INTERPOLATION_END)
    return undefined
  }

  const start = getCursor(context)
  advanceBy(context, open.length)
  const innerStart = getCursor(context)
  const innerEnd = getCursor(context)
  const rawContentLength = closeIndex - open.length
  const rawContent = context.source.slice(0, rawContentLength)
  const preTrimContent = parseTextData(context, rawContentLength, mode)
  const content = preTrimContent.trim()
  const startOffset = preTrimContent.indexOf(content)
  if (startOffset > 0) {
    advancePositionWithMutation(innerStart, rawContent, startOffset)
  }
  const endOffset =
    rawContentLength - (preTrimContent.length - content.length - startOffset)
  advancePositionWithMutation(innerEnd, rawContent, endOffset)
  advanceBy(context, close.length)

  return {
    type: NodeTypes.INTERPOLATION,
    content: {
      type: NodeTypes.SIMPLE_EXPRESSION,
      isStatic: false,
      // Set `isConstant` to false by default and will decide in transformExpression
      constType: ConstantTypes.NOT_CONSTANT,
      content,
      loc: getSelection(context, innerStart, innerEnd)
    },
    loc: getSelection(context, start)
  }
}

parseComment

解析注释的函数,返回注释类型的结构数据节点。
参考:

  • emitError 触发报错的函数。
  • getSelection 返回包含目标内容的位置信息对象。
function parseComment(context: ParserContext): CommentNode {
  __TEST__ && assert(startsWith(context.source, '<!--'))

  const start = getCursor(context)
  let content: string

  // Regular comment.
  const match = /--(\!)?>/.exec(context.source)
  if (!match) {
    content = context.source.slice(4)
    advanceBy(context, context.source.length)
    emitError(context, ErrorCodes.EOF_IN_COMMENT)
  } else {
    if (match.index <= 3) {
      emitError(context, ErrorCodes.ABRUPT_CLOSING_OF_EMPTY_COMMENT)
    }
    if (match[1]) {
      emitError(context, ErrorCodes.INCORRECTLY_CLOSED_COMMENT)
    }
    content = context.source.slice(4, match.index)

    // Advancing with reporting nested comments.
    const s = context.source.slice(0, match.index)
    let prevIndex = 1,
      nestedIndex = 0
    while ((nestedIndex = s.indexOf('<!--', prevIndex)) !== -1) {
      advanceBy(context, nestedIndex - prevIndex + 1)
      if (nestedIndex + 4 < s.length) {
        emitError(context, ErrorCodes.NESTED_COMMENT)
      }
      prevIndex = nestedIndex + 1
    }
    advanceBy(context, match.index + match[0].length - prevIndex + 1)
  }

  return {
    type: NodeTypes.COMMENT,
    content,
    loc: getSelection(context, start)
  }
}

parseElement

解析元素内容的函数,返回元素类型的结构数据节点。
一个普通的元素包含: 开标签 + 子内容 + 闭标签;所以parseElement就是按这个顺序处理的,parseTag处理标签,parseChildren函数处理子内容。
参考:

  • parseTag — 解析标签的函数。
  • checkCompatEnabled — 判断是否可以进行兼容处理。
function parseElement(
  context: ParserContext,
  ancestors: ElementNode[]
): ElementNode | undefined {
  __TEST__ && assert(/^<[a-z]/i.test(context.source))

  // Start tag.
  const wasInPre = context.inPre
  const wasInVPre = context.inVPre
  const parent = last(ancestors)
  const element = parseTag(context, TagType.Start, parent)
  const isPreBoundary = context.inPre && !wasInPre
  const isVPreBoundary = context.inVPre && !wasInVPre

  if (element.isSelfClosing || context.options.isVoidTag(element.tag)) {
    // #4030 self-closing <pre> tag
    if (isPreBoundary) {
      context.inPre = false
    }
    if (isVPreBoundary) {
      context.inVPre = false
    }
    return element
  }

  // Children.
  ancestors.push(element)
  const mode = context.options.getTextMode(element, parent)
  const children = parseChildren(context, mode, ancestors)
  ancestors.pop()

  // 2.x inline-template compat
  if (__COMPAT__) {
    const inlineTemplateProp = element.props.find(
      p => p.type === NodeTypes.ATTRIBUTE && p.name === 'inline-template'
    ) as AttributeNode
    if (
      inlineTemplateProp &&
      checkCompatEnabled(
        CompilerDeprecationTypes.COMPILER_INLINE_TEMPLATE,
        context,
        inlineTemplateProp.loc
      )
    ) {
      const loc = getSelection(context, element.loc.end)
      inlineTemplateProp.value = {
        type: NodeTypes.TEXT,
        content: loc.source,
        loc
      }
    }
  }

  element.children = children

  // End tag.
  if (startsWithEndTagOpen(context.source, element.tag)) {
    parseTag(context, TagType.End, parent)
  } else {
    emitError(context, ErrorCodes.X_MISSING_END_TAG, 0, element.loc.start)
    if (context.source.length === 0 && element.tag.toLowerCase() === 'script') {
      const first = children[0]
      if (first && startsWith(first.loc.source, '<!--')) {
        emitError(context, ErrorCodes.EOF_IN_SCRIPT_HTML_COMMENT_LIKE_TEXT)
      }
    }
  }

  element.loc = getSelection(context, element.loc.start)

  if (isPreBoundary) {
    context.inPre = false
  }
  if (isVPreBoundary) {
    context.inVPre = false
  }
  return element
}

parseText

解析文本内容,返回文本类型的结构数据节点。

function parseText(context: ParserContext, mode: TextModes): TextNode {
  __TEST__ && assert(context.source.length > 0)

  const endTokens =
    mode === TextModes.CDATA ? [']]>'] : ['<', context.options.delimiters[0]]

  let endIndex = context.source.length
  for (let i = 0; i < endTokens.length; i++) {
    const index = context.source.indexOf(endTokens[i], 1)
    if (index !== -1 && endIndex > index) {
      endIndex = index
    }
  }

  __TEST__ && assert(endIndex > 0)

  const start = getCursor(context)
  const content = parseTextData(context, endIndex, mode)

  return {
    type: NodeTypes.TEXT,
    content,
    loc: getSelection(context, start)
  }
}

parseBogusComment

解析伪注释内容,返回注释类型的结构数据节点。
一些内容会被这个函数当作注释解析,并会给出警告信息。

function parseBogusComment(context: ParserContext): CommentNode | undefined {
  __TEST__ && assert(/^<(?:[\!\?]|\/[^a-z>])/i.test(context.source))

  const start = getCursor(context)
  const contentStart = context.source[1] === '?' ? 1 : 2
  let content: string

  const closeIndex = context.source.indexOf('>')
  if (closeIndex === -1) {
    content = context.source.slice(contentStart)
    advanceBy(context, context.source.length)
  } else {
    content = context.source.slice(contentStart, closeIndex)
    advanceBy(context, closeIndex + 1)
  }

  return {
    type: NodeTypes.COMMENT,
    content,
    loc: getSelection(context, start)
  }
}

结语

上篇:【vue3】vue3源码探索之旅:compiler-core(一)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/367214.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

使用输出事件激活Simulink块

什么是输出事件? 输出事件是发生在Stateflow图表中,但在图表外的Simulink块中可见的事件。这种类型的事件允许图表将图表中发生的事件通知模型中的其他块。 您可以使用输出事件来激活同一模型中的其他块。您可以在图表中定义多个输出事件,其中每个输出事件映射到一个输出端…

Quick BI中lod函数之lod_include

一、lod函数简介 LOD函数的全称是详细级别表达式&#xff08;Level Of Detail Expressisons&#xff09;。它主要是为了克服一些表达式之间计算颗粒度不一致的问题。比如&#xff0c;要计算第一季度各月销售收入占比&#xff0c;这里分子计算颗粒度为’月’&#xff0c;但是分…

聊聊ClickHouse MergeTree引擎的固定/自适应索引粒度

前言 我们在刚开始学习ClickHouse的MergeTree引擎时&#xff0c;就会发现建表语句的末尾总会有SETTINGS index_granularity 8192这句话&#xff08;其实不写也可以&#xff09;&#xff0c;表示索引粒度为8192。在每个data part中&#xff0c;索引粒度参数的含义有二&#xf…

pytorch 利用Tensorboar记录训练过程loss变化

文章目录 1. LossHistory日志类定义2. LossHistory类的使用2.1 实例化LossHistory2.2 记录每个epoch的loss2.3 训练结束close掉SummaryWriter 3. 利用Tensorboard 可视化3.1 显示可视化效果 参考 利用Tensorboard记录训练过程中每个epoch的训练loss以及验证loss&#xff0c;便于…

布隆过滤器的概述和使用

1 布隆过滤器概述 1.1 概述 布隆过滤器&#xff08;Bloom Filter&#xff09;是1970年由布隆提出的。它实际上是由一个很长的二进制向量&#xff08;数组&#xff09;和一系列随机映射函数&#xff08;hash函数&#xff09;组成&#xff0c;它不存放数据的明细内容&#xff0…

FANUC机器人开机时无法进入系统,示教器黑屏故障处理总结

FANUC机器人开机时无法进入系统&#xff0c;示教器黑屏故障处理总结 故障描述&#xff1a; FANUC机器人开机时&#xff0c;示教器在初始化时显示&#xff1a;EMAC initial call failed&#xff08;示教器上电时会进入boot画面&#xff0c;左上角会出现一些白色的英文提示&#…

YOLOv5白皮书-第Y3周:yolov5s.yaml文件解读

YOLOv5白皮书-第Y3周:yolov5s.yaml文件解读 YOLOv5白皮书-第Y3周:yolov5s.yaml文件解读一、前言二、我的环境三、yolov5s.yaml源文件内容四、Parameters五、anchors配置六、backbone七、head八、总结 OLOv5-第Y2周&#xff1a;训练自己的数据集) YOLOv5白皮书-第Y3周:yolov5s.…

学习日志以及个人总结 (16)

共用体 共用体 union 共用体名 { 成员列表&#xff1b; }&#xff1b;//表示定义一个共用体类型 注意&#xff1a; 1.共用体 初始化 --- 只能给一个值&#xff0c;默认是给到第一个成员变量 2.共用体成员变量辅助 3.可以判断大小端 ----※&#xff01;&#xff01; 实际用途…

猫用空气净化器好吗?好用的养猫宠物空气净化器品牌推荐

作为一个养猫五年的资深铲屎官&#xff0c;我对如何轻松快乐地养猫有一些心得。猫咪每天在家里奔跑&#xff0c;导致家里经常会出现“猫毛雪”&#xff0c;沙发、地板和衣服都成了重灾区。在除猫毛的问题上&#xff0c;我真的尝试了各种方法&#xff0c;几乎用上了所有的技能。…

2024美赛预测算法 | 回归预测 | Matlab基于RIME-LSSVM霜冰算法优化最小二乘支持向量机的数据多输入单输出回归预测

2024美赛预测算法 | 回归预测 | Matlab基于RIME-LSSVM霜冰算法优化最小二乘支持向量机的数据多输入单输出回归预测 目录 2024美赛预测算法 | 回归预测 | Matlab基于RIME-LSSVM霜冰算法优化最小二乘支持向量机的数据多输入单输出回归预测预测效果基本介绍程序设计参考资料 预测效…

2024美赛E题数学建模思路代码数据分享

2024 ICM Problem E: Sustainability of Property Insurance 本题要求选取不同大陆上经历极端天气的两个地区来为保险公司开发模型&#xff0c;本题的重点是找到尽可能多而全的数据&#xff0c;包括天气数据&#xff0c;经济数据&#xff0c;人口数据等。 模型选择&#xff1a…

《最新出炉》系列入门篇-Python+Playwright自动化测试-10-标签页操作(tab)

1.简介 标签操作其实也是基于浏览器上下文&#xff08;BrowserContext&#xff09;进行操作的&#xff0c;而且宏哥在之前的BrowserContext也有提到过&#xff0c;但是有的童鞋或者小伙伴还是不清楚怎么操作&#xff0c;或者思路有点模糊&#xff0c;因此今天单独来对其进行讲…

Windows内存管理 - 物理内存概念(Physical Memory Address)

作为windows驱动程序的程序员&#xff0c;需要比普通程序员更多的了解Windows内部的内存管理机制&#xff0c;并在驱动程序中有效地使用内存。在驱动程序编写中&#xff0c;分配和管理内存不能使用熟知的Win32 API函数&#xff0c;取而代之的是DDK提供的高效的内核函数。程序员…

PKG系统安装包及IPSW固件:MacOS 11-14 Sonoma 正式版

MacOS 14 Sonoma&#xff0c;为提高生产力和创造力带来了全新的功能&#xff0c;有了更多使用小部件和令人惊叹的新屏幕保护程序进行个性化设置的方法&#xff0c;对Safari浏览器和视频会议进行了重大更新&#xff0c;以及优化的游戏体验——Mac体验比以往任何时候都更好。 mac…

MySQL篇----第三篇

系列文章目录 文章目录 系列文章目录前言一、InnoDB与MyISAM的区别二、索引三、常见索引原则有前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章男女通用,看懂了就去分享给你的码吧。 一、InnoDB与MyISAM…

【Android Studio 启动出错】

Android Studio版本&#xff1a;2022.3.1 出错前操作&#xff1a; 昨晚开着三四个项目&#xff0c;然后太晚了直接关机睡觉&#xff0c;第二天起来开机&#xff0c;启动Android Studio&#xff0c;就出现了这个问题&#xff1a; Internal error. Please refer to https://co…

opencv+mediapipe 手势识别控制电脑音量(详细注释解析)

前段时间社团布置了一个手势识别控制电脑音量的小任务&#xff0c;今天记录一下学习过程&#xff0c;将大佬作品在我的贫瘠的基础上解释一下~ 项目主要由以下4个步骤组成&#xff1a; 1、使用OpenCV读取摄像头视频流 2、识别手掌关键点像素坐标 3、根据拇指和食指指尖的坐标…

今日早报 每日精选15条新闻简报 每天一分钟 知晓天下事 2月3日,星期六

每天一分钟&#xff0c;知晓天下事&#xff01; 2024年2月3日 星期六 农历腊月廿四 南小年 1、 气象局&#xff1a;将雨雪冰冻三级应急响应提升为二级&#xff0c;针对性做好春运气象保障服务。 2、 教育部&#xff1a;鼓励银龄教师投身西部地区、民族地区民办学校。 3、 四部…

解决java.lang.ClassCastException

目录 问题 原因 解决方案 问题 前后端分离开发中&#xff0c;往往需要统一封装返回数据用到一个Result<T>类包装多个接口&#xff1a; 重复劳动并不优雅&#xff0c;于是想用RestControllerAdvice做控制器拦截增强&#xff0c;进行封装。 代码如下&#xff1a; Res…

[Python] 什么是PCA降维技术以及scikit-learn中PCA类使用案例(图文教程,含详细代码)

什么是维度&#xff1f; 对于Numpy中数组来说&#xff0c;维度就是功能shape返回的结果&#xff0c;shape中返回了几个数字&#xff0c;就是几维。索引以外的数据&#xff0c;不分行列的叫一维&#xff08;此时shape返回唯一的维度上的数据个数&#xff09;&#xff0c;有行列…