当学习成为了习惯,常识也就变成了常识。 感激各位的 关注 、 点赞 、 珍藏 和评论。
新视频和文章会第一工夫在微信公众号发送,欢送关注:李永宁 lyn
文章已收录到 github 仓库 liyongning/blog,欢送 Watch 和 Star。
前言
上一篇文章 Vue 源码解读(8)—— 编译器 之 解析 具体详解了编译器的第一局部,如何将 html 模版字符串编译成 AST。明天带来编译器的第二局部,优化 AST,也是大家常说的动态标记。
指标
深刻了解编译器的动态标记过程
源码解读
入口
/src/compiler/index.js
/**
* 在这之前做的所有的事件,只有一个目标,就是为了构建平台特有的编译选项(options),比方 web 平台
*
* 1、将 html 模版解析成 ast
* 2、对 ast 树进行动态标记
* 3、将 ast 生成渲染函数
* 动态渲染函数放到 code.staticRenderFns 数组中
* code.render 为动静渲染函数
* 在未来渲染时执行渲染函数失去 vnode
*/
export const createCompiler = createCompilerCreator(function baseCompile (
template: string,
options: CompilerOptions
): CompiledResult {
// 将模版解析为 AST,每个节点的 ast 对象上都设置了元素的所有信息,比方,标签信息、属性信息、插槽信息、父节点、子节点等。// 具体有那些属性,查看 start 和 end 这两个解决开始和完结标签的办法
const ast = parse(template.trim(), options)
// 优化,遍历 AST,为每个节点做动态标记
// 标记每个节点是否为动态节点,而后进一步标记出动态根节点
// 这样在后续更新的过程中就能够跳过这些动态节点了
// 标记动态根,用于生成渲染函数阶段,生成动态根节点的渲染函数
if (options.optimize !== false) {optimize(ast, options)
}
// 从 AST 生成渲染函数,生成像这样的代码,比方:code.render = "_c('div',{attrs:{"id":"app"}},_l((arr),function(item){return _c('div',{key:item},[_v(_s(item))])}),0)"
const code = generate(ast, options)
return {
ast,
render: code.render,
staticRenderFns: code.staticRenderFns
}
})
optimize
/src/compiler/optimizer.js
/**
* 优化:* 遍历 AST,标记每个节点是动态节点还是动静节点,而后标记动态根节点
* 这样在后续更新的过程中就不须要再关注这些节点
*/
export function optimize(root: ?ASTElement, options: CompilerOptions) {if (!root) return
/**
* options.staticKeys = 'staticClass,staticStyle'
* isStaticKey = function(val) {return map[val] }
*/
isStaticKey = genStaticKeysCached(options.staticKeys || '')
// 平台保留标签
isPlatformReservedTag = options.isReservedTag || no
// 遍历所有节点,给每个节点设置 static 属性,标识其是否为动态节点
markStatic(root)
// 进一步标记动态根,一个节点要成为动态根节点,须要具体以下条件:// 节点自身是动态节点,而且有子节点,而且子节点不只是一个文本节点,则标记为动态根
// 动态根节点不能只有动态文本的子节点,因为这样收益太低,这种状况下始终更新它就好了
markStaticRoots(root, false)
}
markStatic
/src/compiler/optimizer.js
/**
* 在所有节点上设置 static 属性,用来标识是否为动态节点
* 留神:如果有子节点为动静节点,则父节点也被认为是动静节点
* @param {*} node
* @returns
*/
function markStatic(node: ASTNode) {
// 通过 node.static 来标识节点是否为 动态节点
node.static = isStatic(node)
if (node.type === 1) {
/**
* 不要将组件的插槽内容设置为动态节点,这样能够防止:* 1、组件不能扭转插槽节点
* 2、动态插槽内容在热重载时失败
*/
if (!isPlatformReservedTag(node.tag) &&
node.tag !== 'slot' &&
node.attrsMap['inline-template'] == null
) {
// 递归终止条件,如果节点不是平台保留标签 && 也不是 slot 标签 && 也不是内联模版,则间接完结
return
}
// 遍历子节点,递归调用 markStatic 来标记这些子节点的 static 属性
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、v-else-if、v-else 这些指令,则顺次标记 block 中节点的 static
if (node.ifConditions) {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}
}
}
}
}
isStatic
/src/compiler/optimizer.js
/**
* 判断节点是否为动态节点:* 通过自定义的 node.type 来判断,2: 表达式 => 动静,3: 文本 => 动态
* 但凡有 v-bind、v-if、v-for 等指令的都属于动静节点
* 组件为动静节点
* 父节点为含有 v-for 指令的 template 标签,则为动静节点
* @param {*} node
* @returns boolean
*/
function isStatic(node: ASTNode): boolean {if (node.type === 2) { // expression
// 比方:{{msg}}
return false
}
if (node.type === 3) { // text
return 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-in
isPlatformReservedTag(node.tag) && // not a component
!isDirectChildOfTemplateFor(node) &&
Object.keys(node).every(isStaticKey)
))
}
markStaticRoots
/src/compiler/optimizer.js
/**
* 进一步标记动态根,一个节点要成为动态根节点,须要具体以下条件:* 节点自身是动态节点,而且有子节点,而且子节点不只是一个文本节点,则标记为动态根
* 动态根节点不能只有动态文本的子节点,因为这样收益太低,这种状况下始终更新它就好了
*
* @param {ASTElement} node 以后节点
* @param {boolean} isInFor 以后节点是否被包裹在 v-for 指令所在的节点内
*/
function markStaticRoots(node: ASTNode, isInFor: boolean) {if (node.type === 1) {if (node.static || node.once) {
// 节点是动态的 或者 节点上有 v-once 指令,标记 node.staticInFor = true or false
node.staticInFor = isInFor
}
if (node.static && node.children.length && !(
node.children.length === 1 &&
node.children[0].type === 3
)) {
// 节点自身是动态节点,而且有子节点,而且子节点不只是一个文本节点,则标记为动态根 => node.staticRoot = true,否则为非动态根
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], isInFor || !!node.for)
}
}
// 如果节点存在 v-if、v-else-if、v-else 指令,则为 block 节点标记动态根
if (node.ifConditions) {for (let i = 1, l = node.ifConditions.length; i < l; i++) {markStaticRoots(node.ifConditions[i].block, isInFor)
}
}
}
}
总结
-
面试官 问:简略说一下 Vue 的编译器都做了什么?
答:
Vue 的编译器做了三件事件:
- 将组件的 html 模版解析成 AST 对象
- 优化,遍历 AST,为每个节点做动态标记,标记其是否为动态节点,而后进一步标记出动态根节点,这样在后续更新的过程中就能够跳过这些动态节点了;标记动态根用于生成渲染函数阶段,生成动态根节点的渲染函数
- 从 AST 生成运行渲染函数,即大家说的 render,其实还有一个,就是 staticRenderFns 数组,外面寄存了所有的动态节点的渲染函数
-
面试官:具体说一下动态标记的过程
答:
-
标记动态节点
- 通过递归的形式标记所有的元素节点
- 如果节点自身是动态节点,然而存在非动态的子节点,则将节点批改为非动态节点
-
标记动态根节点,基于动态节点,进一步标记动态根节点
- 如果节点自身是动态节点 && 而且有子节点 && 子节点不全是文本节点,则标记为动态根节点
- 如果节点自身不是动态根节点,则递归的遍历所有子节点,在子节点中标记动态根
-
-
面试官:什么样的节点才能够被标记为动态节点?
答:
- 文本节点
- 节点上没有 v-bind、v-for、v-if 等指令
- 非组件
链接
- 配套视频,微信公众号回复:” 精通 Vue 技术栈源码原理视频版 ” 获取
- 精通 Vue 技术栈源码原理 专栏
- github 仓库 liyongning/Vue 欢送 Star
感激各位的:关注 、 点赞 、 珍藏 和评论,咱们下期见。
当学习成为了习惯,常识也就变成了常识。 感激各位的 关注 、 点赞 、 珍藏 和评论。
新视频和文章会第一工夫在微信公众号发送,欢送关注:李永宁 lyn
文章已收录到 github 仓库 liyongning/blog,欢送 Watch 和 Star。