vue原理Compile之optimize标记静态节点源码示例

目录
  • 引言
  • 而 optimize 的作用是什么呢?
    • 1 是否存在 v-pre
    • 2 不能存在 node.hasBindings
    • 3 不能存在 node.if 和 node.for
    • 4 节点名称不能是slot 或者 component
    • 5 isPlatformReservedTag(node.tag)
    • 6 isDirectChildOfTemplateFor(node)
    • 7 Object.keys(node).every(isStaticKey)
  • 标记静态节点
    • 1 isStatic 这个方法对 ast 节点本身进行初步判断
    • 2 判断静态节点的额外的处理
      • 节点类型是 1
    • 3 是正常 html 标签,标签是 slot,存在 inline-template 属性
      • 疑点
  • 标记静态根节点
    • markStaticRoots 和 markStatic$1 有什么区别?
    • 被判断为静态根节点的条件是什么?
    • 为什么子节点只有是静态文本时,成本会大?
      • 1 维护静态模板存储对象
      • 2 多层函数调用

引言

optimize这一步的内容好像不太多,但是非常重要,于是是一个更新性能优化, 非常重要

先来看看 optimize 在什么位置,就在 parse 处理完之后,generate 之前

var ast = parse(template.trim(), options);
if (options.optimize !== false) {
    optimize(ast, options);
}
var code = generate(ast, options);

上面这段代码在函数 baseCompile 中,如果想了解的,看这里 Compile - 从新建实例到 compile结束的主要流程

而 optimize 的作用是什么呢?

Vue官方注释

优化器的目标

遍历生成的模板AST树,检测纯静态的子树,即永远不需要更改的DOM。

一旦我们检测到这些子树,我们可以:

1、把它们变成常数,这样我们就不需要了在每次重新渲染时为它们创建新的节点

2、在修补过程中完全跳过它们。

那是怎么做的呢?

给静态ast节点设置属性 static,当节点时静态是

el.static = true

下面就来看下源码

function optimize(root, options) {
    if (!root) return
    // first pass: mark all non-static nodes.
    markStatic$1(root);
    // second pass: mark static roots.
    markStaticRoots(root);
}

里面主要调用了两个函数,这两个函数会分别分析

但是在此之前,我们先来看一个函数,这个函数就是 判断静态节点的 主力函数

直接传入 ast 节点,各种组合判断,然后给 ast 节点添加上 static 属性

function isStatic(node) {
    // 文字表达式
    if (node.type === 2) return false
    // 纯文本
    if (node.type === 3) return true
    return (
        // 设置了 v-pre 指令,表示不用解析
        node.pre ||
        (
            !node.hasBindings && // 没有动态绑定
            ! node.if && !node.for && // 不存在v-if ,v-for 指令
            ! ['slot','component'].indexOf(node.tag)>-1 && // 需要编译的标签
            isPlatformReservedTag(node.tag) && // 正常html 标签
            ! isDirectChildOfTemplateFor(node) &&
            Object.keys(node).every(isStaticKey)
        )
    )
}

如果要判断为静态节点,就要经过下面7个条件的审判(把上面的代码列了出来)

1 是否存在 v-pre

如果添加了指令 v-pre,那么 node.pre 为 true,表明所有节点都不用解析了

2 不能存在 node.hasBindings

当节点有绑定 Vue属性的时候,比如指令,事件等,node.hasBindings 会为 true

3 不能存在 node.if 和 node.for

同样,当 节点有 v-if 或者 v-for 的时候,node.if 或者 node.for 为true

4 节点名称不能是slot 或者 component

因为这两者是要动态编译的,不属于静态范畴

所以只要是 slot 或者 component 都不可能是静态节点

5 isPlatformReservedTag(node.tag)

isPlatformReservedTag 是用于判断该标签是否是正常的HTML 标签,有什么标签呢?

标签必须是正常HTML标签,如下

html,body,base,head,link,meta,style,title

address,article,aside,footer,header,h1,h2,h3,h4,h5,h6,hgroup,nav,section

div,dd,dl,dt,figcaption,figure,picture,hr,img,li,main,ol,p,pre,ul

a,b,abbr,bdi,bdo,br,cite,code,data,dfn,em,i,kbd,mark,q,rp,rt,rtc,ruby

s,samp,small,span,strong,sub,sup,time,u,var,wbr,area,audio,map,track,video

embed,object,param,source,canvas,script,noscript,del,ins

caption,col,colgroup,table,thead,tbody,td,th,tr

button,datalist,section,form,input,label,legend,meter,optgroup,option

output,progress,select,textarea

details,dialog,menu,menuitem,summary

content,element,shadow,template,blockquote,iframe,tfoot

svg,animate,circle,clippath,cursor,defs,desc,ellipse,filter,font-face

foreignObject,g,glyph,image,line,marker,mask,missing-glyph,path,pattern

polygon,polyline,rect,switch,symbol,text,textpath,tspan,use,view

是不是挺多的,哈哈,尤大真厉害,估计收集了很多,我觉得应该有用,就全放上来了

6 isDirectChildOfTemplateFor(node)

看下这个函数的源码

function isDirectChildOfTemplateFor(node) {
    while (node.parent) {
        node = node.parent;
        if (node.tag !== 'template') {
            return false
        }
        if (node.for) {
            return true
        }
    }
    return false
}

表明了节点父辈以上所有节点不能是 template 或者 带有 v-for

7 Object.keys(node).every(isStaticKey)

isStaticKey是一个函数,用于判断传入的属性是否在下面的范围内

type,tag,attrsList,attrsMap,plain,parent,children,attrs

比如这样的 ast

<div style="" ></div>
{
    attrsList: []
    attrsMap: {style: ""}
    children: []
    parent: undefined
    plain: false
    tag: "div"
    type: 1
}

上面的 ast 的所有属性通过 isStaticKey 判断之后,都在上面列出的属性范围中,都是静态属性,所以这就是一个静态节点

而当你存在之外的其他属性的时候,这个节点就不是静态ast

然后下面就来看 optimize 中出现的两个函数把

markStatic$1 和 markStaticRoot

标记静态节点

怎么标记一个节点是否是静态节点呢,就在 markStatic$1 中进行处理

// 标记节点是否是静态节点
function markStatic$1(node) {
    node.static = isStatic(node);
    if (node.type !== 1) return
    // 不要将组件插槽内容设置为静态。
    // 这就避免了
    // 1、组件无法更改插槽节点
    // 2、静态插槽内容无法热加载
    if (
        // 正常 thml 标签 才往下处理,组件之类的就不可以
        !isPlatformReservedTag(node.tag) &&
        // 标签名是 slot 才往下处理
        node.tag !== 'slot' &&
        // 有 inline-tempalte 才往下处理
        node.attrsMap['inline-template'] == null
    ) {
        return
    }
    // 遍历所有孩子,如果孩子 不是静态节点,那么父亲也不是静态节点
    var l = node.children.length
    for (var i = 0;i < l; i++) {
        var child = node.children[i];
        // 递归设置子节点,子节点再调用子节点
        markStatic$1(child);
        if (!child.static) {
            node.static = false;
        }
    }
    if (node.ifConditions) {
        var c = node.ifConditions.length
        for (var j = 1; j < c; j++) {
            // block 是 节点的 ast
            var block = node.ifConditions[j].block;
            markStatic$1(block);
            if (!block.static) {
                node.static = false;
            }
        }
    }
}

这个方法做了下面三种事情

1 isStatic 这个方法对 ast 节点本身进行初步判断

进行初步静态节点判断

2 判断静态节点的额外的处理

给节点本身判断完是否静态节点之后,需要做额外的处理,就是需要检查所有的子孙节点

于是便会逐层递归子节点,如果某子节点不是静态节点,那么父节点就不能是静态节点,但是并不是所有节点都会进行特殊处理,是有条件的

节点类型是 1

  • 类型 1 是 标签元素
  • 类型 2 是 文字表达式
  • 类型 3 是 纯文本

3 是正常 html 标签,标签是 slot,存在 inline-template 属性

1、必须是正常标签,也就是说自定义标签不需要再次处理

2、slot 会额外处理

3、有 inline-template 属性也会额外处理

只有有一个满足,就会进行额外处理

疑点

你可以看到源码中的最后一步

判断 node.ifCondition,并且如果 ifCondition 中保存的节点不是静态的话,那么这个 node 也不是静态节点

这个判断就很让我匪夷所思了

明明如果存在 v-if 的话,该节点在 一开始的 isStatic 中,就会被设置 node.static 为 false 了

为什么还要在末尾 再判断一遍呢?

这里我觉得好像有点多余?反正我没有想通 尤大 的想法啊啊啊啊啊,为了确保正确?

经过这一步,所有的节点,都会被添加上 static 属性,节点是否静态,一看便知

标记静态根节点

// 标记根节点是否是静态节点
function markStaticRoots(node) {
    if (node.type === 1) return
    // 要使一个节点符合静态根的条件,它应该有这样的子节点
    // 不仅仅是静态文本。否则,吊装费用将会增加
    // 好处大于好处,最好总是保持新鲜。
    if (
        // 静态节点
        node.static &amp;&amp;
        // 有孩子
        node.children.length &amp;&amp;
        // 孩子有很多,或者第一个孩子不是纯文本
        ! (node.children.length === 1
            &amp;&amp; node.children[0].type === 3
          )
    ) {
        node.staticRoot = true;
        return
    }
    else {
        node.staticRoot = false;
    }
    if (node.children) {
        var l = node.children.length
        for (var i = 0; i &lt; l; i++) {
            markStaticRoots(
                node.children[i]
            );
        }
    }
}

这个方法只会不断的寻找 静态的根节点,应该说是区域根节点吧,反正一个节点下面有马仔节点,这个节点就算是根节点

递归他的所有子孙,看看谁是静态根节点,如果是静态ast,就会被添加上 staticRoot 这个属性

markStaticRoots 也是递归调用的,但是并不是会处理到所有节点

因为找到一个根节点是静态根节点后,就不会递归处理他的子节点了

然后我们需要了解两个问题

1、markStaticRoot 和 markStatic$1 的区别

2、判断静态根节点的依据是什么

markStaticRoots 和 markStatic$1 有什么区别?

找出静态根节点才是性能优化的最终作用者

markStatic$1 这个函数只是为 markStaticRoots 服务的,是为了先把每个节点都处理之后,更加方便快捷静态根节点,可以说是把功能分开,这样处理的逻辑就更清晰了

先给所有的节点都划分身份,之后处理静态节点时,只用找 那部分的根节点(区域负责人就好了)

当然,上面都是我个人的理解,那么我的依据是什么呢?

markStatic$1 添加的 static 属性,我全局搜索,并没有在处理DOM和 生成 render上使用过

而 markStaticRoots 添加的 staticRoot ,在生成 render 上使用了

而且再 根据 markStaticRoots 写的功能逻辑 并 使用了 static 属性进行判断

所以我认为 markStatic$1 是为 markStaticRoots 服务的一个函数

被判断为静态根节点的条件是什么?

1该节点的所有子孙节点都是静态节点

而 node.static = true 则表明了其所有子孙都是静态的,否则上一步就被设置为 false 了

2必须存在子节点

3子节点不能只有一个 纯文本节点

这一点我不太明白,为什么只有一个纯文本子节点时,这个点不能是静态根节点?

注意:只有纯文本子节点时,他是静态节点,但是不是静态根节点。静态根节点是optimize 优化的条件,没有静态根节点,说明这部分不会被优化

而 Vue 官方说明是,如果子节点只有一个纯文本节点,如果优化的话,带来的成本就比好处多了,所以就不优化

那么我就疑惑了

为什么子节点只有是静态文本时,成本会大?

下面是我的个人探索的想法

首先,我们明确,优化的好处是,减少DOM比对,加速更新

而带来的成本是什么呢?

1、维护静态模板存储对象

2、多层函数调用

现在我们来简单解释下上面两种成本

1 维护静态模板存储对象

一开始的时候,所有的静态根节点 都会被解析生成 VNode,并且被存在一个缓存对象中,就在 Vue.proto._staticTree 中

比如下面这个静态模板

解析后被存了进去

随着静态根节点的增加,这个存储对象也会越来越大,那么占用的内存就会越来越多

势必要减少一些不必要的存储,所有只有纯文本的静态根节点就被排除了

2 多层函数调用

这个问题涉及到 render 和 静态 render 的合作

举个例子

一个动态跟静态混合的模板

生成的 render 函数是这样的

with(this) {
    return _c('div', [
        _m(0),
        ( testStaticRender) ? _c('span') : _e()
    ])
}

看到 _m(0) 了吗,这个函数就是去获取静态模板的

这样,静态模板的处理

就多了一个 _m 函数的调用,加上初期涉及到了很多函数的处理,其中包括上一步的存储

再者,既然纯文本节点不做优化

那么就是说更新时需要比对这部分喽?

但是纯文本的比对,就是直接 比较字符串 是否相等而已啊

消耗简直不要太小,那么这样,我还有必要去维护多一个静态模板缓存吗?

综上所述

只有纯文本子节点最好不要当做静态模板处理

以上只是个人的意淫想法,如有不同意见可以提出

番外疑惑

我不禁疑惑到,难道只有一个普通标签子节点的时候,好处难道会大一些吗?

可以看到模板放在了 staticRenderFns 上,做了静态模板处理

结果论出发的话,可能消耗的确大一些吧哈哈哈

更新的时候,会比较 div 和 span 和 span 内的纯文本

以上就是vue原理Compile之optimize标记静态节点源码示例的详细内容,更多关于Compile optimize标记静态节点的资料请关注我们其它相关文章!

(0)

相关推荐

  • Vue 中的compile操作方法

    在 Vue 里,模板编译也是非常重要的一部分,里面也非常复杂,这次探究不会深入探究每一个细节,而是走一个全景概要,来吧,大家和我一起去一探究竟. 初体验 我们看了 Vue 的初始化函数就会知道,在最后一步,它进行了 vm.$mount(el) 的操作,而这个 $mount 在两个地方定义过,分别是在 entry-runtime-with-compiler.js(简称:eMount) 和 runtime/index.js(简称:rMount) 这两个文件里,那么这两个有什么区别呢? // entr

  • vue原理Compile从新建实例到结束流程源码

    目录 引言 1 获取 template 模板 2 生成 render 3 保存 render createCompiler baseCompile createCompilerCreator 生成一个函数 compile 返回 compileToFunctions 和 compile 但是为什么分出一个 compileToFunctions 呢? 因为要做模板缓存!! createCompileToFunctionFn 他的缓存是怎么做的 理清思路 内部函数的作用是 引言 Compile 的内容

  • 详解如何解决Vue和vue-template-compiler版本之间的问题

    今天把远程仓库拉下项目,运行'npm run dev'时,报错 Module build failed: Error: Cannot find module 'vue-template-compiler' 报错原因:通常出现于一些依赖库的更新或者安装新的依赖库之后(可以认为npm update已经成为一种习惯),导致了vue和vue-template-compiler的版本不一致. 解决方案:统一vue和vue-template-compiler的版本 "vue": "2.3

  • 解决vue安装less报错Failed to compile with 1 errors的问题

    1.创建vue项目后安装less,执行 npm install less less-loader --save-dev 下载版本为:less-loader@6.1.0 , less@3.11.3,重启服务报错,报错信息如下: 2.报错原因 less 本版太高需要降低版本,执行代码 先移除之前版本: npm uninstall less-loader 下载指定版本: npm install less-loader@5.0.0 -D 3.重启代码就可以了,若还是报错可移除文件node_modules

  • Vue编译器optimize源码分析

    目录 引言 optimize 源码之旅 markStatic$1源码 isStatic源码 复杂点的 回归到markStatic$1 markStaticRoots 源码 引言 接上文 parseHTML 函数源码解析 chars.end.comment钩子函数 上一章节我们讲到通过解析将template转成AST(抽象语法树),接下来继续对模型树优化,进行静态标注.那么问题来了,什么是静态标注?为什么要静态标注. 在源码的注释中我们找到了下面这段话: /** * Goal of the opt

  • vue原理Compile之optimize标记静态节点源码示例

    目录 引言 而 optimize 的作用是什么呢? 1 是否存在 v-pre 2 不能存在 node.hasBindings 3 不能存在 node.if 和 node.for 4 节点名称不能是slot 或者 component 5 isPlatformReservedTag(node.tag) 6 isDirectChildOfTemplateFor(node) 7 Object.keys(node).every(isStaticKey) 标记静态节点 1 isStatic 这个方法对 as

  • SparkGraphx计算指定节点的N度关系节点源码

    直接上代码: package horizon.graphx.util import java.security.InvalidParameterException import horizon.graphx.util.CollectionUtil.CollectionHelper import org.apache.spark.graphx._ import org.apache.spark.rdd.RDD import org.apache.spark.storage.StorageLevel

  • vue中v-for加载本地静态图片方法

    vue-cli 项目中本地图片放在assets目录下(原因vue-cli最开始的vue图片就在里面,就把所有图片放在里面了): 之后v-for 动态加载图片路径就遇到了问题 源码: <ul> <li v-for="(item,index) in teamInfo" @click="trastFun(item)"> <div><img v-bind:src="item.imageurl"/></

  • vue完成项目后,打包成静态文件的方法

    vue完成项目后,如何打包成静态文件,并且用Node调试 打包 1.修改config里面的index.js里面的productionSourceMap为false,默认情况是true(true代表打包环境是开发环境,可以进行调试:false表示生产环境,正式上线的) 2.在cmd里面运行npm run build,(运行的是build里面的build.js文件) 生成的包放在dist下面 使用node进行调试 1.在根目录下创建prod.server.js文件,这个文件的作用是作为一个小的htt

  • vue在index.html中引入静态文件不生效问题及解决方法

    本文针对的是Vue小白,不喜勿喷,谢谢 出现该问题的标志如下 控制台warning(Resource interpreted as Stylesheet but transferred with MIME type text/html) 出现的原因及解决办法 第一种可能出现原因就是引入的静态文件在src文件夹内,这种的解决办法就是把资源引入静态资源的目录static 第二种可能出现的原因就是有单独的静态资源目录但是名字不叫static,这种的解决办法更改配置文件,把对应的几个配置文件内的stat

  • vue+node实现图片上传及预览的示例方法

    本文介绍了vue+node实现图片上传及预览的示例方法,分享给大家,具体如下: 先上效果图 上代码 html部分主要是借助了weui的样式 <template> <div> <myheader :title="'发布动态'"> <i class="iconfont icon-fanhui1 left" slot="left" @click="goback"></i>

  • ssm+vue前后端分离框架整合实现(附源码)

    前言 本文针对Spring+SpringMVC+Mybatis后台开发框架(基于maven构建)与vue前端框架(基于webpack构建)的项目整合进行介绍,对于ssm和vue单独项目的搭建不作为本文的重点,而着重介绍两者之间交互的要点. SSM 项目结构 说明 项目有service和web两个子项目组成,web依赖于service,其中web主要是control层内容,service则对应service层,而MyBatis内容放在了service项目中,spring配置文件放在了web项目中.

  • Springboot+Vue+shiro实现前后端分离、权限控制的示例代码

    本文总结自实习中对项目的重构.原先项目采用Springboot+freemarker模版,开发过程中觉得前端逻辑写的实在恶心,后端Controller层还必须返回Freemarker模版的ModelAndView,逐渐有了前后端分离的想法,由于之前,没有接触过,主要参考的还是网上的一些博客教程等,初步完成了前后端分离,在此记录以备查阅. 一.前后端分离思想 前端从后端剥离,形成一个前端工程,前端只利用Json来和后端进行交互,后端不返回页面,只返回Json数据.前后端之间完全通过public A

  • vue中使用router全局守卫实现页面拦截的示例

    一.背景 在vue项目中使用vue-router做页面跳转时,路由的方式有两种,一种是静态路由,另一种是动态路由.而要实现对路由的控制需要使用vuex和router全局守卫进行判断拦截(安全问题文章最后讨论) 二.使用场景 静态路由的使用场景:在我们使用静态路由实现页面跳转时,不管我们是否登录,当我们在地址栏修改地址后,页面会发生跳转并展示页面内容(数据并不会被展示出来),这样的问题显然是不能够被接受的: 动态路由的使用场景:动态路由无非就是从后端拿到了数据然后在加到router里面了.假如用户

随机推荐