目录
解析器的作用是将html模板解析成AST,而优化器的作用是:在AST中找出静态子树并打上标记。
静态子树是指那些在AST中永远不会发生变化的节点。
标记静态子树两个好处:
1.每次重新渲染时,不需要为静态子树创建新节点;
2.在虚拟dom中打补丁的过程可以跳过。
每次重新渲染时,不需要为静态子树创建新节点,是什么意思?
前面介绍了每次重新渲染 都会使用最新的状态生成一份全新的VNode与旧的VNode进行对比。而在生成VNode的过程中,如果发现一个节点被标记为静态子树,那么除了首次渲染会生成节点之外,在重新渲染时并不会生成新的子节点树,而是克隆已存在的静态子树。
在虚拟dom中打补丁的过程可以被跳过,又是什么意思?
两个节点对比并更新dom时,如果是两个静态节点,旧不需要进行对比更新dom操作,直接跳过。
优化器的内部原理实现主要分为两个步骤:
1.在AST中找出所有静态节点并打上标记;
2.在AST中找出所有静态根节点并打上标记;
落实在AST中,静态节点指的是static属性为true的节点,例如:
{
type:1,
tag:"p",
staticRoot:false,
static:true,
// ...
}
例如:
<div id="el">Hello {{ name }}</div>
经过优化后的AST:
{
type:1,
tag:"div",
attrsList:[
{
name:"id",
value:"el"
}
],
attrsMap:{
id:"el"
},
children:[
{
type:2,
expression:'"Hello "+_s(name)',
text:'Hello {{name}}',
static:false // 标识
}
],
plain:false,
attrs:[
{
name:"id",
value:"'el'"
}
],
static:false, // 标识
staticRoot:false // 标识
}
在源码中是这样实现的:
export function optimize(root){
if(!root) return
// 第一步 标记所有静态节点
markStatic(root)
// 第二步 标记所有静态根节点
markStaticRoots(root)
}
1 找出所有静态节点并标记
找出所有静态子节点并不难,只需要从根节点开始,先判断根节点是不是静态根节点,再用相同的方式处理子节点,接着用同样的方式去处理子节点的子节点,知道所有子节点都被处理之后程序结束,这个过程叫做递归。
源码实现:
function isStatic(node){
if(node.type === 2){
// 带变量的动态文本节点
return false
}
if(node.type === 3){
// 不带变量的纯文本节点
return true
}
return !!(npde.pre || (
!node.hasBindings && // 没有动态绑定
!node.if && !node.for && // 没有v-if或v-else
!isBuiltInTag(node.tag) && // 不是内置标签
isPlatformReserverTag(node.tag) && // 不是组件
!isDirectChildOfTemplateFor(node) &&
Object.keys(node).every(isStaticKey)
))
}
AST中,type的取值:
1 表示 元素节点;
2 表示 带变量的动态文本节点;
3 表示 不带变量的纯文本节点。
如果元素节点使用了指令v-pre,那么直接断定是一个静态节点;如果元素节点没有使用指令v-pre,那么它必须同时满足以下条件才会被认为是一个静态节点:
1.不能使用动态绑定语法,也就是说标签上不能有以v-、@、:开头的属性;
2.不能使用v-if、v-for或v-else指令;
3.不能是内置标签,也就是说不能是slot或component;
4.不能是组件,即标签必须是保留标签,例如:<div></div>是保留标签,<list />不是保留标签;
5.当前节点的父节点不能是带v-for指定的template标签;
6.节点中不存在动态节点才会有的属性(v-for、v-if、v-else、v-else-if、v-once等)。
递归是从上向下依次标记,那么怎么标记父节点是静态根节点呢?
在字节的点被打完标记之后,需要判断它是否是静态节点,如果不是,那么它的父节点也不可能是静态节点,此时需要将父节点的static属性设置为false。
具体代码如下:
function markStatic(node){
node.static = isStatic(node)
if(node.type === 1){
for(let i=0,l=node.children.length;i<l;i++){
const child = node.children[i]
markStatic(child)
// 新增代码
if(!child.static){
node.static = false
}
}
}
}
2 找出所有静态根节点并标记
从根节点开始向下一层一层地用递归方式去找。不一样的是,如果一个节点被判定为静态根节点,那么将不会继续向它的子级继续寻找。因为静态子树肯定只有一个根,就是最上面的那个静态节点,如下图所示。
代码如下:
function markStaticRoots(node){
if(node.type === 1){
// 要使节点符合静态根节点的要求,它必须有子节点
// 这个子节点不能是只有一个静态文本节点的子节点,否则优化成本将超过受益
if(node.static && node.children.length && !(
node.children.length ===1 &&
node.children[0].type === 3
)){
node.staticRoot = true
return
}else{
node.staticRoot = false
}
if(node.children){
for(let i=0,l=node.children.length;i<l;i++){
markStaticRoots(node.children[i])
}
}
}
}
标记当前节点是否是静态根节点:如果节点是静态节点,并且有子节点,并且子节点不是只有一个文本类型的节点,那么该系欸按就是静态根节点,否则不是。排除两种情况:如果静态节点没有子节点,那么就不是静态根节点;如果静态节点只有一个文本节点,那么它也不是静态根节点。
标记子节点是否是静态根节点:循环子节点列表,然后将每一个子节点重复执行同一套逻辑即可。如果当前节点已经被标记为静态根节点,将不会在处理子节点,所以当node.staticRoot = true时就return掉。
3 总结
优化器的作用在AST中找出静态子树并打上标记,有两个好处:
1.每次重新渲染时,不需要为静态子树创建新节点;
2.在虚拟dom中打补丁的过程可以跳过。
优化器的内部实现分两个步骤:
1.在AST中找到所有静态节点并打上标记;
2.在AST中找出所有静态根节点并打上标记;
通过递归的方式从上向下标记静态节点时,如果一个节点被标记为静态节点,但它的子节点却被标记为动态节点,就说明该节点不是静态节点,可以将它改为动态节点。静态节点的特征是它的子节点必须是静态节点。
标记完静态节点之后标记静态根节点,其标记方式也是使用递归的方式从上往下寻找,在寻找的过程中遇到的第一个静态节点就为静态根节点,同时不再向下继续寻找。
注:本文章来自于《深入浅出vue.js》(人民邮电出版社)阅读后的笔记整理