一、總述
上一章節我們講到通過解析将template轉成AST模型樹,接下來繼續對模型樹優化,進行靜态标注。那麼問題來了,什麼是靜态标注?為什麼要靜态标注。
在源碼的注釋中我們找到了下面這段話:
/**
* Goal of the optimizer: walk the generated template AST tree
* and detect sub-trees that are purely static, i.e. parts of
* the DOM that never needs to change.
*
* Once we detect these sub-trees, we can:
*
* 1. Hoist them into constants, so that we no longer need to
* create fresh nodes for them on each re-render;
* 2. Completely skip them in the patching process.
*/
1、永遠不需要變化的DOM就是靜态的。
2、重新渲染時,作為常量,無需建立新節點;在patch的過程中可以忽略他們(後面會專門介紹)。
接下來我們開始源碼之旅,這個子產品的代碼相對簡單。
二、markStatic
optimize方法位于src/compiler/optimize.js,
export function optimize (root: ?ASTElement, options: CompilerOptions) {
if (!root) return
isStaticKey = genStaticKeysCached(options.staticKeys || '')
isPlatformReservedTag = options.isReservedTag || no
//1、标注靜态節點
markStatic(root)
//2、标注靜态根節點
markStaticRoots(root, false)
}
整個過程分為兩部分,第一部分标注靜态節點,第二部分标注靜态根節點。首先看markStatic這個方法。
function markStatic (node: ASTNode) {
//1、标注節點的狀态
node.static = isStatic(node)
//2、對标簽節點進行處理
if (node.type === 1) {
// do not make component slot content static. this avoids
// 1. components not able to mutate slot nodes
// 2. static slot content fails for hot-reloading
if (
//非平台保留标簽(html,svg)
!isPlatformReservedTag(node.tag) &&
//不是slot标簽
node.tag !== 'slot' &&
//不是一個内聯模闆容器
node.attrsMap['inline-template'] == null
) {
return
}
//遞歸其子節點,标注狀态
for (let i = 0, l = node.children.length; i < l; i++) {
const child = node.children[i]
markStatic(child)
//子節點非靜态,則該節點也标注非靜态
if (!child.static) {
node.static = false
}
}
//對ifConditions進行循環遞歸
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
}
}
}
}
}
1、第一步,判斷節點狀态并标注。
function isStatic (node: ASTNode): boolean {
if (node.type === 2) { //表達式,标注非靜态
return false
}
if (node.type === 3) { // 普通文本,标注靜态
return true
}
return !!(node.pre || (//v-pre 指令||
!node.hasBindings && // (無動态綁定&&
!node.if && !node.for && // 沒有 v-if 和 v-for &&
!isBuiltInTag(node.tag) && // 不是内置的标簽,内置的标簽有slot和component &&
isPlatformReservedTag(node.tag) && //是平台保留标簽&&
!isDirectChildOfTemplateFor(node) &&//不是 template 标簽的直接子元素并且沒有包含在 for 循環中
Object.keys(node).every(isStaticKey)//結點包含的屬性隻能有isStaticKey中指定的幾個) //以上條件滿足則為靜态标簽
))
}
節點類型為表達式,标注為非靜态;普通文本為靜态。
v-pre指令(無需編譯)标注為靜态,或者滿足以下條件,也标注為靜态。
(1)無動态綁定
(2)沒有 v-if 和 v-for
(3) 不是内置的标簽,内置的标簽有slot和component
(4)是平台保留标簽(html和svg标簽)
(5)不是 template 标簽的直接子元素并且沒有包含在 for 循環中
(6)結點包含的屬性隻能有isStaticKey中指定的幾個.
2、第二步,對節點類型為标簽的進行處理。
首先對slot内容不做遞歸标注,直接傳回;其他的則循環其子節點,進行遞歸标注,如果子節點為非靜态,那麼該節點也要标注非靜态。是以整個标注過程是自下而上,先标注子節點,然後再是父節點,一層一層往上回溯。
對ifConditions的循環也是類似過程。
三、markStaticRoots
繼續看markStaticRoots方法
function markStaticRoots (node: ASTNode, isInFor: boolean) {
if (node.type === 1) {
//用以标記在v-for内的靜态節點,個屬性用以告訴renderStatic(_m)對這個節點生成新的key,避免patch error
if (node.static || node.once) {
node.staticInFor = isInFor
}
//一個節點要成為根節點,那麼要滿足以下條件:
//1、靜态節點,并且有子節點,
//2、子節點不能僅為一個文本節點
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], isInFor || !!node.for)
}
}
if (node.ifConditions) {
for (let i = 1, l = node.ifConditions.length; i < l; i++) {
markStaticRoots(node.ifConditions[i].block, isInFor)
}
}
}
}
一個節點要成為靜态根節點,需要滿足以下條件:
1、自身為靜态節點,并且有子節點,
2、子節點不能僅為一個文本節點
對于第二個條件,主要考慮到标記靜态根節點的受益較小。
接下來遞歸循環其子節點,循環标記。
以前一章節的template為例,标記完成後的AST模型如下:
{
"type": 1,
"tag": "div",
"attrsList": [
{
"name": "id",
"value": "app"
}
],
"attrsMap": {
"id": "app"
},
"children": [
{
"type": 1,
"tag": "ul",
"attrsList": [],
"attrsMap": {},
"parent": {
"$ref": "$"
},
"children": [
{
"type": 1,
"tag": "li",
"attrsList": [],
"attrsMap": {
"v-for": "item in items"
},
"parent": {
"$ref": "$[\"children\"][0]"
},
"children": [
{
"type": 2,
"expression": "\"\\n itemid:\"+_s(item.id)+\"\\n \"",
"tokens": [
"\n itemid:",
{
"@binding": "item.id"
},
"\n "
],
"text": "\n itemid:{{item.id}}\n ",
"static": false
}
],
"for": "items",
"alias": "item",
"plain": true,
"static": false,
"staticRoot": false,
"forProcessed": true
}
],
"plain": true,
"static": false,
"staticRoot": false
}
],
"plain": false,
"attrs": [
{
"name": "id",
"value": "\"app\""
}
],
"static": false,
"staticRoot": false
}
可以看到每個節點增加了static,staticRoot兩個屬性,由于葉節點包含了表達式,是以所有的父節點都标記為false。
五、總結
optimize整個過程邏輯較簡單,代碼也不多,給節點标記上static後,為後續的render和patch做準備,提升效率。