vue?parseHTML?函数源码解析

news2024/9/23 13:28:06
目录
  • 正文
  • 函数开头定义的一些常量和变量
  • while 循环
    • textEnd ===0
  • parseStartTag 函数解析开始标签
  • 总结:

正文

接上篇:

Vue编译器源码分析AST 抽象语法树

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

function parseHTML(html, options) {

    var stack = [];

    var expectHTML = options.expectHTML;

    var isUnaryTag$$1 = options.isUnaryTag || no;

    var canBeLeftOpenTag$$1 = options.canBeLeftOpenTag || no;

    var index = 0;

    var last, lastTag;

    // 开启一个 while 循环,循环结束的条件是 html 为空,即 html 被 parse 完毕

    while (html) {

        last = html;

        if (!lastTag || !isPlainTextElement(lastTag)) {

            // 确保即将 parse 的内容不是在纯文本标签里 (script,style,textarea)

        } else {

            // parse 的内容是在纯文本标签里 (script,style,textarea)

        }

        //将整个字符串作为文本对待

        if (html === last) {

            options.chars && options.chars(html);

            if (!stack.length && options.warn) {

                options.warn(("Mal-formatted tag at end of template: \"" + html + "\""));

            }

            break

        }

    }

    // Clean up any remaining tags

    parseEndTag();

    function advance(n) {

        index += n;

        html = html.substring(n);

    }

    //parse 开始标签

    function parseStartTag() {

        //...

    }

    //处理 parseStartTag 的结果

    function handleStartTag(match) {

        //...

    }

    //parse 结束标签

    function parseEndTag(tagName, start, end) {

        //...

    }

}

可以看到 parseHTML 函数接收两个参数:html 和 options ,其中 html 是要被编译的字符串,而options则是编译器所需的选项。

整体上来讲 parseHTML分为三部分。

  • 函数开头定义的一些常量和变量
  • while 循环
  • parse 过程中需要用到的 analytic function

函数开头定义的一些常量和变量

先从第一部分开始讲起

?

1

2

3

4

5

6

var stack = [];

var expectHTML = options.expectHTML;

var isUnaryTag$$1 = options.isUnaryTag || no;

var canBeLeftOpenTag$$1 = options.canBeLeftOpenTag || no;

var index = 0;

var last, lastTag;

第一个变量是 stack,它被初始化为一个空数组,在 while 循环中处理 html 字符流的时候每当遇到一个非单标签,都会将该开始标签 push 到该数组。它的作用模板中 DOM 结构规范性的检测。

但在一个 html 字符串中,如何判断一个非单标签是否缺少结束标签呢?

假设我们有如下html字符串:

?

1

<div><p><span></p></div>

在编译这个字符串的时候,首先会遇到 div 开始标签,并将该 push 到 stack 数组,然后会遇到 p 开始标签,并将该标签 push 到 stack ,接下来会遇到 span 开始标签,同样被 push 到 stack ,此时 stack 数组内包含三个元素。

再然后便会遇到 p 结束标签,按照正常逻辑可以推理出最先遇到的结束标签,其对应的开始标签应该最后被push到 stack 中,也就是说 stack 栈顶的元素应该是 span ,如果不是 span 而是 p,这说明 span 元素缺少闭合标签。

这就是检测 html 字符串中是否缺少闭合标签的原理。

第二个变量是 expectHTML,它的值被初始化为 options.expectHTML,也就是编译器选项中的 expectHTML。

第三个常量是 isUnaryTag,用来检测一个标签是否是一元标签。

第四个常量是 canBeLeftOpenTag,用来检测一个标签是否是可以省略闭合标签的非一元标签。

  • index 初始化为 0 ,标识着当前字符流的读入位置。
  • last 存储剩余还未编译的 html 字符串。
  • lastTag 始终存储着位于 stack 栈顶的元素。

while 循环

接下来将进入第二部分,即开启一个 while 循环,循环的终止条件是 html 字符串为空,即html 字符串全部编译完毕。

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

while (html) {

    last = html;

    // Make sure we're not in a plaintext content element like script/style

    if (!lastTag || !isPlainTextElement(lastTag)) {

        var textEnd = html.indexOf('<');

        if (textEnd === 0) {

            // Comment:

            if (comment.test(html)) {

                var commentEnd = html.indexOf('-->');

                if (commentEnd >= 0) {

                    if (options.shouldKeepComment) {

                        options.comment(html.substring(4, commentEnd));

                    }

                    advance(commentEnd + 3);

                    continue

                }

            }

            // http://en.wikipedia.org/wiki/Conditional_comment#Downlevel-revealed_conditional_comment

            if (conditionalComment.test(html)) {

                var conditionalEnd = html.indexOf(']>');

                if (conditionalEnd >= 0) {

                    advance(conditionalEnd + 2);

                    continue

                }

            }

            // Doctype:

            var doctypeMatch = html.match(doctype);

            if (doctypeMatch) {

                advance(doctypeMatch[0].length);

                continue

            }

            // End tag:

            var endTagMatch = html.match(endTag);

            if (endTagMatch) {

                var curIndex = index;

                advance(endTagMatch[0].length);

                parseEndTag(endTagMatch[1], curIndex, index);

                continue

            }

            // Start tag:

            var startTagMatch = parseStartTag();

            if (startTagMatch) {

                handleStartTag(startTagMatch);

                if (shouldIgnoreFirstNewline(startTagMatch.tagName, html)) {

                    advance(1);

                }

                continue

            }

        }

        var text = (void 0),

            rest = (void 0),

            next = (void 0);

        if (textEnd >= 0) {

            rest = html.slice(textEnd);

            while (

                !endTag.test(rest) &&

                !startTagOpen.test(rest) &&

                !comment.test(rest) &&

                !conditionalComment.test(rest)

            ) {

                // < in plain text, be forgiving and treat it as text

                next = rest.indexOf('<', 1);

                if (next < 0) {

                    break

                }

                textEnd += next;

                rest = html.slice(textEnd);

            }

            text = html.substring(0, textEnd);

            advance(textEnd);

        }

        if (textEnd < 0) {

            text = html;

            html = '';

        }

        if (options.chars && text) {

            options.chars(text);

        }

    } else {

        var endTagLength = 0;

        var stackedTag = lastTag.toLowerCase();

        var reStackedTag = reCache[stackedTag] || (reCache[stackedTag] = new RegExp('([\\s\\S]*?)(</' + stackedTag +

            '[^>]*>)', 'i'));

        var rest$1 = html.replace(reStackedTag, function(all, text, endTag) {

            endTagLength = endTag.length;

            if (!isPlainTextElement(stackedTag) && stackedTag !== 'noscript') {

                text = text

                    .replace(/<!\--([\s\S]*?)-->/g, '$1') // #7298

                    .replace(/<!\[CDATA\[([\s\S]*?)]]>/g, '$1');

            }

            if (shouldIgnoreFirstNewline(stackedTag, text)) {

                text = text.slice(1);

            }

            if (options.chars) {

                options.chars(text);

            }

            return ''

        });

        index += html.length - rest$1.length;

        html = rest$1;

        parseEndTag(stackedTag, index - endTagLength, index);

    }

    if (html === last) {

        options.chars && options.chars(html);

        if (!stack.length && options.warn) {

            options.warn(("Mal-formatted tag at end of template: \"" + html + "\""));

        }

        break

    }

}

首先将在每次循环开始时将 html 的值赋给变量 last :

?

1

last = html;

为什么这么做?在 while 循环即将结束的时候,有一个对 last 和 html 这两个变量的比较,在此可以找到答案:

?

1

if (html === last) {}

如果两者相等,则说明html 在经历循环体的代码之后没有任何改变,此时会"Mal-formatted tag at end of template: \"" + html + "\"" 错误信息提示。

接下来可以简单看下整体while循环的结构。

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

while (html) {

  last = html

  if (!lastTag || !isPlainTextElement(lastTag)) {

    // parse 的内容不是在纯文本标签里

  } else {

    // parse 的内容是在纯文本标签里 (script,style,textarea)

  }

  // 极端情况下的处理

  if (html === last) {

    options.chars && options.chars(html)

    if (process.env.NODE_ENV !== 'production' && !stack.length && options.warn) {

      options.warn(`Mal-formatted tag at end of template: "${html}"`)

    }

    break

  }

}

接下来我们重点来分析这个if else 中的代码。

?

1

!lastTag || !isPlainTextElement(lastTag)

lastTag 刚刚讲到它会一直存储 stack 栈顶的元素,但是当编译器刚开始工作时,他只是一个空数组对象,![] == false

isPlainTextElement(lastTag) 检测 lastTag 是否为纯标签内容。

?

1

var isPlainTextElement = makeMap('script,style,textarea', true);

lastTag 为空数组 ,isPlainTextElement(lastTag ) 返回false, !isPlainTextElement(lastTag) ==true, 有兴趣的同学可以阅读下 makeMap 源码。

接下来我们继续往下看,简化版的代码。

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

if (!lastTag || !isPlainTextElement(lastTag)) {

  var textEnd = html.indexOf('<')

  if (textEnd === 0) {

    // 第一个字符就是(<)尖括号

  }

 var text = (void 0),

     rest = (void 0),

     next = (void 0);

  if (textEnd >= 0) {

    //第一个字符不是(<)尖括号

  }

  if (textEnd < 0) {

    // 第一个字符不是(<)尖括号

  }

  if (options.chars && text) {

    options.chars(text)

  }

} else {

  // 省略 ...

}

textEnd ===0

当 textEnd === 0 时,说明 html 字符串的第一个字符就是左尖括号,比如 html 字符串为:<div>box</div>,那么这个字符串的第一个字符就是左尖括号(<)。

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

if (textEnd === 0) {

    // Comment: 如果是注释节点

    if (comment.test(html)) {

        var commentEnd = html.indexOf('-->');

        if (commentEnd >= 0) {

            if (options.shouldKeepComment) {

                options.comment(html.substring(4, commentEnd));

            }

            advance(commentEnd + 3);

            continue

        }

    }

    //如果是条件注释节点

    if (conditionalComment.test(html)) {

        var conditionalEnd = html.indexOf(']>');

        if (conditionalEnd >= 0) {

            advance(conditionalEnd + 2);

            continue

        }

    }

    // 如果是 Doctyp节点

    var doctypeMatch = html.match(doctype);

    if (doctypeMatch) {

        advance(doctypeMatch[0].length);

        continue

    }

    // End tag:  结束标签

    var endTagMatch = html.match(endTag);

    if (endTagMatch) {

        var curIndex = index;

        advance(endTagMatch[0].length);

        parseEndTag(endTagMatch[1], curIndex, index);

        continue

    }

    // Start tag: 开始标签

    var startTagMatch = parseStartTag();

    if (startTagMatch) {

        handleStartTag(startTagMatch);

        if (shouldIgnoreFirstNewline(startTagMatch.tagName, html)) {

            advance(1);

        }

        continue

    }

}

细枝末节我们不看,重点在End tag 、 Start tag 上。

我们先从解析标签开始分析

?

1

2

3

4

5

6

7

8

var startTagMatch = parseStartTag();

if (startTagMatch) {

    handleStartTag(startTagMatch);

    if (shouldIgnoreFirstNewline(startTagMatch.tagName, html)) {

        advance(1);

    }

    continue

}

parseStartTag 函数解析开始标签

解析开始标签会调用parseStartTag函数,如果有返回值,说明开始标签解析成功。

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

function parseStartTag() {

    var start = html.match(startTagOpen);

    if (start) {

        var match = {

            tagName: start[1],

            attrs: [],

            start: index

        };

        advance(start[0].length);

        var end, attr;

        while (!(end = html.match(startTagClose)) &amp;&amp; (attr = html.match(attribute))) {

            advance(attr[0].length);

            match.attrs.push(attr);

        }

        if (end) {

            match.unarySlash = end[1];

            advance(end[0].length);

            match.end = index;

            return match

        }

    }

}

parseStartTag 函数首先会调用 html 字符串的 match 函数匹配 startTagOpen 正则,前面我们分析过编译器所需的正则。

Vue编译器token解析规则-正则分析

如果匹配成功,那么start 将是一个包含两个元素的数组:第一个元素是标签的开始部分(包含< 和 标签名称);第二个元素是捕获组捕获到的标签名称。比如有如下template:

?

1

<div></div>

start为:

?

1

start = ['&lt;div', 'div']

接下来:

定义了 match 变量,它是一个对象,初始状态下拥有三个属性:

  • tagName:它的值为 start[1] 即标签的名称。
  • attrs :这个数组就是用来存储将来被匹配到的属性。
  • start:初始值为 index,是当前字符流读入位置在整个 html 字符串中的相对位置。

?

1

advance(start[0].length);

相对就比较简单了,他的作用就是在源字符中截取已经编译完成的字符,我们知道当html 字符为 “”,整个词法分析的工作就结束了,在这中间扮演重要角色的就是advance方法。

?

1

2

3

4

function advance(n) {

    index += n;

    html = html.substring(n);

}

接下来:

?

1

2

3

4

5

6

7

8

9

10

11

12

var end, attr;

while (!(end = html.match(startTagClose)) &amp;&amp; (attr = html.match(attribute))) {

    advance(attr[0].length);

    match.attrs.push(attr);

}

if (end) {

    match.unarySlash = end[1];

    advance(end[0].length);

    match.end = index;

    return match

  }

}

主要看while循环,循环的条件有两个,第一个条件是:没有匹配到开始标签的结束部分,这个条件的实现方式主要使用了 startTagClose 正则,并将结果保存到 end 变量中。

第二个条件是:匹配到了属性,主要使用了attribute正则。

总结下这个while循环成立要素:没有匹配到开始标签的结束部分,并且匹配到了开始标签中的属性,这个时候循环体将被执行,直到遇到开始标签的结束部分为止。

接下来在循环体内做了两件事,首先调用advance函数,参数为attr[0].length即整个属性的长度。然后会将此次循环匹配到的结果push到前面定义的match对象的attrs数组中。

?

1

2

advance(attr[0].length);

match.attrs.push(attr);

接下来看下最后这部分代码。

?

1

2

3

4

5

6

if (end) {

    match.unarySlash = end[1];

    advance(end[0].length);

    match.end = index;

    return match

}

首先判断了变量 end 是否为真,我们知道,即使匹配到了开始标签的开始部分以及属性部分但是却没有匹配到开始标签的结束部分,这说明这根本就不是一个开始标签。所以只有当变量end存在,即匹配到了开始标签的结束部分时,才能说明这是一个完整的开始标签。

如果变量end的确存在,那么将会执行 if 语句块内的代码,不过我们需要先了解一下变量end的值是什么?

比如当html(template)字符串如下时:

<br />

那么匹配到的end的值为:

end = ['/>', '/']

比如当html(template)字符串如下时:

<div></div>

那么匹配到的end的值为:

end = ['>', undefined]

结论如果end[1]不为undefined,那么说明该标签是一个一元标签。

那么现在再看if语句块内的代码,将很容易理解,首先在match对象上添加unarySlash属性,其值为end[1]

?

1

match.unarySlash = end[1];

然后调用advance函数,参数为end[0].length,接着在match 对象上添加了一个end属性,它的值为index,注意由于先调用的advance函数,所以此时的index已经被更新了。最后将match 对象作为 parseStartTag 函数的返回值返回。

只有当变量end存在时,即能够确定确实解析到了一个开始标签的时候parseStartTag函数才会有返回值,并且返回值是match对象,其他情况下parseStartTag全部返回undefined。

总结:

我们模拟假设有如下html(template)字符串:

?

1

<div id="box" v-if="watings"></div>

则parseStartTag函数的返回值如下:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

match = {

  tagName: 'div',

  attrs: [

    [

      'id="box"',

      'id',

      '=',

      'box',

      undefined,

      undefined

    ],

    [

      ' v-if="watings"',

      'v-if',

      '=',

      'watings',

      undefined,

      undefined

    ]

  ],

  start: index,

  unarySlash: undefined,

  end: index

}

我们讲解完了parseStartTag函数及其返回值,现在我们回到对开始标签的 parse 部分,接下来我们会继续讲解,拿到返回值之后的处理。

?

1

2

3

4

5

6

7

8

var startTagMatch = parseStartTag();

if (startTagMatch) {

    handleStartTag(startTagMatch);

    if (shouldIgnoreFirstNewline(startTagMatch.tagName, html)) {

        advance(1);

    }

    continue

}

篇幅有限请移步:

parseHTML 函数源码解析返回值后的处理

 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【遇到的问题】ServiceLoader.load(Driver.class),没有找到Driver接口对应的实体类

ServiceLoader ServiceLoder的SPIServiceLoader.load(Driver.class)的作用 代码详解出现的问题解决方式 ServiceLoder的SPI ServiceLoader.load(Driver.class)的作用 java.util.ServiceLoader工具类方法会使用ClassLoad类的getResources方法获取指定目录下的文件&#xff0c;…

C语言中的字符串输入(gets_s、fgets、scanf、fscanf)与相关内存分配知识

0. C语言的内存分配知识 分配内存空间有两种方式&#xff1a;静态内存分配和动态内存分配 0.1 静态内存分配 指的是在编译时确定数组等数据类型的大小&#xff0c;然后由计算机分配好&#xff0c;通常是存在栈上的数据 例如&#xff1a;在声明数组时&#xff0c;需要显示的指明…

Nacos 配置统一管理、热部署、多环境配置共享

目录 一、Nacos 配置统一管理 1.1、启动 Nacos 服务 1.2、Nacos 新建配置 1.3、引入依赖 1.4、Nacos 地址读取 1.5、演示效果 二、Nacos 配置热部署 三、多环境配置共享 一、Nacos 配置统一管理 1.1、启动 Nacos 服务 在当前文件下打开终端&#xff0c;输入如下指令启…

vue2实现卡片拖拽式课程表

目录 一、效果展示 二、代码分析 2.1、两栏布局、表格编写与课程卡片 2.2、初始化数据与渲染 2.3、拖拽卡片到表格&#xff0c;进行插入 2.4、自定义指令进行删除 一、效果展示 二、代码分析 主页面代码&#xff1a; <template><div class"board"&…

【QT】枚举常用宏(Q_ENUM,Q_FLAG,Q_DECLARE_FLAGS,Q_DECLARE_OPERATORS_FOR_FLAGS)

目录 1. Q_ENUM宏 与 QMetaEnum类1.1 Q_ENUM宏的作用1.2 使用Q_ENUM注意的问题1.3 在写有关枚举的代码时&#xff0c;我们可能遇到这种情况&#xff1a;需要用到枚举的字符串&#xff0c;该怎么办&#xff1f;1.4 下面通过一段简单的代码来说明Q_ENUM的作用 2. Q_FLAG宏2.1 Q_F…

datax mysql同步数据到clickhouse配置文件样例及说明

datax mysql同步数据到clickhouse配置文件样例及说明 { "job": { "content": [ { "reader": { "parameter": { "password": "…

魔兽世界私人服务器怎么开

开设魔兽世界的私人服务器涉及到一系列复杂的步骤和技术要求。下面是一个大致的指南&#xff0c;以供参考&#xff1a; 1. 硬件需求&#xff1a;首先&#xff0c;你需要一台强大的服务器来承载游戏服务器。服务器的规模和配置将取决于你计划同时容纳多少玩家以及服务器的性能要…

linux下unmount了移动硬盘之后,硬盘灯还是常亮并且硬盘还在一直转动

linux下unmount了移动硬盘之后&#xff0c;硬盘灯还是常亮并且硬盘还在一直转动 ​ 参考:https://www.zhihu.com/question/23362385 希捷2T移动硬盘 在windows下卸载硬盘之后硬盘灯就不亮了&#xff0c;手摸也没有震动感。 在ubuntu下卸载硬盘之后&#xff0c;硬盘灯仍然常…

语音采集技术新革命,4G语音工牌问世,它有哪些应用价值?

随着ChatGpt的火爆和大语言模型的日趋成熟&#xff0c;智能语音赛道迎一轮新的发展。越来越多的企业开始着眼语音数据价值的挖掘&#xff0c;期望能借此来实现销售过程的洞察、赋能&#xff0c;服务过程的管理&#xff0c;客户的精细化运营。基于此&#xff0c;语音前端的采集工…

基于数字全息和相位恢复算法的信息加密与重建实验研究-Matlab代码

▒▒本文目录▒▒ 一、引言二、相位恢复算法三、数字全息显微加密与重建实验验证3.1 基于相位恢复算法全息图加密与解密3.2 菲涅耳变换法重建像3.3 卷积法重建像3.4 角谱法重建像 四、参考文献五、Matlab程序获取 一、引言 近年来&#xff0c;基于光学信息处理技术对图像进行加…

【期末专题】数据库知识点整理

1.要求&#xff1a;修改表的“价格”列&#xff0c;使其数据类型为decimal(6,2) 语句&#xff1a;alter table BookInfo modify price decimal(6,2); 注意点&#xff1a;修改一个表中已有列的数据类型的语句格式&#xff1a; alter table <表名> modify <列名> &…

【STM32】F103 总线结构

一、总线的概念二、STM32的总线结构2.1 STM32的总线矩阵2.2 STM32的存储器映射2.3 STM32的外设寄存器 一、总线的概念 总线是连接多个部件的信息传输线&#xff0c;是各部件共享的传输介质。总线是一种电路&#xff0c;它是CPU、RAM、ROM、输入、输出等设备传递信息的公共通道…

DYnamics 365如何隐藏实体列表页面home page页面上的PowerBI按钮和EXCEL template按钮

如何隐藏以上两个按钮&#xff0c;用ribbon工具根本找不到这2个按钮。 解决方案&#xff1a;添加一个没用的按钮&#xff0c;通过调用enable方法来隐藏。 // JavaScript source code function HiddenButton() { HiddePowerBIButton(); HiddeDocumentTemplateButton(); return…

u盘文件加密怎么设置?丢失重要数据怎么办?

“我同事经常趁我不在工位上的时候&#xff0c;拿我的U盘拷贝了一些文件资料&#xff0c;都没经过我的同意。本来U盘里就存储了很多个人数据&#xff0c;比较隐私&#xff0c;并不想被别人看见&#xff0c;我想给U盘加密&#xff0c;请问u盘文件加密怎么设置&#xff1f;有没有…

国内的“PMP证书”来了,值不值得考?(PMP证书免考增持CSPM-2)

2021年10月&#xff0c;中共中央、国务院发布的《国家标准化发展纲要》明确提出构建多层次从业人员培养培训体系&#xff0c;开展专业人才培养培训和国家质量基础设施综合教育。建立健全人才的职业能力评价和激励机制。由中国标准化协会&#xff08;CAS&#xff09;组织开展的项…

H5学习(二)-- 常用标签

标签内容 一、标题标签二、表单标签啊三、段落标签四、插入图片标签五、换行标签六、列表标签七、超链接标签八、容器标签九、结构性标签十、级块性标签十一、行内语义性标签 HTML中的常用的标签 一、标题标签 <body><!--标题标签--><h1>h1标签</h1>&…

关于vue中element-UI中table的循环展示以及分页方式

vue中table多用到分页&#xff0c;有时会忘记怎么使用分页和循环展示表格 直接上代码&#xff1a; 父组件 <tableDate :dateTable"dateTable" :tableData"tableData"></tableDate>js部分 export default {components: {searchBox,tableDate}…

sounddevice通过ffmpeg读取rstp远程网络设备声音;conda环境里用不了电脑系统环境里的应用ffmpeg

1、sounddevice通过ffmpeg读取rstp远程声音 *** samples维度是samples_per_read指定 *** ##用全路径&#xff0c;调用系统ffmpeg&#xff0c;直接conda里运行不然容易出错 C:/Users/loong/.conda/envs/nlp/python.exe D:\sound\ffmpeg_test.pyffmpeg_test.py: import sound…

Revit添加自己的快捷键和一键剪切

一、Revit中如何自己添加快捷键 我们用Revit做模型时&#xff0c;快捷键可以加快我们的操作速度&#xff0c;提高工作效率。那么我们如何自己添加快捷键呢&#xff0c;下面请看步骤。 1、 点击“视图”&#xff0c;最右“用户界面” 2、 点击“快捷键”&#xff0c;过滤器为“全…

vue2/vue3中,H5自动生成骨架屏代码

generate-skeleton-h5 vue2/vue3自动生成h5骨架屏 安装骨架屏插件 npm i draw-page-structure -D页面引入 import { generateSkeleton } from "generate-skeleton-h5" generateSkeleton().then(res > {// 当前页面的骨架屏代码&#xff0c;含html与cssconsole…