我的开源库:
- fly-barrage 前端弹幕库,项目官网:https://fly-barrage.netlify.app/,可实现类似于 B 站的弹幕效果,并提供了完整的 DEMO,Gitee 推荐项目;
- fly-gesture-unlock 手势解锁库,项目官网:https://fly-gesture-unlock.netlify.app/,在线体验:https://fly-gesture-unlock-online.netlify.app/,可高度自定义锚点的数量、样式以及尺寸;
优化器的逻辑相比解析器简单了很多,其只做了两件事:标记静态节点和标记静态根节点,所以理解起来会轻松很多。
为什么要做优化操作呢?因为在一个 Vue 应用中,有些页面内容是不随数据的改变而改变的,这些页面内容只需要在初次渲染的时候渲染一次就可以了,所以需要借助优化器打个静态的标记,这样在 patch 的环节,就可以直接跳过静态节点,提高性能。
这一小节,代码解释都在注释中,我写的很详细。
1,src/compiler/optimizer.js ==> optimize
export function optimize (root: ?ASTElement, options: CompilerOptions) {
if (!root) return
isStaticKey = genStaticKeysCached(options.staticKeys || '')
isPlatformReservedTag = options.isReservedTag || no
// first pass: mark all non-static nodes.
// 第一步:标记静态节点
markStatic(root)
// second pass: mark static roots.
// 第二步:标记静态根节点
markStaticRoots(root, false)
}
第一步标记静态节点,第二步标记静态根节点。
2,src/compiler/optimizer.js ==> markStatic 标记静态节点
// 标记所有的静态节点(从根节点向下)
function markStatic (node: ASTNode) {
// isStatic 判断某一个节点是不是静态节点
node.static = isStatic(node)
if (node.type === 1) {
// 对子节点进行静态节点标志的处理,因为只有元素节点才有子节点,所以用 if (node.type === 1) 进行判断
// 不要将自定义组件标记为静态节点,所以在这里,直接 return
if (
!isPlatformReservedTag(node.tag) &&
node.tag !== 'slot' &&
node.attrsMap['inline-template'] == null
) {
return
}
// 对子节点遍历执行 markStatic 函数,
for (let i = 0, l = node.children.length; i < l; i++) {
const child = node.children[i]
markStatic(child)
if (!child.static) {
// 如果有一个子节点不是静态节点的话,那么其父节点就肯定不是静态节点
node.static = false
}
}
// 如果当前的节点绑定了 v-if 的话
if (node.ifConditions) {
// 遍历除了它之外的存在于条件链的 AST 节点(可以看到是从下标 1 开始遍历的,而不是 0,下标 0 是 node 本身)
// 条件链是指这样的代码:
// <h2 v-if="status == 1">名字是小明</h2>
// <h3 v-else-if="status == 2">名字2:{{name}}</h3>
// <h4 v-else>名字是小山</h4>
// 如果条件链的某个分支节点不是静态节点的话,当前节点就不是静态节点,
for (let i = 1, l = node.ifConditions.length; i < l; i++) {
const block = node.ifConditions[i].block
markStatic(block)
if (!block.static) {
// 如果有一个子节点不是静态节点的话,那么其父节点就肯定不是静态节点
node.static = false
}
}
}
}
}
2-1,借助 isStatic 判断节点是不是静态节点
function markStatic (node: ASTNode) {
// isStatic 判断某一个节点是不是静态节点
node.static = isStatic(node)
}
函数开头直接借助 isStatic(node) 判断节点是不是静态节点,isStatic() 函数的源码如下:
// 判断某一个节点是不是静态节点
function isStatic (node: ASTNode): boolean {
// AST type 解释
// 1:元素节点
// 2:含有表达式的文本节点
// 3:纯文本节点
if (node.type === 2) { // expression
// 如果是含有表达式的文本节点的话,肯定不是静态节点
return false
}
if (node.type === 3) { // text
// 如果是纯文本节点的话,肯定是静态节点
return true
}
// 剩下的就是判断元素节点是不是静态节点
// 元素节点的判断稍微复杂一些,有很多种情况
// 一:如果该节点有 v-pre 指令的话,一定是静态节点。
// 二:如果该节点没有 v-pre 指令的话,则必须满足一系列的条件才能是静态节点。
// (1)不能有动态绑定语法(v-,@,:)
// (2)元素节点不能有 v-if 和 v-for 或者 v-else。
// (3)不能是内建组件(slot、component)
// (4)必须是平台上面的标签,例如:web 端的 div、p等等。
// (5)元素节点的父级节点不能是带 v-for 的 template,
// (6)元素节点上不存在动态节点才会有的属性
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-in
isPlatformReservedTag(node.tag) && // not a component
!isDirectChildOfTemplateFor(node) &&
Object.keys(node).every(isStaticKey)
))
}
3,src/compiler/optimizer.js ==> markStaticRoots 标记静态根节点
静态节点标记之后,接下来的工作就是标记静态根节点。
如果当前节点是静态节点,且该节点有子节点,并且这个子节点不是单一的文本节点的话,就将当前的节点标记为静态根节点。
// 标记静态根节点
function markStaticRoots (node: ASTNode, isInFor: boolean) {
if (node.type === 1) {
if (node.static || node.once) {
node.staticInFor = isInFor
}
// 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 = true
return
} else {
node.staticRoot = false
}
// 递归调用子节点
// 注意:如果当前节点被认定为静态根节点,就会 return,不会执行到下面的代码,
// 也就是说,一旦某个节点被认定为静态根节点,将不会再处理他的根节点
if (node.children) {
for (let i = 0, l = node.children.length; i < l; i++) {
markStaticRoots(node.children[i], isInFor || !!node.for)
}
}
if (node.ifConditions) {
for (let i = 1, l = node.ifConditions.length; i < l; i++) {
markStaticRoots(node.ifConditions[i].block, isInFor)
}
}
}
}