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" ...
随机推荐
- 借助Git实现本地与GitHub远程双向传输(同步GitHub仓库)以及一些使用错误解决
前言 GitHub作为程序员必备的学习交流平台,虽然在国内速度不算快,但只要好好利用这个平台,我相信还是可以学习到很多东西.在暑期的时候,我曾经就初次远程连接到了GitHub,但开学后,不知道为什么又 ...
- PriorityBlockingQueue
public class PriorityBlockingQueueTest { /** * 有优先级顺序的阻塞队列,底层实现是数组,无边界.默认是11. * 构造方法可以传入一个比较器,不传的话,默 ...
- elementui中的el-table中拼接两个列表字段
我们知道,在ElementUI中我们是使用下面的语法来展示列表字段的: <el-table :data="yanggbs" stripe style="width: ...
- efcore dotnet cli add-migrations update-database
add-migrations update-database 如何通过dotnet cli调用 dotnet tool install --global dotnet-ef dotnet ef mig ...
- C# read dll config
public static SqlConnection GetSqlConnection() { Configuration myDllConfig = ConfigurationManager.Op ...
- ArchLinux 2019.11.01安装流程--安装基本系统
安装前的一些话 本文是参考官方文档ArchLinux的Installation guide(简体中文)加实际操作编写的. 有啥都好说,转载时请注明作者,这是基本素质,也是法律要求 安装是在虚拟机上进行 ...
- Winform中在ZedGraph中最多可以添加多少条曲线
场景 Winforn中设置ZedGraph曲线图的属性.坐标轴属性.刻度属性: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/10 ...
- 使用android日志工具
Log的级别? 日志级别按照高低排序为:ERROR,WARN,INFO,DEBUG,VERBOSE, 日志输出: Log.e()输出ERROR级别的日志信息 Log.w()输出WARN,ERROR级别 ...
- 剑指offer 15:链表的倒数第k个节点
题目描述 输入一个链表,输出该链表中倒数第k个结点. 解题思路 使用快慢指针法,让快指针先走k步,然后再让慢指针开始走,当快指针到达链表尾部时,慢指针刚好到达倒数第k个节点. C++代码实现: /* ...
- 删除带外键的表【foreign key constraint fails】报错
title: 删除带外键的表[foreign key constraint fails]报错 date: 2018-08-02 21:59:06 tags: 数据库 --- 遥想当时正在学hibern ...