皮皮网

皮皮网

【网络源码钓鱼】【volcano源码解读】【学java 源码】vue动态绑定class源码_vue动态绑定class写法

时间:2024-12-25 14:01:16 分类:娱乐

1.Vue3 中的动定ce动定 v-bind 指令:你不知道的那些工作原理
2.vue双向绑定原理实现
3.v2双项数据绑定?
4.vue早期源码学习系列之四:如何实现动态数据绑定
5.vue3中的编译器原理和优化策略

vue动态绑定class源码_vue动态绑定class写法

Vue3 中的 v-bind 指令:你不知道的那些工作原理

       Vue3中的v-bind指令,以其灵活的态绑态绑使用方式让人熟知。本文将深入探讨其工作原理,源码以debug源码解析其多变的写法绑定形式是如何实现的。

       首先,动定ce动定我们通过一个简单的态绑态绑网络源码钓鱼示例来展示v-bind指令的使用:将变量title绑定到div的title属性上。编译后的源码代码显示,无论使用何种写法(如:v-bind:title,写法 v-bind:title=, 或者vue3.4新引入的写法),最终的动定ce动定props对象都是{ title: $setup.title },确保了属性绑定的态绑态绑正确性。

       继续深入,源码v-bind指令的写法处理过程在transformElement函数中进行,这是动定ce动定在编译阶段处理内置指令如v-for和v-model的函数之一。通过debug工具,态绑态绑我们发现v-bind指令会在transformElement的源码执行中生成对应的props对象。

       在buildProps函数中,v-bind指令的属性被解析和合并到props对象中,随后在transformBind函数里,根据指令的写法和值(或省略的值)进行处理,生成包含key和value的属性对象。例如,当省略值时,会将变量名转换并补全,形成最终的volcano源码解读键值对。

       总结来说,v-bind指令在Vue3的编译流程中,通过transformElement和其内部函数,无论写法如何变化,都能确保属性绑定的准确性和灵活性,为开发者提供了强大的动态绑定能力。

vue双向绑定原理实现

       数据双向绑定的核心在于实现视图与数据之间的实时同步更新,达到一种动态响应的效果。Vue通过实现MVVM模式,实现这一目标。

       Vue的双向绑定,其原理主要依赖于Object.defineProperty()方法,重新定义对象属性的获取和设置操作。以此,当数据发生改变时,视图能实时响应并更新,反之亦然。

       在Vue中,数据双向绑定的实现需借助三个关键组件:Observer、Watcher和Compile。

       Observer组件作为数据监听器,通过Object.defineProperty()方法,对所有属性进行劫持监听。学java 源码当属性值发生变化时,会通知订阅者Watcher进行更新。这里引入了Dep消息订阅器,用于收集所有Watcher,并进行统一管理。此组件将数据变化与视图更新之间的联系紧密绑定。

       Watcher组件在接收到属性变化通知后,执行相应的更新函数,从而更新视图。这样的机制确保了数据与视图之间的实时同步。

       Compile组件负责解析HTML模板中的指令,初始化数据和订阅者Watcher,并将模型数据与视图组件绑定。通过指令解析,Vue能够实现数据的实时绑定和视图的动态更新。

       数据双向绑定的流程如下:首先,使用Observer对数据进行劫持监听,监测数据变化。然后,通过Watcher处理数据变化通知,触发视图更新。最后,Compile解析模板中的银河ngrid源码指令,初始化数据和订阅者,实现数据与视图之间的动态绑定。

       Vue源码中,v-model的实现涉及Observer、Watcher和Compile三个组件协作,共同完成数据与视图之间的双向绑定,实现高效的数据驱动视图更新。

v2双项数据绑定?

       vue数据双向绑定原理

       1、vue数据双向绑定是通过数据劫持结合发布者-订阅者模式的方式来实现的,其中比较关键的是数据劫持,下面咱们看一个例子。

       2、视图交互变化(input)-数据model变更的双向绑定效果。v-model是什么?怎么使用?vue中标签怎么绑定事件?可以实现双向绑定,指令(v-class、v-for、v-if、v-show、v-on)。vue的model层的data属性。

       3、响应式原理:每个组件实例都对应一个watcher实例,它会在组件渲染的龙口指标源码过程中把“接触”过的数据property记录为依赖。之后当依赖项的setter触发时,会通知watcher,从而使它关联的组件重新渲染。

       4、于是vue中就是每当有这样的可能用到双向绑定的指令,就在一个Dep中增加一个订阅者,其订阅者只是更新自己的指令对应的数据,也就是v-model=name和{ { name}}有两个对应的订阅者,各自管理自己的地方。

vue的v-once指令怎么用

       v-cloak和CSS规则如[v-cloak]{ display:none}一起用时,这个指令可以隐藏未编译的Mustache标签直到组件实例准备完毕,主要用于解决闪动问题,现在Vue3一般不会出现这个问题了。不会显示,直到编译结束。

       v-once:该指令后面不需要跟任何表达式(v-for后面接表达式),该指令表示元素和组件只渲染一次,不会随着数据的改变而改变。v-html:v-html会将数据当html标签解析后输出。

       vue的常用指令v-bind:用于绑定属性,通过v-bind:绑定过的属性,可以直接在属性值写表达式。可以简写为:v-on:用于绑定事件,通过v-on:绑定过的事件,可以指定vue实例定义的方法。

       使用v-on指令监听DOM事件,并在触发时运行一些JavaScript代码。

       Vue.js为v-on提供了事件修饰符来处理DOM事件细节,如:event.preventDefault()或event.stopPropagation()。Vue.js通过由点(.)表示的指令后缀来调用修饰符。

       本小节我们介绍Vue中如何进行事件处理。在章节2中我们已经介绍了指令v-on,本章节我们将详细介绍在v-on的一些用法。包括如何传递参数、如何使用事件修饰符等。其中,事件修饰符是本章节的难点。

实现双向数据绑定

       在上篇文章当中,我们实现了单向数据绑定,那么接下来,咱们一步一步来实现双向数据绑定。

       一般来说要实现这种双向数据绑定,在前端我目前了解的有三种形式:目前angular,regular的实现都是基于脏检查。当发生某些特定的事情的时候,框架会调用相关的digest方法。内部逻辑就是遍历所有的watcher,对监控的属性做对比。

       Vue主要通过以下4个步骤来实现数据双向绑定的:实现一个监听器Observer:对数据对象进行遍历,包括子属性对象的属性,利用Object.defineProperty()对属性都加上setter和getter。

       vue实现数据双向绑定主要是:采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应监听回调。

       单个v-model数据绑定默认情况下,组件上的v-model便用modelvalue作为prop和update:modelvalu作为事件。

       vuex源码分析(二)——双向数据绑定

       通过Compile来解析编译模板指令(vue中是用来解析{ { }}),最终利用watcher搭起observer和Compile之间的通信桥梁,达到数据变化—视图更新;视图交互变化(input)—数据model变更双向绑定效果。

       vue双向数据绑定是通过数据劫持结合发布订阅模式实现的,数据和视图同步,即数据发生变化,视图跟着变化,视图变化,数据也随之发生改变核心:Object.defineProperty()参数:obj:要定义属性的对象。

       于是vue中就是每当有这样的可能用到双向绑定的指令,就在一个Dep中增加一个订阅者,其订阅者只是更新自己的指令对应的数据,也就是v-model=name和{ { name}}有两个对应的订阅者,各自管理自己的地方。

vue早期源码学习系列之四:如何实现动态数据绑定

       本系列更多文章,请查阅我的博客:GitHub - youngwind/blog: 梁少峰的个人博客。或扫描下方二维码关注公众号“前端控”,欢迎交流讨论!

       在前一篇中,我们学习了如何监听数据变化以及使用观察者模式和事件传递响应变化事件。今天,我们将探讨如何基于watch库实现动态数据绑定。

       我们可以将问题具体化为一个例子:如何实现当user.name或user.age发生变化时,HTML上的DOM元素也能相应地改变?

       最初,我采取了以下方法:在数据顶层注册一个事件,当任意属性改变时,重新遍历DOM模板,将{ { user.name}}等转换为实际值,在内存中拼接成fragment,最后将新fragment替换掉原有的DOM结构。这种方法虽然简单,但存在不少问题。

       基于上述问题,我们需要改进这种做法。为此,我们引入了Directive(指令)的概念,其构造函数和原型方法如下所示。关键实现思路是:在遍历DOM模板的过程中,当遍历到文本节点:"{ { name}}"时,先将其中的表达式"name"匹配出来,然后新建一个空的textNode,插入到这个文本节点的前面,最后移除这个文本节点。这样,就实现了用一个程序生成的textNode代替原来的textNode,从而实现每个textNode都跟它的表达式一一对应起来。

       然而,这种方法仍然存在问题。为了解决这些问题,我们引入了Binding和Watcher这两个“类”。Binding用于解决键值索引,Watcher用于解决$watch。Binding、Watcher和Directive三者之间的关系如下:(此处插入)

       从图中可以看出,有一个_rootBind对象,其属性是按照DOM模板中用到的数据层层深入排列下去的。在每个属性上都有一个_subs数组,这个数组表示订阅的意思,里面存放的是一系列Watcher。Watcher既可以装载Directive,也可以装载$watch。这就是vue实现动态数据绑定的三大核心概念。

       学习Vue.js,我checkout的是vue的这个版本。相比于之前的学习,代码量大幅增加,从七八百行增加到差不多五千行。特别是Binding、Watcher和Directive这几个核心概念,一开始让人难以理解。经过多日的思考和不断调试,我才逐渐明白。

       此外,通过Binding、Watcher、Directive构建的动态数据绑定体系还存在一个重大缺陷,我们将在下一篇文章中专门阐述。

vue3中的编译器原理和优化策略

       å­¦ä¹ ç›®æ ‡

       ç¼–译器原理

       vue3编译过程剖析

       vue3编译优化策略

       åœ¨åˆå§‹åŒ–之前可能有编译的过程,最终的产物是个渲染函数,我们知道渲染函数返回的值是一个虚拟DOM(vnode),那么这个虚拟DOM在我们后续的更新过程中到底有什么作用呢?我们今天就来探讨一下。

编译器原理1.概念

       å¹¿ä¹‰ä¸Šçš„编译原理:编译器是将源代码转化成机器码的软件;所以编译的过程则是将源代码转化成机器码的过程,也就是cpu可执行的二进制代码。例如使用高级语言java编写的程序需要编译成我们看不懂但计算机能看懂的的字节码。

       å¦‚果了解过编译器的工作流程的同学应该知道,一个完整的编译器的工作流程会是这样:

       é¦–先,parse解析原始代码字符串,生成抽象语法树AST。

       å…¶æ¬¡ï¼Œtransform转化抽象语法树,让它变成更贴近目标「DSL」的结构。

       æœ€åŽï¼Œcodegen根据转化后的抽象语法树生成目标「DSL」的可执行代码。

2.vue中的编译

       åœ¨vue里也有编译的过程,我们经常写的那个HTML模版,在真正工作的时候,并不是那个HTML模版,它实际上是一个渲染函数,在这个过程中就发生了转换,也就是编译,也就是那个字符串的模版最终会变成一个JS函数,叫render函数。所以在这个过程中我们就需要引入编译器的概念。在计算机中当一种东西从一种形态到另一种形态进行转换的时候,就需要编译。编译器:用来将模板字符串编译成为JavaScript渲染函数的代码

       é‚£ä¹ˆvue中的编译发生在什么时候呢?

       è¿™ä¸ªæ—¶å€™æˆ‘们就需要进一步了解vue包的不同版本的不同功能了。vue有携带编译器和不携带编译的包(对不同构建版本的解释)。

3.运行时编译

       åœ¨ä½¿ç”¨æºå¸¦ç¼–译器(compiler)的vue包的时候,vue编译的时刻是发生在挂载($mount)的时候。

4.运行时不编译

       å¦‚果使用未携带编译器的vue包的时候,vue在运行时是不会进行编译的。那么它的编译又发生在什么时候呢?使用未携带编译器的vue包的时候,需要进行预编译,也就是基于构建工具使用,就是我们平时使用的vue-cli进行构建的项目,就是使用webpack调用vue-loader进行预编译,将所有vue文件,就是SFC,将里面的template模版部分转换成render函数。这样做的好处就是vue的包体积变小了,执行的时候速度更快了,因为不需要进行编译了。

vue编译器原理

       ç®€å•æ¥è¯´å°±æ˜¯ï¼šå…ˆå°†template模版转换成ast抽象语法树,ast再转换成渲染函数render。

       é‚£ä¹ˆä»€ä¹ˆæ˜¯æ˜¯ast抽象语法树呢?

1.ast抽象语法树

       åœ¨template模版和render函数之间有一个中间产物叫做ast抽象语法树。它就是个js对象,它能够描述当前模版的结构信息,跟vnode很类似。注意,ast只是程序运行过程中编译产生的,它跟我们最终程序的运行是没有任何关系的。也就是当这个渲染函数生成之后,ast的生命周期就结束了,不再需要了,而那个虚拟DOM则伴随整个程序的生命周期。这个就是ast和虚拟DOM的本质区别。

2.为什么需要ast呢

       åœ¨ast转换成render函数的过程中,需要进行特别的操作。第一次,将template转成的ast是个非常粗糙的js对象,是一次非常粗糙的转换,类似正则表达式的匹配,然后我们的template模版中还有很多表达式,指令,事件需要重新解析,经过这些具体的深加工的解析(transform)之后会得到一个终极ast,然后这个对这个终极ast进行generate,生成render函数

template=>ast=>transform=>ast=>render3.mini版vue编译器

       ä¸‹é¢æˆ‘们来看一个mini版的vue编译器,具体代码已省略,具体代码我已经放在Github上了:mini-vue-compiler

functiontokenizer(input){ ...}functionparse(template){ consttokens=tokenizer(template)...}functiontransform(ast){ ...}functiontraverse(ast,context){ ...}functiongenerate(ast){ ...}functioncompile(template){ //1.解析constast=parse(template)console.log(JSON.stringify(ast,null,2))//2.转换transform(ast)//3.生成constcode=generate(ast)console.log(code)//returnfunctionrender(ctx){ //returnh("h3",{ },//ctx.title//)}returnnewFunction(code)()}lettmpl=`<h3>{ { title}}</h3>`compile(tmpl)

       å¤§æ¦‚有以上操作,其中parse函数就是发生在把template转换成ast的这过程,具体是通过一些正则表达式的匹配template中的字符串。比如将

xxx

       è½¬æˆast对象,那么就是通过正则表达式匹配如果是

那么就设置一个开始标记,再往后面匹配到xxx内容,然后就设置一个子元素,最后匹配到

       é‚£ä¹ˆå°±è®¾ç½®ä¸€ä¸ªç»“束标记,以此类推。parse解析之后得到的是一个粗糙的ast对象。经过parse解析得到一个粗糙的ast对象之后,就用transform进行深加工,最后要经过generate生成代码。

Vue3编译过程剖析

       æŒ‚载的时候先把template编译成render函数,在创建实例之后,直接调用组件实例的render函数创建这个组件的真实DOM,然后继续向下做递归。

1.vue2.x和vue3.x的编译对比

       Vue2.x中的Compile过程会是这样:

       parse词法分析,编译模板生成原始粗糙的AST。

       optimize优化原始AST,标记ASTElement为静态根节点或静态节点。

       generate根据优化后的AST,生成可执行代码,例如_c、_l之类的。

       åœ¨Vue3中,整体的Compile过程仍然是三个阶段,但是不同于Vue2.x的是,第二个阶段换成了正常编译器都会存在的阶段transform。

       parse词法分析,编译模板生成原始粗糙的AST。

       transform遍历AST,对每一个ASTelement进行转化,例如文本元素、指令元素、动态元素等等的转化

       generate根据优化后的AST,生成可执行代码函数。

2.源码编译入口

       æˆ‘们先从一个入口来开始我们的源码阅读,packages/vue/index.ts。

//web平台特有编译函数functioncompileToFunction(template:string|HTMLElement,options?:CompilerOptions):RenderFunction{ //省略...if(template[0]==='#'){ //获取模版内容constel=document.querySelector(template)//省略...template=el?el.innerHTML:''}//编译const{ code}=compile(template,extend({ //省略...},options))constrender=(__GLOBAL__?newFunction(code)():newFunction('Vue',code)(runtimeDom))asRenderFunction//省略...return(compileCache[key]=render)}//注册编译函数registerRuntimeCompiler(compileToFunction)export{ compileToFunctionascompile}

       è¿™ä¸ªå…¥å£æ–‡ä»¶çš„代码比较简单,只有一个compileToFunction函数,但函数体内的内容却又比较关键,主要是经历以下步骤:

       ä¾èµ–注入编译函数至runtimeregisterRuntimeCompiler(compileToFunction)

       runtime调用编译函数compileToFunction

       è°ƒç”¨compile函数

       è¿”回包含code的编译结果

       å°†code作为参数传入Function的构造函数将生成的函数赋值给render变量

       å°†render函数作为编译结果返回

3.template获取

       app.mount()获取了templatepackages/runtime-dom/src/index.ts

4.编译template

       compile将传?template编译为render函数,packages/runtime-core/src/component.ts

       å®žé™…执?的是baseCompile,packages/compiler-core/src/compile.ts

       ç¬¬?步解析-parse:解析字符串template为抽象语法树ast

       ç¬¬?步转换-transform:解析属性、样式、指令等

       ç¬¬ä¸‰æ­¥?成-generate:将ast转换为渲染函数

Vue3编译器优化策略

       è¿™æ˜¯ä¸€ä¸ªéžå¸¸å…¸åž‹çš„用内存换时间的操作

1.静态节点提升<div><div>{ { msg}}</div><p>coboy</p><p>coboy</p><p>coboy</p></div>

       ä»¥ä¸Šè¿™ä¸ªæ®µtemplate如果没有开启静态节点提升它编译后是这样的:

import{ toDisplayStringas_toDisplayString,createVNodeas_createVNode,openBlockas_openBlock,createBlockas_createBlock}from"vue"exportfunctionrender(_ctx,_cache,$props,$setup,$data,$options){ return(_openBlock(),_createBlock("div",null,[_createVNode("div",null,_toDisplayString(_ctx.msg),1/*TEXT*/),_createVNode("p",null,"coboy"),_createVNode("p",null,"coboy"),_createVNode("p",null,"coboy")]))}

       å¦‚果开启了静态节点提升之后它编译后则是这样的:

import{ toDisplayStringas_toDisplayString,createVNodeas_createVNode,openBlockas_openBlock,createBlockas_createBlock}from"vue"const_hoisted_1=/*#__PURE__*/_createVNode("p",null,"coboy",-1/*HOISTED*/)const_hoisted_2=/*#__PURE__*/_createVNode("p",null,"coboy",-1/*HOISTED*/)const_hoisted_3=/*#__PURE__*/_createVNode("p",null,"coboy",-1/*HOISTED*/)exportfunctionrender(_ctx,_cache,$props,$setup,$data,$options){ return(_openBlock(),_createBlock("div",null,[_createVNode("div",null,_toDisplayString(_ctx.msg),1/*TEXT*/),_hoisted_1,_hoisted_2,_hoisted_3]))}

       æˆ‘们可以看到template里存在大量的不会变的p标签,所以当这个组件重新渲染的时候,这些静态的不会变的标签就不应该再次创建了。所以vue3就把这些静态的不会变的标签的VNode放在了render函数作用域的外面,在下次render函数再次执行的时候,那些静态标签的VNode已经在内存里了,不需要重新创建了。相当于占用当前机器的内存,避免重复创建VNode,用内存来换时间。大家仔细斟酌一番静态提升的字眼,静态二字我们可以不看,但是提升二字,直抒本意地表达出它(静态节点)被提高了。

2.补丁标记和动态属性记录<div><div:title="title">coboy</div></div>

       æ„æ€å°±æ˜¯åœ¨ç¼–译的过程中,像人眼一样对模版进行扫描看哪些东西是动态的,然后提前把这些动态的东西提前保存起来,作个标记和记录,等下次更新的时候,只更新这些保存起来的动态的记录。比如上面模版的title是动态的,提前做个标记和记录,更新的时候就只更新title部分的内容。

import{ createVNodeas_createVNode,openBlockas_openBlock,createBlockas_createBlock}from"vue"exportfunctionrender(_ctx,_cache,$props,$setup,$data,$options){ return(_openBlock(),_createBlock("div",null,[_createVNode("div",{ title:_ctx.title},"coboy",8/*PROPS*/,["title"])]))}<div><div:title="title">{ { text}}</div></div>import{ toDisplayStringas_toDisplayString,createVNodeas_createVNode,openBlockas_openBlock,createBlockas_createBlock}from"vue"exportfunctionrender(_ctx,_cache,$props,$setup,$data,$options){ return(_openBlock(),_createBlock("div",null,[_createVNode("div",{ title:_ctx.title},_toDisplayString(_ctx.text),9/*TEXT,PROPS*/,["title"])]))}

       æˆ‘们可以观察到在_createVNode函数的第四个参数是个9,后面是一个注释:/TEXT,PROPS/,这个是表示在当前的节点里面有两个东西是动态的,一个是内部的文本,一个是属性,然后具体是哪个属性,在第五个参数的数组里面则记录了下来["title"],有个title的属性是动态的。

       åœ¨å°†æ¥è¿›è¡Œpatch更新的时候,就可以根据当前记录的信息,进行更新,缩减更新过程和操作,可以非常精确地只进行title和文本的更新。

       å¦‚æžœdiv标签里是静态文本的话,_createVNode函数的第四个参数则变成了8,后面的注释变成了:/PROPS/,后面的第五个参数数据不变。

       _createVNode函数的第四个参数的数字其实是一个二进制数字转成十进制的数字。

       8的二进制是,9的二进制是,很容易可以看出二进制的每一位的数字都代表着特殊的含义。这些数字就是patchFlag,那么什么是patchFlag呢?

什么是patchFlag

       patchFlag是complier时的transform阶段解析ASTElement打上的补丁标记。它会为runtime时的patchVNode提供依据,从而实现靶向更新VNode和静态提升的效果。

       patchFlag被定义为一个数字枚举类型,它的每一个枚举值对应的标识意义是:

       TEXT=1动态文本的元素

       CLASS=2动态绑定class的元素

       STYLE=4动态绑定style的元素

       PROPS=8动态props的元素,且不含有class、style绑定

       FULL_PROPS=动态props和带有key值绑定的元素

       HYDRATE_EVENTS=事件监听的元素

       STABLE_FRAGMENT=子元素的订阅不会改变的Fragment元素

       KEYED_FRAGMENT=自己或子元素带有key值绑定的Fragment元素

       UNKEYED_FRAGMENT=没有key值绑定的Fragment元素

       NEED_PATCH=带有ref、指令的元素

       DYNAMIC_SLOTS=动态slot的组件元素

       HOISTED=-1静态的元素

       BAIL=-2不是render函数生成的一些元素,例如renderSlot

       æ•´ä½“上patchFlag的分为两大类:

       å½“patchFlag的值大于0时,代表所对应的元素在patchVNode时或render时是可以被优化生成或更新的

       å½“patchFlag的值小于0时,代表所对应的元素在patchVNode时,是需要被fulldiff,即进行递归遍历VNodetree的比较更新过程。

       ä»¥ä¸Šå°±æ˜¯vue3的一个非常高效的优化策略叫补丁标记和动态属性记录。

3.缓存事件处理程序functiontokenizer(input){ ...}functionparse(template){ consttokens=tokenizer(template)...}functiontransform(ast){ ...}functiontraverse(ast,context){ ...}functiongenerate(ast){ ...}functioncompile(template){ //1.解析constast=parse(template)console.log(JSON.stringify(ast,null,2))//2.转换transform(ast)//3.生成constcode=generate(ast)console.log(code)//returnfunctionrender(ctx){ //returnh("h3",{ },//ctx.title//)}returnnewFunction(code)()}lettmpl=`<h3>{ { title}}</h3>`compile(tmpl)0

       å°†æ¥æ¡†æž¶ä¼šåƒreact那样把@click="onClick"变成@click="()=>onClick()",最后可能是这样的一个箭头函数。那就意味着每次onClick的函数都是一个全新的函数,那就会造成这个回调函数明明没有变,都会被认为变了,那就必须进行一系列的更新,那么如果能把这个回调函数缓存起来,更新的时候,就不要再创建了。

       æœªè¿›è¡Œç¼“存事件处理程序之前的编译

functiontokenizer(input){ ...}functionparse(template){ consttokens=tokenizer(template)...}functiontransform(ast){ ...}functiontraverse(ast,context){ ...}functiongenerate(ast){ ...}functioncompile(template){ //1.解析constast=parse(template)console.log(JSON.stringify(ast,null,2))//2.转换transform(ast)//3.生成constcode=generate(ast)console.log(code)//returnfunctionrender(ctx){ //returnh("h3",{ },//ctx.title//)}returnnewFunction(code)()}lettmpl=`<h3>{ { title}}</h3>`compile(tmpl)1

       è¿›è¡Œç¼“存事件处理程序之后的编译

functiontokenizer(input){ ...}functionparse(template){ consttokens=tokenizer(template)...}functiontransform(ast){ ...}functiontraverse(ast,context){ ...}functiongenerate(ast){ ...}functioncompile(template){ //1.解析constast=parse(template)console.log(JSON.stringify(ast,null,2))//2.转换transform(ast)//3.生成constcode=generate(ast)console.log(code)//returnfunctionrender(ctx){ //returnh("h3",{ },//ctx.title//)}returnnewFunction(code)()}lettmpl=`<h3>{ { title}}</h3>`compile(tmpl).块block

       è¿™æ˜¯ä»€ä¹ˆæ„æ€å‘¢ï¼Ÿæ ¹æ®å°¤é›¨æºªæœ¬äººçš„解析,他说,根据他的统计那个动态的部分最多只有三分之一,基本上都是静态部分,所以在编译的过程中,能不能发现那个比较小的动态部分,把它放到比较靠上