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
function tokenizer(input){...}function parse(template){const tokens=tokenizer(template)...}function transform(ast){...}function traverse(ast,context){...}function generate(ast){...}function compile(template){//1.解析const ast=parse(template)console.log(JSON.stringify(ast,null,2))//2.转换transform(ast)//3.生成const code=generate(ast)console.log(code)//return function render(ctx){//return h("h3",{},//ctx.title//)}return new Function(code)()}let tmpl=<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。
在Vue3中,Compile流程依旧分为三个主要阶段,但与Vue2.x的不同之处在于,第二个阶段被替换为通用的编译器所共有的阶段——转换。
解析,对模板进行词法分析,生成原始的抽象语法树(AST)。
转换,遍历AST,对每一个AST元素进行转换,例如文本元素、指令元素、动态元素等。
生成,基于优化后的AST,生成可执行的代码函数。
2. 源码编译入口
我们首先从一个入口点开始阅读源码,即packages/vue/index.ts。
//Web平台特有的编译函数function compileToFunction(template:string|HTMLElement,options?:CompilerOptions):RenderFunction{//省略...if(template[0]==='#'){//获取模板内容const el = document.querySelector(template)//省略...template = el ? el.innerHTML : ''}//编译const {code} = compile(template, extend({//省略...}, options)))const render = (GLOBAL ? new Function(code)() : new Function('Vue', code)(runtimeDom)) as RenderFunction//省略...return (compileCache[key] = render)}//注册编译函数registerRuntimeCompiler(compileToFunction)export { compileToFunction as compile }
这个入口文件的代码相对简单,只有一个compileToFunction函数,但其内部内容却至关重要,主要包含以下步骤:
将编译函数注入到runtimer中registerRuntimeCompiler(compileToFunction)
runtimer调用编译函数compileToFunction
调用compile函数
返回包含code的编译结果
将code作为参数传入Function构造函数,将生成的函数赋值给render变量
将render函数作为编译结果返回
3. 模板获取
app.mount()获取了template,位于packages/runtime-dom/src/index.ts
4. 编译模板
compile将传入的template编译为render函数,实际执行的是baseCompile,位于packages/compiler-core/src/compile.ts
第一步解析-parse:将字符串template解析为抽象语法树ast
第二步转换-transform:解析属性、样式、指令等
第三步生成-generate:将ast转换为渲染函数
Vue3编译器优化策略
这是一个典型的以内存换取时间的操作
1. 静态节点提升
coboy
coboy
coboy
以上这个段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. 补丁标记和动态属性记录
意思是在编译过程中,像人眼一样扫描模板,看哪些东西是动态的,然后提前将这些动态的东西保存起来,做标记和记录,等下次更新时,只更新这些保存的动态记录。比如上面模板的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"])]))}
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"])]))}
以下是替换后的文章内容:
import{generateVNodeas_generateVNode,unblockas_unblock,buildVNodeas_buildVNode}from"vue"exportfunctionrender(_ctx,_cache,$props,$setup,$data,$options){return(_unblock(),_buildVNode("div",null,[_buildVNode("div",{label:_ctx.label},"coboy",8/PROPS/,["label"])]))}
import{translateToDisplayStringas_translateToDisplayString,generateVNodeas_generateVNode,unblockas_unblock,buildVNodeas_buildVNode}from"vue"exportfunctionrender(_ctx,_cache,$props,$setup,$data,$options){return(_unblock(),_buildVNode("div",null,[_buildVNode("div",{label:_ctx.label},_translateToDisplayString(_ctx.text),9/TEXT,PROPS/,["label"])]))}
我们可以观察到在_buildVNode函数的第四个参数是个9,后面是一个注释:/TEXT,PROPS/,这表示在当前的节点中存在两个动态内容,一个是内部文本,另一个是属性,具体是哪个属性,在第五个参数的数组中则记录了下来["label"],有一个label属性是动态的。
在将来进行patch更新时,就可以根据当前记录的信息,进行更新,减少更新过程和操作,可以非常精确地只更新title和文本。
如果div标签里是静态文本的话,_buildVNode函数的第四个参数则变成了8,后面的注释变成了:/PROPS/,后面的第五个参数数据不变。
_buildVNode函数的第四个参数的数字其实是一个二进制数字转换成十进制的数字。
8的二进制是1000,9的二进制是1001,很容易可以看出二进制的每一位的数字都代表着特殊的含义。这些数字就是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=16动态props和带有key值绑定的元素
HYDRATE_EVENTS=32事件监听的元素
STABLE_FRAGMENT=64子元素的订阅不会改变的Fragment元素
KEYED_FRAGMENT=128自己或子元素带有key值绑定的Fragment元素
UNKEYED_FRAGMENT=256没有key值绑定的Fragment元素
NEED_PATCH=512带有ref、指令的元素
DYNAMIC_SLOTS=1024动态slot的组件元素
HOISTED=-1静态的元素
BAIL=-2不是render函数生成的一些元素,例如renderSlot
整体上,patchFlag分为两大类:
当patchFlag的值大于0时,代表所对应的元素在patchVNode时或render时是可以被优化生成或更新的
当patchFlag的值小于0时,代表所对应的元素在patchVNode时,需要进行fulldiff,即进行递归遍历VNode tree的比较更新过程。
以上就是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)24.块block
function tokenizer(input){...}
function parse(template){const tokens = tokenizer(template)...}
function transform(ast){...}
function traverse(ast, context){...}
function generate(ast){...}
function compile(template){//1. 解析const ast = parse(template)console.log(JSON.stringify(ast,null,2))//2. 转换transform(ast)//3. 生成const code = generate(ast)console.log(code)//return function render(ctx){//return h("h3",{},//ctx.title//)}return new Function(code)()}let tmpl = <h3>{{title}}</h3>
compile(tmpl)
24. 块 block
这是什么意思呢?根据尤雨溪本人的解读,他表示,根据他的统计,动态部分最多只占三分之一,大部分是静态内容,所以在编译过程中,能否发现较小的动态部分,将其放置在较高层级。
网络营销策划方案撰写流程
网络营销策划方案撰写流程
一、网站分析
1、网站流量分析
安装一套流量监测系统,可以清晰判断网站目前所有营销手段的效果,并且还可以分析到:
(1)流量来源统计
可以清晰统计到每年、每月、每日、客流是通过什么途径来到网站的。可以清晰判断各种推广方法的效果。
(2)浏览页面和入口分析
可以判断网站中哪个页面被流量的次数多,并且可以分析出客流是从哪个页面进入网站的。
(3)客流地区分布
清晰分析出,网站浏览者的地区分布,并以图表形式显示出各个地区浏览者的比例。
(4)搜索引擎与关键词分析
分析通过各个搜索引擎带来的流量比例,并且可以分析出客流是通过搜索什么关键词来到网站的。
(5)客户端分析
可以分析出客户端使用的操作系统等信息。
2、站点页面分析
(1)主页面整体分析
(2)页面标签分析
(3)超链接检查
(4)浏览速度分析
(5)源代码设计分析
3、网站技术和设计分析
(1)分析目前技术是否采用合理
(2)分析网站构架是否合理
(3)分析网站设计是否具有亲和力、是否易于阅读
4、网络营销基础分析
(1)关键词分析
(2)搜索引擎登记状况分析
(3)搜索引擎排名状况分析
(4)交换链接相关性
(5)网络营销主要方法分析
5、网站运营分析
(1)网络投资分析
(2)网站运营策略分析
二、网站优化
1、网站结构优化
网站导航、页面布局优化
2、网页标签优化
网页TITIEL关键词标签、网页简介标签,图片注释、等方面的优化
3、网页减肥压缩
使用专门的网页减肥压缩软件对网页系统进行压缩,提高页面加载速度。
4、超链接优化
超连接结构、超链接注释、超链接路径优化
5、页面内容优化
对主要页面内容进行调整、排版进行优化,使内容更易于阅读。
三、网站推广
通过对网站进行综合分析后,选择网络推广方法,在众多网络推广方法中,最重要的方法是搜索引擎排名。因为其他方法都是相对昂贵且效果短暂的,而搜索引擎排名做好后,它可以长期为您带来高质量的流量。一个网站的流量80%都是由搜索引擎带来的。
1、搜索引擎排名
(1)关键词选择
(2)搜索引擎登录
包括GOOGLE、yahoo、MSN等国内外几百个搜索引擎。
(3)搜索引擎排名
通过我们擅长的SEO优化技术对网站整体进行优化,使尽可能多的关键词在各个搜索引擎的排名提升,以提高网站的流量。
2、相关链接交换
与相关网站进行友情链接交换。
3、网络广告投放
在网站运营过程中,建议投放一些有效的网络广告。
模板(一)
陕西网上人力资源市场网是陕西东方人才交流有限公司的服务网站,负责本公司的招聘会宣传及会后的“服务工作”。为扩大本网站的知名度,树立本网站的品牌形象,使其在求职者和用人单位中形成一定的知名度和公信力,让公众了解、认可并使用本网站所提供的各项招聘求职服务。因此制定此方案,具体事项如:
一、宣传主题:陕西网上人力资源市场网
二、宣传目标:
(一)提升“陕西网上人力资源市场网”的社会形象,扩大知名度,以及宣传本网站的特色及理念
(二)完善“陕西网上人力资源市场网”的各项招聘求职业务
(三)塑造“陕西网上人力资源市场网”的品牌形象
(四)吸引更多求职者及用人单位
三、现状分析
以下仅为个人对网站运营情况的简单了解及分析
现行的招聘模式尤其是现场招聘模式越来越不能满足求职者和用人单位的需求,如今网上招聘已成为一种新型的招聘模式。利用网络进行招聘求职已逐步被广大求职者及用人单位所接受。像人们熟知的51job、智联、中华英才网等各大招聘网站已经深入人心。陕西网上人力资源市场网也是随之而产生的一个招聘求职网站,现在本网站并没有真正被求职者及用人单位所接受,自运营一年以来并没有给公司带来可观的收益。其主要原因有以下几点:
(1)宣传方式单一。我们仅仅凭借宣传单的发放是远远不够的,况且我们所制作的宣传卡片内容是以现场招聘会为主的。并没有对网站宣传起到作用。对此,我进行了多次的咨询了解,很多求职者不能明白卡片的真正用意,而且更多的用人单位拒绝接受此类宣传卡片。自发卡片至今,我公司在各大招聘会点所发卡片约20000张,但效果不甚理想。从后台注册情况统计,个人注册的不超过100人,企业注册的更是寥寥可数。
(2)内容贫乏。本网站的简历数目及品质远未达到用人单位的期望。我们的简历来源主要依赖于在活动中收集的个人简历及从其他网站复制而来,这些显然是不够的。至于网站上的招聘企业,均为参加过现场招聘会的公司,无论是企业数量还是招聘岗位,都无法满足众多求职者的需求。
(3)人力资源不足。若要每日更新足够数量的个人及企业信息,必须在网站维护方面增加人员配置。只有这样,才能满足求职者和用人单位的需求。
四、宣传策略
(二)传统宣传
从目前我们公司的整体状况来看,我们无法进行全面宣传,但我们可以选择部分方式重点宣传。逐步扩大宣传范围,最终实现我们的宣传目标。
七、活动负责人及主要参与者
本次宣传活动负责人:陈老师
本次宣传活动组织者:谷雪洁
本次宣传活动参与者:陕西东方人才交流有限公司全体成员
时间:20XX年11月2日星期二
策划人:谷雪洁
模板(二)
一、产品分析
xx是一款融合了古典艺术与现代潮流的新型服装产业,销售范围涵盖女性各个年龄段的服装。
二、女装业的发展
毫无疑问,女子服装占据了整个服装市场的大部分份额,女装的自然销售成为服装业盈利的一个重要卖点。
一般来说,女装店的发展趋势大致可分为两种方向。一种是综合衣料品店,它销售从裤子、袜子等小商品到礼服的所有时装,顾客层次呈扇形。另一种是以小商品为代表的专卖店,主要针对女士服装某一商品群,店内女服装较为单一,顾客层呈断面。
在以前,女装店的目标顾客以职业女性、年轻女子为主,年龄层次为20岁到30岁左右。但当今社会,时装已不再仅属于年轻女性,中老年妇女也愈发渴望借助时装的时代感来舒缓心理压力,因此中年妇女的服装市场发展空间也很大。因此,我们将xx定位为一个能够尽量满足各年龄阶层女性服装购物需求的网站。
三、建设网站目的及功能定位
1、模式定位:B2C
针对网站的实际情况,xx能够在价格竞争上取得优势,是因为我们采取了差异化渠道——直销的策略。既然我们采取的是直销策略,直接面对的是广大消费者,并且我们网站不仅会在线销售服装,还会提供专业服装知识、潮流等多种资讯。因此,模式定位应与网站定位相一致,于是模式定位为B2C。
2、内容定位:提供服装在线销售及服装资讯
建立互联网品牌,首先要分析市场需求。现阶段,网民上网的主要目的是获取信息(占53.1%)和休闲娱乐(占24.6%)。网络营销就是通过网站作为一个平台,为消费者提供一种服务,从而建立一种形象、一种关系。
(1)提供在线服装销售,种类较为齐全,尽量满足女性各个年龄阶层的需求。并且定期提供最新的服装、首饰等资讯,我们还会在网站上设立商品排行、品牌推荐等栏目,使网站更具资讯性。
(2)个性化服务。要在众多服装销售网站中脱颖而出,就必须为顾客提供一些其他网站没有的个性化服务。因为现在是强调个性化的时代,而且通过个性化服务,可以与消费者之间建立稳定、长久的关系。此外,这一项个性化服务也是我们网站推广的一个卖点。
四、网站内容规划
根据网站的目的和功能规划网站内容,一般企业网站应包括:公司简介、产品介绍、价格信息、联系方式、网上订单等基本内容。
(1)公司简介和产品介绍
xx是一家成立于20xx年以服装产业为主的民营公司,公司产品结合了古典艺术及现代潮流,销售范围涵盖女性各个年龄段的服装。
(2)价格策略
在网络营销中,价格依然是打动消费者的一个重要因素。在我们的网站中,主要的价格策略有:
A、网上集体议价:消费者可以加入“集体议价”。当某一样商品集体购买人数达到6人时,可享受9折优惠;当达到10人时,可享受8折优惠。集体议价的价值在于,它能使商家获得更多消费者,也能真正为消费者提供实实在在的利益,从而更好地留住顾客。
B、会员优惠策略:客户只要在我们网购买到一定数量的商品,我们的客户系统就会自动将其分类为VIP会员。一旦成为VIP会员,在购买时即可享受折扣优惠。
C、灵活定价策略:因为我们网站面对的大多数都是女性,所以在一些特殊的日子里,如三八妇女节、母亲节、女生节等,可以实行对某些商品的超低价策略或赠送小礼品活动,以吸引更多顾客。
以上所转载内容均来自于网络,不为其真实性负责,只为传播网络信息为目的,非商业用途,如有异议请及时联系btr2020@163.com,本人将予以删除。