Vue.js 源码分析(二十) 指令篇 v-once指令详解
数据绑定最常见的形式就是使用“Mustache”语法 (双大括号) 的文本插值,例如:<p>Message: {{ msg }}</p>以后每当msg属性发生了改变,插值处的内容都会自动更新。
可以给DOM节点添加一个v-once指令,这样模板只会在第一次更新时显示数据,此后再次更新该DOM里面引用的数据时,内容不会自动更新了,例如:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
</head>
<body>
<div id="d" v-once>
<p>{{message}}</p>
</div>
<script>
Vue.config.productionTip=false;
Vue.config.devtools=false;
var app = new Vue({el:'#d',data:{message:'Hello World!'}})
</script>
</body>
</html>
DOM渲染为:

为了验证修改message属性不会触发DOM更新,我们在控制台输入app.message="Hello Vue"来修改message属性

可以发现DOM并未更新,此时app.message等于"Hello Vue!"的,我们打印看看,如下:

可以看到app.message等于Hello Vue!,为什么没有触发更新了,因为Vue内部把模板缓存起来了,把v-once对应的节点当作一个静态节点来看待,而不是一个响应式的数据(没有经过Object.defineproperty处理)
源码分析
在解析模板生成AST节点树对象的时候会通过processOnce尝试去获取v-once指令,如果有定义则在当前AST对象上增加一个once属性,值为true,如下:
function processOnce (el) { //第9460行 解析v-once属性
var once$$1 = getAndRemoveAttr(el, 'v-once'); //获取v-once属性
if (once$$1 != null) { //如果存在,则给el增加一个once属性,值为true
el.once = true;
}
}
例子里的模板解析时执行到这里后等于:

接下来在generate生成rendre函数的时候会先判断AST中有无once属性,如果有则调用genOnce函数,genOnce会调用genStatic()去生成一个静态节点
function genElement (el, state) { //第10139行 生成函数字符串
if (el.staticRoot && !el.staticProcessed) {
return genStatic(el, state)
} else if (el.once && !el.onceProcessed) { //如果有设置了once属性,则调用genOnce()函数
return genOnce(el, state)
} else if (el.for && !el.forProcessed) {
/*略*/
}
function genOnce (el, state) { //第10179行 渲染v-once指令
el.onceProcessed = true;
if (el.if && !el.ifProcessed) { //如果有定义了v-if指令
return genIf(el, state)
} else if (el.staticInFor) { //如果是在v-for环境下
var key = '';
var parent = el.parent;
while (parent) {
if (parent.for) {
key = parent.key;
break
}
parent = parent.parent;
}
if (!key) {
"development" !== 'production' && state.warn(
"v-once can only be used inside v-for that is keyed. "
);
return genElement(el, state)
}
return ("_o(" + (genElement(el, state)) + "," + (state.onceId++) + "," + key + ")")
} else {
return genStatic(el, state) //否则直接调用genStatic()函数
}
}
genStatic函数是静态节点渲染时的分支,如下:
function genStatic (el, state) { //第10172行
el.staticProcessed = true;
state.staticRenderFns.push(("with(this){return " + (genElement(el, state)) + "}")); //再次调用genElement(el, state),但是结果保存到state.staticRenderFns里面
return ("_m(" + (state.staticRenderFns.length - 1) + (el.staticInFor ? ',true' : '') + ")") //对于静态节点,返回格式为_m(id),id为staticRenderFns数组属性里的索引,生成Vnode时用于缓存用的
}
对于v-once和静态节点来说,渲染后它的render函数是一个_m函数,其中参数是一个索引值,是存储在staticRenderFns数组属性对应的索引,每个值是一个静态DOM,只会渲染一次的
例子里的模板渲染后等于render和staticRenderFns属性如下:

最后执行render函数的时候就会执行_m(0)这个函数,_m等于全局的renderStatic函数,如下:
writer by:大沙漠 QQ:22969969
function renderStatic ( //第3869行 渲染静态节点
index,
isInFor
) {
var cached = this._staticTrees || (this._staticTrees = []); //这个是缓存
var tree = cached[index]; //尝试从缓存中拿到tree
// if has already-rendered static tree and not inside v-for,
// we can reuse the same tree.
if (tree && !isInFor) { //如果该静态AST在缓存中有了,而且不是在v-for环境下
return tree //则直接返回tree即可
}
// otherwise, render a fresh tree.
tree = cached[index] = this.$options.staticRenderFns[index].call( //调用$options.staticRenderFns里对应的函数渲染成一个Vnode,并保存到缓存中
this._renderProxy,
null,
this // for render fns generated for functional component templates
);
markStatic(tree, ("__static__" + index), false); //设置标记
return tree
}
可以看到对于v-once节点来说,如果没有和v-if或v-for配合使用,则它会被当作一个静态节点来对待,经过了第一次渲染后就会把模板缓存起来,以后的更新渲染都只是从缓存中拿出结果而已。
Vue.js 源码分析(二十) 指令篇 v-once指令详解的更多相关文章
- Vue.js 源码分析(二十六) 高级应用 作用域插槽 详解
普通的插槽里面的数据是在父组件里定义的,而作用域插槽里的数据是在子组件定义的. 有时候作用域插槽很有用,比如使用Element-ui表格自定义模板时就用到了作用域插槽,Element-ui定义了每个单 ...
- Vue.js 源码分析(二十八) 高级应用 transition组件 详解
transition组件可以给任何元素和组件添加进入/离开过渡,但只能给单个组件实行过渡效果(多个元素可以用transition-group组件,下一节再讲),调用该内置组件时,可以传入如下特性: n ...
- Vue.js 源码分析(二十九) 高级应用 transition-group组件 详解
对于过度动画如果要同时渲染整个列表时,可以使用transition-group组件. transition-group组件的props和transition组件类似,不同点是transition-gr ...
- Vue.js 源码分析(二十四) 高级应用 自定义指令详解
除了核心功能默认内置的指令 (v-model 和 v-show),Vue 也允许注册自定义指令. 官网介绍的比较抽象,显得很高大上,我个人对自定义指令的理解是:当自定义指令作用在一些DOM元素或组件上 ...
- Vue.js 源码分析(二十二) 指令篇 v-model指令详解
Vue.js提供了v-model指令用于双向数据绑定,比如在输入框上使用时,输入的内容会事实映射到绑定的数据上,绑定的数据又可以显示在页面里,数据显示的过程是自动完成的. v-model本质上不过是语 ...
- Vue.js 源码分析(二十五) 高级应用 插槽 详解
我们定义一个组件的时候,可以在组件的某个节点内预留一个位置,当父组件调用该组件的时候可以指定该位置具体的内容,这就是插槽的用法,子组件模板可以通过slot标签(插槽)规定对应的内容放置在哪里,比如: ...
- jQuery 源码分析(二十) DOM操作模块 插入元素 详解
jQuery的DOM操作模块封装了DOM模型的insertBefore().appendChild().removeChild().cloneNode().replaceChild()等原生方法.分为 ...
- Vue.js 源码分析(二十三) 指令篇 v-show指令详解
v-show的作用是将表达式值转换为布尔值,根据该布尔值的真假来显示/隐藏切换元素,它是通过切换元素的display这个css属性值来实现的,例如: <!DOCTYPE html> < ...
- Vue.js 源码分析(二十一) 指令篇 v-pre指令详解
该指令会跳过所在元素和它的子元素的编译过程,也就是把这个节点及其子节点当作一个静态节点来处理,例如: <!DOCTYPE html> <html lang="en" ...
随机推荐
- C++入门到理解阶段二基础篇(6)——C++数组
概述 C++ 支持数组数据结构,它可以存储一个固定大小的相同类型元素的顺序集合.数组是用来存储一系列数据,但它往往被认为是一系列相同类型的变量. 数组的声明并不是声明一个个单独的变量,比如 numbe ...
- Redis for OPS 07:Redis 补充说明
写在前面的话 redis 的各种架构搭建暂时就到这里,本文主要用于补充说明 Redis 的一些概念以及配置文件的相关信息. 常用词汇 缓存穿透: 类似热点数据存储 Redis 一样,对于非热点数据存储 ...
- 投色子--html demo
这是之前客户想要看的一个效果,不知道放在博客里面有没有关系,当做备份吧. <!DOCTYPE HTML> <html> <head> <meta charse ...
- DataGridView中的rows.Count比实际行数多1的原因以及解决办法
场景 DataGridView怎样实现添加.删除.上移.下移一行: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/10281414 ...
- CAD制图初学入门教程:怎么在CAD中绘制箭头
在接触CAD的时候大家有没有和小编一样感觉无所适从,所以下面就来和大家分享一个CAD制图初学入门教程,在CAD中绘制箭头.在CAD图形上进行标注内容的时候一般都会使用箭头来进行指示,那具体怎么在CAD ...
- 从高版本的 SQL Server 向低版本的 SQL Server 转移数据
1.在源数据库上右键任务,选择生成脚本- 2.在生成脚本的高级选项中,根据数据库的内容,选择相应的选项,主要是红框圈出的部分,最后选择仅架构(若数据库的数据量不大,可以直接导出 架构和数据,在新数据库 ...
- SQL Server 数据库本地备份文件通过OSS工具上阿里云(恢复还原数据库)
SQL Server数据库上云,通过备份文件上传进行恢复. 1.通过OSS工具上传备份文件. 相关知识和操作步骤请参考: https://blog.csdn.net/weixin_35773751/a ...
- django前奏
目录 前言 web框架本质 服务器程序和应用程序 python三大主流web框架 django flask torndao Django安装配置 注意事项 命令行创建项目 app的概念 pycharm ...
- Python—变量详解
变量赋值 a = 1 b = 2 c = 3 print a, b, c # 1 2 3 a = b = c = 1 print a, b, c # 1 1 1 a, b, c = 1, 2, 3 p ...
- 大话IdentityServer4之使用 IdentityServer4 保护 ASP.NET Core 应用
这几天一直在研究IdentityServer4在asp.net core3.0中的应用,下面说说我的理解: 我们每一个.net core 项目大家可以理解为我新建了一个动物园或者植物园等,注册用户想要 ...