Vue 模板编译原理

Vue 3的内部模块化设计:`@vue/runtime-core`/`@vue/compiler-core`等模块的依赖与职责 模块依赖模块职责组件生命周期管理、响应式系统集成、虚拟 DOM 操作、Provide/Inject、内置组件、自定义渲染器 API提供操作 DOM 的 API、注册全局指令、处理事件监听解析、转换、代码生成,将模板编译成渲染函数处理特定于 DOM 的编译任务、优化 DOM 操作响应式数据、依赖追踪、Effect、Computed、Ref无提供各种工具函数。 阅读详情

原 文地址 ,如果感兴趣或者对美股感兴趣可以加我微信: xiaobei060537, 一起交流 😝。

Vue 模板编译原理

关于vue的内部原理其实有很多个重要的部分,变化侦测,模板编译,virtualDOM,整体运行流程等。

之前写过一篇《深入浅出 - vue变化侦测原理》 讲了关于变化侦测的实现原理。

那今天主要把 模板编译 这部分的实现原理单独拿出来讲一讲。

本文我可能不会在文章中说太多细节部分的处理,我会把 vue 对模板编译这部分的整体原理讲清楚,主要是让读者读完文章后对模板编译的整体实现原理有一个清晰的思路和理解。

关于 Vue 编译原理这块的整体逻辑主要分三个部分,也可以说是分三步,这三个部分是有前后关系的:

  • 第一步是将 模板字符串 转换成 element ASTs(解析器)
  • 第二步是对 AST 进行静态节点标记,主要用来做虚拟DOM的渲染优化(优化器)
  • 第三步是 使用 element ASTs 生成 render 函数代码字符串(代码生成器)

解析器

解析器主要干的事是将 模板字符串 转换成 element ASTs,例如:

<div><p>{{name}}</p>
</div> 

上面这样一个简单的 模板 转换成 element AST 后是这样的:

{tag: "div"type: 1,staticRoot: false,static: false,plain: true,parent: undefined,attrsList: [],attrsMap: {},children: [{tag: "p"type: 1,staticRoot: false,static: false,plain: true,parent: {tag: "div", ...},attrsList: [],attrsMap: {},children: [{type: 2,text: "{{name}}",static: false,expression: "_s(name)"}]}]
} 

我们先用这个简单的例子来说明这个解析器的内部究竟发生了什么。

这段模板字符串会扔到 while 中去循环,然后 一段一段 的截取,把截取到的 每一小段字符串 进行解析,直到最后截没了,也就解析完了。

上面这个简单的模板截取的过程是这样的:

<div><p>{{name}}</p>
</div> 
<p>{{name}}</p>
</div> 
<p>{{name}}</p>
</div> 
{{name}}</p>
</div> 
</p>
</div> 
</div> 
</div> 

那是根据什么截的呢?换句话说截取字符串有什么规则么?

当然有

只要判断模板字符串是不是以 < 开头我们就可以知道我们接下来要截取的这一小段字符串是 标签 还是 文本。

举个🌰:

<div></div> 这样的一段字符串是以 < 开头的,那么我们通过正则把 <div> 这一部分 match 出来,就可以拿到这样的数据:

{tagName: 'div',attrs: [],unarySlash: '',start: 0,end: 5
} 

好奇如何用正则解析出 tagName 和 attrs 等信息的同学可以看下面这个demo代码:

const ncname = '[a-zA-Z_][\\w\\-\\.]*'
const qnameCapture = `((?:${ncname}\\:)?${ncname})`
const startTagOpen = new RegExp(`^<${qnameCapture}`)
const startTagClose = /^\s*(\/?)>/
let html = `<div></div>`
let index = 0
const start = html.match(startTagOpen)

const match = {tagName: start[1],attrs: [],start: 0
}
html = html.substring(start[0].length)
index += start[0].length
let end, attr
while (!(end = html.match(startTagClose)) && (attr = html.match(attribute))) {html = html.substring(attr[0].length)index += attr[0].lengthmatch.attrs.push(attr)
}
if (end) {match.unarySlash = end[1]html = html.substring(end[0].length)index += end[0].lengthmatch.end = index
}
console.log(match) 

Stack

用正则把 开始标签 中包含的数据(attrs, tagName 等)解析出来之后还要做一个很重要的事,就是要维护一个 stack。

那这个 stack 是用来干什么的呢?

这个 stack 是用来记录一个层级关系的,用来记录DOM的深度。

更准确的说,当解析到一个 开始标签 或者 文本,无论是什么, stack 中的最后一项,永远是当前正在被解析的节点的 parentNode 父节点。

通过 stack 解析器就可以把当前解析到的节点 push 到 父节点的 children 中。

也可以把当前正在解析的节点的 parent 属性设置为 父节点。

事实上也确实是这么做的。

但并不是只要解析到一个标签的开始部分就把当前标签 push 到 stack 中。

因为在 HTML 中有一种 自闭和标签,比如 input。

<input /> 这种 自闭和的标签 是不需要 push 到 stack 中的,因为 input 并不存在子节点。

所以当解析到一个标签的开始时,要判断当前被解析的标签是否是自闭和标签,如果不是自闭和标签才 push 到 stack 中。

if (!unary) {currentParent = elementstack.push(element)
} 

现在有了 DOM 的层级关系,也可以解析出DOM的 开始标签,这样每解析一个 开始标签 就生成一个 ASTElement (存储当前标签的attrs,tagName 等信息的object)

并且把当前的 ASTElement push 到 parentNode 的 children 中,同时给当前 ASTElement 的 parent 属性设置为 stack 中的最后一项

currentParent.children.push(element)
element.parent = currentParent 

< 开头的几种情况

但并不是所有以 < 开头的字符串都是 开始标签,以 < 开头的字符串有以下几种情况:

当然我们解析器在解析的过程中遇到的最多的是 开始标签 结束标签 和 注释

截取文本

我们继续上面的例子解析,div 的 开始标签 解析之后剩余的模板字符串是下面的样子:

<p>{{name}}</p>
</div> 

这一次我们在解析发现 模板字符串 不是以 < 开头了。

那么如果模板字符串不是以 < 开头的怎么处理呢??

其实如果字符串不是以 < 开头可能会出现这么几种情况:

我是text <div></div> 

或者:

我是text </p> 

不论是哪种情况都会将标签前面的文本部分解析出来,截取这段文本其实并不难,看下面的例子:

// 可以直接将本 demo 放到浏览器 console 中去执行
const html = '我是text </p>'
let textEnd = html.indexOf('<')
const text = html.substring(0, textEnd)
console.log(text) 

当然 vue 对文本的截取不只是这么简单,vue对文本的截取做了很安全的处理,如果 < 是文本的一部分,那上面 DEMO 中截取的内容就不是我们想要的,例如这样的:

a < b </p> 

如果是这样的文本,上面的 demo 肯定就挂了,截取出的文本就会遗漏一部分,而 vue 对这部分是进行了处理的,看下面的代码:

let textEnd = html.indexOf('<')
let text, rest, next
if (textEnd >= 0) {rest = html.slice(textEnd)// 剩余部分的 HTML 不符合标签的格式那肯定就是文本// 并且还是以 < 开头的文本while (!endTag.test(rest) &&!startTagOpen.test(rest) &&!comment.test(rest) &&!conditionalComment.test(rest)) {// < in plain text, be forgiving and treat it as textnext = rest.indexOf('<', 1)if (next < 0) breaktextEnd += nextrest = html.slice(textEnd)}text = html.substring(0, textEnd)html = html.substring(0, textEnd)
} 

这段代码的逻辑是如果文本截取完之后,剩余的 模板字符串 开头不符合标签的格式规则,那么肯定就是有没截取完的文本

这个时候只需要循环把 textEnd 累加,直到剩余的 模板字符串 符合标签的规则之后在一次性把 text 从 模板字符串 中截取出来就好了。

继续上面的例子,当前剩余的 模板字符串 是这个样子的:

<p>{{name}}</p>
</div> 

截取之后剩余的 模板字符串 是这个样子的:

<p>{{name}}</p>
</div> 

被截取出来的文本是这样的:

"
 " 

截取之后就需要对文本进行解析,不过在解析文本之前需要进行预处理,也就是先简单加工一下文本,vue 是这样做的:

const children = currentParent.children
text = inPre || text.trim()? isTextTag(currentParent) ? text : decodeHTMLCached(text)// only preserve whitespace if its not right after a starting tag: preserveWhitespace && children.length ? ' ' : '' 

这段代码的意思是:

  • 如果文本不为空,判断父标签是不是script或style,1.如果是则什么都不管,2.如果不是需要 decode 一下编码,使用github上的 he 这个类库的 decodeHTML 方法
  • 如果文本为空,判断有没有兄弟节点,也就是 parent.children.length 是不是为 01.如果大于0 返回 ' '2.如果为 0 返回 ''

结果发现这一次的 text 正好命中最后的那个 '',所以这一次就什么都不用做继续下一轮解析就好

继续上面的例子,现在的 模板字符串 变是这个样子:

<p>{{name}}</p>
</div> 

接着解析 <p>,解析流程和上面的 <div> 一样就不说了,直接继续:

{{name}}</p>
</div> 

通过上面写的文本的截取方式这一次截取出来的文本是这个样子的 "{{name}}"

解析文本

其实解析文本节点并不难,只需要将文本节点 push 到 currentParent.children.push(ast) 就行了。

但是带变量的文本和不带变量的纯文本是不同的处理方式。

带变量的文本是指 Hello {{ name }} 这个 name 就是变量。

不带变量的文本是这样的 Hello Berwin 这种没有访问数据的纯文本。

纯文本比较简单,直接将 文本节点的ast push 到 parent 节点的 children 中就行了,例如:

children.push({type: 3,text: '我是纯文本'
}) 

而带变量的文本要多一个解析文本变量的操作:

const expression = parseText(text, delimiters) // 对变量解析 {{name}} => _s(name)
children.push({type: 2,expression,text
}) 

上面例子中 "{{name}}" 是一个带变量的文本,经过 parseText 解析后 expression 是 _s(name),所以最后 push 到 currentParent.children 中的节点是这个样子的:

{expression: "_s(name)",text: "{{name}}",type: 2
} 

结束标签的处理

现在文本解析完之后,剩余的 模板字符串 变成了这个样子:

</p>
</div> 

这一次还是用上面说的办法,html.indexOf('<') === 0,发现是 < 开头的,然后用正则去 match 发现符合 结束标签的格式,把它截取出来。

并且还要做一个处理是用当前标签名在 stack 从后往前找,将找到的 stack 中的位置往后的所有标签全部删除(意思是,已经解析到当前的结束标签,那么它的子集肯定都是解析过的,试想一下当前标签都关闭了,它的子集肯定也都关闭了,所以需要把当前标签位置往后从 stack中都清掉)

结束标签不需要解析,只需要将 stack 中的当前标签删掉就好。

虽然不用解析,但 vue 还是做了一个优化处理,children 中的最后一项如果是空格 " ",则删除最后这一项:

if (lastNode && lastNode.type === 3 && lastNode.text === ' ' && !inPre) {element.children.pop()
} 

因为最后这一项空格是没有用的,举个例子:

<ul><li></li>
</ul> 

上面例子中解析成 element ASTs之后 ul 的结束标签 </ul> 和 li 的结束标签 </li> 之间有一个空格,这个空格也属于文本节点在 ul 的 children 中,这个空格是没有用的,把这个空格删掉每次渲染dom都会少渲染一个文本节点,可以节省一定的性能开销。

现在剩余的 模板字符串 已经不多了,是下面的样子:

</div> 

然后解析文本,就是一个其实就是一个空格的文本节点。

然后再一次解析结束标签 </div>

</div> 

解析完毕退出 while 循环。

解析完之后拿到的 element ASTs 就是文章开头写的那样。

总结一下

其实这样一个模板解析器的原理不是特别难,主要就是两部分内容,一部分是 截取 字符串,一部分是对截取之后的字符串做 解析

每截取一段标签的开头就 push 到 stack中,解析到标签的结束就 pop 出来,当所有的字符串都截没了也就解析完了。

上文中的例子是比较简单的,不涉及一些循环啊,什么的,注释的处理这些也都没有涉及到,但其实这篇文章中想表达的内容也不是来扣细节的,如果扣细节可能要写一本小书才够,一篇文章的字数可能只够把一个大体的逻辑给大家讲清楚,希望同学们见谅,如果对细节感兴趣可以在下面评论,咱们一起讨论共同学习进步~

优化器

优化器的目标是找出那些静态节点并打上标记,而静态节点指的是 DOM 不需要发生变化的节点,例如:

<p>我是静态节点,我不需要发生变化</p> 

标记静态节点有两个好处:

1.每次重新渲染的时候不需要为静态节点创建新节点
2.在 Virtual DOM 中 patching 的过程可以被跳过

优化器的实现原理主要分两步:

  • 第一步:用递归的方式将所有节点添加 static 属性,标识是不是静态节点
  • 第二步:标记所有静态根节点

什么是静态根节点? 答:子节点全是静态节点的节点就是静态根节点,例如:

<ul><li>我是静态节点,我不需要发生变化</li><li>我是静态节点2,我不需要发生变化</li><li>我是静态节点3,我不需要发生变化</li>
</ul> 

ul 就是静态根节点。

如何将所有节点标记 static 属性?

vue 判断一个节点是不是静态节点的做法其实并不难:

1.先根据自身是不是静态节点做一个标记 node.static = isStatic(node)
2.然后在循环 children,如果 children 中出现了哪怕一个节点不是静态节点,在将当前节点的标记修改成 false: node.static = false。

如何判断一个节点是不是静态节点?

也就是说 isStatic 这个函数是如何判断静态节点的?

function isStatic (node: ASTNode): boolean {if (node.type === 2) { // expressionreturn false}if (node.type === 3) { // textreturn true}return !!(node.pre || (!node.hasBindings && // no dynamic bindings!node.if && !node.for && // not v-if or v-for or v-else!isBuiltInTag(node.tag) && // not a built-inisPlatformReservedTag(node.tag) && // not a component!isDirectChildOfTemplateFor(node) &&Object.keys(node).every(isStaticKey)))
} 

先解释一下,在上文讲的解析器中将 模板字符串 解析成 AST 的时候,会根据不同的文本类型设置一个 type:

type说明
——
1元素节点
2带变量的动态文本节点
3不带变量的纯文本节点

所以上面 isStatic 中的逻辑很明显,如果 type === 2 那肯定不是 静态节点 返回 false,如果 type === 3 那就是静态节点,返回 true。

那如果 type === 1,就有点复杂了,元素节点判断是不是静态节点的条件很多,咱们先一个个看。

首先如果 node.pre 为 true 直接认为当前节点是静态节点,关于 node.pre 是什么 请狠狠的点击我。

其次 node.hasBindings 不能为 true。

node.hasBindings 属性是在解析器转换 AST 时设置的,如果当前节点的 attrs 中,有 v-、@、:开头的 attr,就会把 node.hasBindings 设置为 true。

const dirRE = /^v-|^@|^:/
if (dirRE.test(attr)) {// mark element as dynamicel.hasBindings = true
} 

并且元素节点不能有 if 和 for属性。

node.if 和 node.for 也是在解析器转换 AST 时设置的。

在解析的时候发现节点使用了 v-if,就会在解析的时候给当前节点设置一个 if 属性。

就是说元素节点不能使用 v-if v-for v-else 等指令。

并且元素节点不能是 slot 和 component。

并且元素节点不能是组件。

例如:

<List></List> 

不能是上面这样的自定义组件

并且元素节点的父级节点不能是带 v-for 的 template,查看详情 请狠狠的点击我。

并且元素节点上不能出现额外的属性。

额外的属性指的是不能出现 type``tag attrsList attrsMap plain parent children attrs staticClass staticStyle 这几个属性之外的其他属性,如果出现其他属性则认为当前节点不是静态节点。

只有符合上面所有条件的节点才会被认为是静态节点。

如何标记所有节点?

上面讲如何判断单个节点是否是静态节点,AST 是一棵树,我们如何把所有的节点都打上标记(static)呢?

还有一个问题是,判断 元素节点是不是静态节点不能光看它自身是不是静态节点,如果它的子节点不是静态节点,那就算它自身符合上面讲的静态节点的条件,它也不是静态节点。

所以在 vue 中有这样一行代码:

for (let i = 0, l = node.children.length; i < l; i++) {const child = node.children[i]markStatic(child)if (!child.static) {node.static = false}
} 

markStatic 可以给节点标记,规则上面刚讲过,vue.js 通过循环 children 打标记,然后每个不同的子节点又会走相同的逻辑去循环它的 children 这样递归下来所有的节点都会被打上标记。

然后在循环中判断,如果某个子节点不是 静态节点,那么讲当前节点的标记改为 false。

这样一圈下来之后 AST 上的所有节点都被准确的打上了标记。

如何标记静态根节点?

标记静态根节点其实也是递归的过程。

vue 中的实现大概是这样的:

function markStaticRoots (node: ASTNode, isInFor: boolean) {if (node.type === 1) {// For a node to qualify as a static root, it should have children that// are not just static text. Otherwise the cost of hoisting out will// outweigh the benefits and it's better off to just always render it fresh.if (node.static && node.children.length && !(node.children.length === 1 &&node.children[0].type === 3)) {node.staticRoot = truereturn} else {node.staticRoot = false}if (node.children) {for (let i = 0, l = node.children.length; i < l; i++) {markStaticRoots(node.children[i], isInFor || !!node.for)}}}
} 

这段代码其实就一个意思:

当前节点是静态节点,并且有子节点,并且子节点不是单个静态文本节点这种情况会将当前节点标记为根静态节点。

额,,可能有点绕口,重新解释下。

上面我们标记 静态节点 的时候有一段逻辑是只有所有 子节点 都是 静态节点,当前节点才是真正的 静态节点。

所以这里我们如果发现一个节点是 静态节点,那就能证明它的所有 子节点 也都是静态节点,而我们要标记的是 静态根节点,所以如果一个静态节点只包含了一个文本节点那就不会被标记为 静态根节点。

其实这么做也是为了性能考虑,vue 在注释中也说了,如果把一个只包含静态文本的节点标记为根节点,那么它的成本会超过收益~

总结一下

整体逻辑其实就是递归 AST 这颗树,然后将 静态节点 和 静态根节点 找到并打上标记。

代码生成器

代码生成器的作用是使用 element ASTs 生成 render 函数代码字符串。

使用本文开头举的例子中的模板生成后的 AST 来生成 render 后是这样的:

{render: `with(this){return _c('div',[_c('p',[_v(_s(name))])])}`
} 

格式化后是这样的:

with(this){return _c('div',[_c('p',[_v(_s(name))])])
} 

生成后的代码字符串中看到了有几个函数调用 _c,_v,_s。

_c 对应的是 createElement,它的作用是创建一个元素。

1.第一个参数是一个HTML标签名
2.第二个参数是元素上使用的属性所对应的数据对象,可选项
3.第三个参数是 children

例如:

一个简单的模板:

<p title="Berwin" @click="c">1</p> 

生成后的代码字符串是:

`with(this){return _c('p',{attrs:{"title":"Berwin"},on:{"click":c}},[_v("1")])}` 

格式化后:

with(this){return _c('p',{attrs:{"title":"Berwin"},on:{"click":c}},[_v("1")])
} 

关于 createElement 想了解更多请狠狠的点击我。

_v 的意思是创建一个文本节点。

_s 是返回参数中的字符串。

代码生成器的总体逻辑其实就是使用 element ASTs 去递归,然后拼出这样的 _c('div',[_c('p',[_v(_s(name))])]) 字符串。

那如何拼这个字符串呢??

请看下面的代码:

function genElement (el: ASTElement, state: CodegenState) {const data = el.plain ? undefined : genData(el, state)const children = el.inlineTemplate ? null : genChildren(el, state, true)
	let code = `_c('${el.tag}'${data ? `,${data}` : '' // data}${children ? `,${children}` : '' // children})`return code
} 

因为 _c 的参数需要 tagName、data 和 children。

所以上面这段代码的主要逻辑就是用 genData 和 genChildren 获取 data 和 children,然后拼到 _c 中去,拼完后把拼好的 "_c(tagName, data, children)" 返回。

所以我们现在比较关心的两个问题:

1.data 如何生成的(genData 的实现逻辑)?
2.children 如何生成的(genChildren 的实现逻辑)?

我们先看 genData 是怎样的实现逻辑:

function genData (el: ASTElement, state: CodegenState): string {let data = '{'// keyif (el.key) {data += `key:${el.key},`}// refif (el.ref) {data += `ref:${el.ref},`}if (el.refInFor) {data += `refInFor:true,`}// preif (el.pre) {data += `pre:true,`}// ... 类似的还有很多种情况data = data.replace(/,$/, '') + '}'return data
} 

可以看到,就是根据 AST 上当前节点上都有什么属性,然后针对不同的属性做一些不同的处理,最后拼出一个字符串~

然后我们在看看 genChildren 是怎样的实现的:

function genChildren (el: ASTElement,state: CodegenState
): string | void {const children = el.childrenif (children.length) {return `[${children.map(c => genNode(c, state)).join(',')}]`}
}

function genNode (node: ASTNode, state: CodegenState): string {if (node.type === 1) {return genElement(node, state)} if (node.type === 3 && node.isComment) {return genComment(node)} else {return genText(node)}
} 

从上面代码中可以看出,生成 children 的过程其实就是循环 AST 中当前节点的 children,然后把每一项在重新按不同的节点类型去执行 genElement genComment genText。如果 genElement 中又有 children 在循环生成,如此反复递归,最后一圈跑完之后能拿到一个完整的 render 函数代码字符串,就是类似下面这个样子。

"_c('div',[_c('p',[_v(_s(name))])])" 

最后把生成的 code 装到 with 里。

export function generate (ast: ASTElement | void,options: CompilerOptions
): CodegenResult {const state = new CodegenState(options)// 如果ast为空,则创建一个空divconst code = ast ? genElement(ast, state) : '_c("div")'return {render: `with(this){return ${code}}`}
} 

关于代码生成器的部分到这里就说完了,其实源码中远不止这么简单,很多细节我都没有去说,我只说了一个大体的流程,对具体细节感兴趣的同学可以自己去看源码了解详情。

总结

本篇文章我们说了 vue 对模板编译的整体流程分为三个部分:解析器(parser),优化器(optimizer)和代码生成器(code generator)。

解析器(parser)的作用是将 模板字符串 转换成 element ASTs。

优化器(optimizer)的作用是找出那些静态节点和静态根节点并打上标记。

代码生成器(code generator)的作用是使用 element ASTs 生成 render函数代码(generate render function code from element ASTs)。

用一张图来表示:

解析器(parser)的原理是一小段一小段的去截取字符串,然后维护一个 stack 用来保存DOM深度,每截取到一段标签的开始就 push 到 stack 中,当所有字符串都截取完之后也就解析出了一个完整的 AST。

优化器(optimizer)的原理是用递归的方式将所有节点打标记,表示是否是一个 静态节点,然后再次递归一遍把 静态根节点 也标记出来。

代码生成器(code generator)的原理也是通过递归去拼一个函数执行代码的字符串,递归的过程根据不同的节点类型调用不同的生成方法,如果发现是一颗元素节点就拼一个 _c(tagName, data, children) 的函数调用字符串,然后 data 和 children 也是使用 AST 中的属性去拼字符串。

如果 children 中还有 children 则递归去拼。

最后拼出一个完整的 render 函数代码。

自动驾驶仿真:python和carsim联合仿真案例 carsim内部有许多相关联合仿真的demo,simulink、labview等等都有涉及,这里简单介绍下python和carsim联合仿真的方法,虽然carsim官方有个Steer_Control.py相关的案例,但是感觉比较冗余,这里抽出重点部分和大家交流探讨下。提示:以下是本篇文章正文内容,下面案例可供参考1、这里关于solvers的细节其实都没说,因为里面确实也没什么内容好讲的,本质就是调用carsim.dll,如果你需要更多的函数解析其实可以看vs_api.h,路径在,具体内容如下图所示。 阅读详情

相关推荐

FPGA波束形成:从算法到硬件的艺术之旅

本文深入探讨了FPGA在波束形成技术中的应用,从算法原理到Verilog硬件实现的全过程。文章详细分析了FPGA并行处理、低延迟和可重构特性如何优化波束形成系统性能,并提供了Verilog关键模块代码示例和资源优化策略,为工程师实现高效FPGA波束形成解决方案提供实用指导。

tea88的博客 1044

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

compiler-core包是vue3源码代码编译的核心代码包,且与平台无关,这个包**主要功能是将代码解析成ast、然后转换成codegenNode对象、再编译生成可执行代码(render渲染函数)**

qq_2511276124的博客 1866

海思ISP实战:YUV Sharpen参数调试全解析

本文深入解析了在海思ISP平台上进行YUV Sharpen参数调试的实战经验。文章以海思3516EV300芯片为例,详细拆解了EdgeFiltStr、TextureFreq等核心参数的作用与协同策略,并重点阐述了如何通过ISO联动调试,在图像清晰度与噪声抑制之间取得最佳平衡,为图像调试工程师提供了宝贵的避坑指南和实战参考。

grape的博客 209

Vue3 模板编译原理 (Vue 的编译模块整体逻辑)

Vue 的编译模块包含 4 个目录: compiler-core compiler-dom // 浏览器 compiler-sfc // 单文件组件 compiler-ssr // 服务端渲染 复制代码 其中 compiler-core 模块是 Vue 编译的核心模块,并且是平台无关的。而剩下的三个都是在 compiler-core 的基础上针对不同的平台作了适配处理。 Vue 的编译分为三个阶段,分别是:parse、transform、codegen。 其中 parse 阶段将模板字符串转化为语法抽象..

More_Action的博客 1427

避坑指南:在Linux服务器上用Conda安装antiSMASH 5.2.0(解决Jinja2报错和Matplotlib权限问题)

本文详细介绍了在Linux服务器上使用Conda安装antiSMASH 5.2.0的避坑指南,重点解决了Jinja2报错和Matplotlib权限问题。通过创建专用conda环境、安装指定版本依赖包和配置缓存目录,帮助用户顺利完成antiSMASH的本地部署,适用于批量处理基因组分析任务。

weixin_26911099的博客 316

深度学习环境搭建以及DeepLabCut安装过程

文章目录 一、查询电脑硬件 安装GeForce驱动程序 二、环境搭建与软件安装 1.安装CUDA运算平台软件 2.安装cuDNN支持包 3.配置环境变量 三、 DeepLabCut环境配置 1.安装Anaconda 2.创建容器环境 3.激活容器环境 4.安装TensorFlow 5.验证TensorFlow是否安装成功 6...

m0_51136557的博客 3157

vue3.0编译器compiler-core源码解析

Vue3的源代码正在国庆假期就这么突然放出来了,假期还没结束,陆陆续续看到努力的码农就在各种分析了。 目前还是 pre Alpha,乐观估计还有 Alpha,Beta版本,最后才是正式版。 话不多说,看 Pre-Alpha。 瞧 compiler-core 热门的 reactivity 被大佬翻来覆去再研究了,我就和大伙一起来解读一下 ”冷门“ 的 compiler 吧!???????????????? 如果你对 AS...

2306

DLMS/COSEM协议的理解体会

DLMS是通信协议,COSEM是电表建模规范。HDLC帧基本格式为0x7e......

一个bug是bug,多个bug能work 8312

Vue(九):Vue Compile模版类DOM操作

Compile源码 const handles = { // v-click vclick: { implement(vm, node, name, value) { let fun = vm.$methods[value]; node.addEventListener('click', function(){ fun.call(vm); return fun;

You瞧谁不起 - 道不尽世间的沧桑,诉不完人生的悲凉 651

JMeter 中实现 100 个用户在 3 秒内并发登录

通过以上配置,你可以在 JMeter 中实现 100 个用户在 3 秒内并发登录

南开大学 | 10 年 + 软件研发与测试实战 | 现任企业质控负责人 | 专注端到端交付质量 1680

YOLO 线性小目标涨点|3890 张输电导线散股 VOC+YOLO 单类别数据集实战,无人机电力缺陷巡检全流程落地

架空输电导线是电网能量传输核心载体,长期受风力摆动、雷击、腐蚀、外力撞击作用,外层铝股极易出现断裂散股故障。导线散股初期仅少量铝丝开裂,肉眼与无人机远距离航拍下属于细长微弱线性小目标;若未及时发现,散股会持续扩张,引发导线过热、断线、大面积停电重大电力安全事故。传统人工巡检存在多重行业痛点:山区、跨江河线路人员无法攀爬登杆,高空作业人身坠落风险极高;无人机单次航拍产出数千张高清图片,人工逐帧筛查微小散股耗时 3-5 天,阴天、逆光、植被遮挡场景漏检率超 45;

lin_xiu的博客 52

Vue实战指南:Vue的模板编译原理

Vue.js是一个流行的JavaScript框架,它通过简洁的API和高效的响应式系统来简化前端开发。Vue的核心特性之一是它的模板系统,它允许开发者以一种接近HTML的方式声明式地描述DOM结构。然而,Vue不仅仅是一个模板引擎,它的编译器将模板转换为可执行的渲染函数,从而提高了应用的性能。通过以上示例和讨论,我们不仅了解了Vue模板编译的基本原理,还掌握了如何利用这些知识来改进我们的Vue应用。希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。

一个做过前端开发的产品经理,经历过睿智产品的折磨导致脱发之后,励志要翻身"农奴"把歌唱,一边打入敌人内部一边持续提升自己,为我们广大开发同胞谋福祉,坚决抵制睿智产品折磨我们码农兄弟! 1290

`tokenizer.decode` 出现乱码或异常输出,怎么处理

摘要: 在使用Hugging Face Transformers库时,tokenizer.decode出现乱码或异常输出的解决方案包括:1)确保模型与分词器严格匹配,加载时使用trust_remote_code=True;2)谨慎处理特殊token,先保留所有token再针对性清理;3)校验模型输出token的维度和数值是否合法;4)通过convert_tokens_to_string替代解码;5)检查生成配置如max_new_tokens等参数。核心思路是确保模型与分词器逻辑对齐、输出token合法,必要

ZhangJiqun&191cm&85kg 585

Vue模板编译原理

在 Vue.js 中,模板是用来描述组件的结构和内容的一种声明式的、基于 HTML 的语法。模板可以包含 Vue.js 的指令、插值表达式、事件处理等内容,它是构建用户界面的重要组成部分。

z142536x的博客 1032

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

深入探究 Vue 模板的编译原理

Vue.js 是一款构建用户界面的渐进式框架,它的核心库只关注视图层。Vue 模板编译原理是 Vue.js 能够将模板编译成可执行的 JavaScript 代码的关键。本文将详细介绍 Vue 模板编译的原理,帮助读者更好地理解 Vue.js 的实现机制。Vue 模板编译原理是 Vue.js 实现数据绑定和交互功能的关键。本文详细介绍了 Vue 模板编译的过程,包括解析模板、优化 AST、生成 JavaScript 代码、数据绑定和事件处理。希望读者能从中受益,更好地理解 Vue.js 的实现机制。

你若盛开,清风自来 1703

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

Vue3 模板编译原理

vue优缺点

weixin_44356698的博客 142

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

vue的模板编译原理

vue提供了模板语法,允许我们声明式地描述状态和DOM之间的绑定关系,比如。模板编译指的是模板编译成虚拟DOM的过程,vue会将模板编译成虚拟DOM渲染函数。模板编译的主要目的是生成渲染函数,渲染函数的作用是每次执行时,会根据最新状态生成新的vnode 解析器:将模板解析为AST(Abstract Syntax Tree 抽象语法树) 优化器:遍历AST标记静态节点,因为静态节点不可变,不需要为打上标签的静态节点创建新的虚拟节点,直接克隆已有的虚拟节点。 代码生成器:使用AST生成渲染函数。将AST转换成渲

好好学习天天向上 2679

Vue 模板编译原理解析

首先我们看一下什么是编译?所谓编译(Compile),指的是将语言 A 翻译成语言 B,语言 A 就被称之为源码(source code),语言 B 就被称之为目标代码(target code),这个事情谁来做?编译器来做。编译器你也不用想得那么神秘,就是一段程序而已。上面的步骤,如果从大的方面去分,那么可以分为和。编译后的结果是什么?编译后的结果就是渲染函数注意这里,整个编译过程并非一蹴而就,而是经历了一个又一个的步骤,一点一点转换而来的。

共同学习,心怀感激。 1845
下一篇: vue 项目优雅的对 url 参数加密
李小珣1926
博客等级 码龄4年 0粉丝 9原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值