****vue源码学习路径**** vue-js.com/learn-vue/s…
1、什么是MVVM框架?它适用于哪些场景?
(1)mvvm框架是什么?
MVVM是Model-View-ViewModel的简写
Model:模型
View:视图
ViewModel:视图模型,连接view和model的桥梁
通常要实现一个observer观察者,当数据发生变化,ViewModel能够监听到数据的这种变化,然后通知到对应的视图做自动更新,而当用户操作视图,ViewModel 也能监听到视图的变化,然后通知数据做改动,这实际上就实现了数据的双向绑定。
(2)它和其它框架(jquery)的区别是什么?
概念上:vue:前端js库,是一个精简的MVVM,它专注于MVVM模型的viewModel层,通过双向数据绑定把view和model层连接起来,通过对数据的操作就可以完成对页面视图的渲染; jquery:轻量级的js库,
在操作思想上: vue是使用数据驱动的方式,通过vue对象将数据和view完全分离开,对数据操 作,不在引用相应的DOM对象,通过vue对象,将数据和相应的DOM对象相互绑定起 来;主要是操作数据基于一种MVVM模式,jQuery是使用选择器($)选取DOM对象,并对其进行赋值、取值、事件绑定等 操作,主要是操作DOM
(3)哪些场景适合?
应用场景的区别:
vue适用的场景:复杂数据操作的后台页面,表单填写页面;
jquery适用的场景:比如说一些html5的动画页面,一些需要js来操作页面样式的页面。
二者也是可以结合起来一起使用的,vue侧重数据绑定,jquery侧重样式操作, 动画效果等,则会更加高效率的完成业务
2、Vue的响应式原理(MVVM)深入解析
3、vue 源码解读。(非常重要)
4、Vue源码解读之Dep,Observer和Watcher
5、vue原理、vnode-源码版
1、VNode是什么及作用?
VNode 表示 虚拟节点 Virtual DOM,为什么叫虚拟节点呢,因为不是真的 DOM 节点。
他只是用 javascript 对象来描述真实 DOM,这么描述,把DOM标签,属性,内容都变成 对象的属性
就像用 JavaScript 对象描述一个人一样
{sex:'男', name:'神仙朱', salary:5000,children:null}
过程就是,把你的 template 模板 描述成 VNode,然后一系列操作之后通过 VNode 形成真实DOM进行挂载
a、VNode是什么
JavaScript 对象
b、VNode有什么作用?
1、兼容性强,不受执行环境的影响。VNode 因为是 JS 对象,不管 Node 还是 浏览器,都可以统一操作, 从而获得了服务端渲染、原生渲染、手写渲染函数等能力
2、减少操作 DOM。任何页面的变化,都只使用 VNode 进行操作对比,只需要在最后一步挂载更新DOM,不需要频繁操作DOM,从而提高页面性能
6、VNode怎么生成
在 Vue 源码中,vnode 是通过一个构造函数生成的,构造函数看起来挺简单的
本来以为很多内容,带着沉重的心情去探索,然后看到之后就放松了下来,看了一会,心情再次沉重了起来
其中涉及的内容还是挺多的....不然哪里来开篇的那么多问题
行了,看下 VNode 的构造函数
function VNode(
tag, data, children,
text, elm, context,
componentOptions
) {
this.tag = tag; // 标签名
this.data = data;
this.children = children; // 子元素
this.text = text; // 文本内容
this.elm = elm; // Dom 节点
this.context = context;
this.componentOptions = componentOptions;
this.componentInstance = undefined;
this.parent = undefined;
this.isStatic = false; // 是否静态节点
this.isComment = false; // 是否是注释节点
this.isCloned = false; // 是否克隆节点
};
看完上面,先不要纠结都是什么东西,先来走一遍
比如我们使用 vnode 去描述这样一个template
<div class="parent" style="height:0" href="2222">
111111
</div>
使用 VNode 构造函数就可以生成下面的 VNode
{
tag: 'div',
data: {
attrs:{href:"2222"}
staticClass: "parent",
staticStyle: {
height: "0"
}
},
children: [{
tag: undefined,
text: "111111"
}]
}
这个 JS 对象,就已经囊括了整个模板的所有信息,完全可以根据这个对象来构造真实DOM了
至于其中都是什么意思,请看下个问题
7、VNode存放什么信息
新建一个 vnode 的时候,包含了非常多的属性,每个属性都是节点的描述的一部分
我们只捡一些属性来探索一下,了解主体即可
普通属性
1、data
1、存储节点的属性,class,style 等
2、存储绑定的事件
3、....其他
2、elm
真实DOM 节点
生成VNode 的时候,并不存在真实 DOM
elm 会在需要创建DOM 时完成赋值,具体函数在 createElm 中
赋值语句就是一句(简化了源码)
vnode.elm = document.createElement(tag)
3、context
渲染这个模板的上下文对象
意思就是,template 里面的动态数据要从这个 context 中获取,而 context 就是 Vue 实例
如果是页面,那么context 就是本页面的实例,如果是组件,context则是组件的实例
4 isStatic
是否是静态节点
当一个节点被标记为静态节点的时候,说明这个节点可以不用去更新它了,当数据变化的时候,可以忽略去比对他,以提高比对效率
组件相关属性
1、parent
这个parent 表示是组件的外壳节点
额,什么是外壳节点,举个栗子先吧
1、存在这样一个组件 test
2、页面中使用这个组件
诶,到这里就有意思了,组件其实应有两种 VNode
这两种VNode 名义上都是对的,都有理,谁是正牌不好说
最后尤大判定第一个 VNode 是 第二个 VNode 的爸爸,也就是外壳节点
外壳节点通常是 父组件和 子组件的 关联,用于保存一些父组件传给子组件的数据 等
2 componentInstance
这个顾名思义,就是组件生成的实例,保存在这里
上面 test 组件的外壳节点中的 componentInstance
3 componentOptions
这个就存储一些 父子组件 PY 交易的证据
比如 props,事件,slot 什么的,打印看下
其中 children 保存的就是 slot,listeners 保存 事件,propsData 保存 props
8、VNode怎么生成
在初始化完选项,解析完模板之后,就需要挂载 DOM了。此时就需要生成 VNode,才能根据 VNode 生成 DOM 然后挂载
挂载 DOM 第一步,就是先执行渲染函数,得到整个模板的 VNode
比如有以下渲染函数,执行会返回 VNode,就是 _c 返回的VNode
function (){
with(this){
return _c('div',{attrs:{"href":"xxxx"}},["1111"]).
}
}
渲染函数会绑定上下文对象,加上 with 的作用,_c 其实就是 vm._c
现在就来看 vm._c 是什么东西
vm._c = function(a, b, c, d) {
return createElement(vm, a, b, c, d, false);
};
function createElement(
context, tag, data,
children, normalizationType
) {
var vnode;
if (tag是正常html标签) {
vnode = new VNode(
tag, data, children, undefined,
undefined, context
);
}
else if (tag 是组件) {
vnode = createComponent(
Ctor, data, context,
children, tag
);
}
return vnode
}
我们可以看到,正常标签 和 组件会走不同流程
1 、正常标签
比如有这样一个正常标签模板
<div href="xxxxx">1111</div>
解析成渲染函数如下
function (){
with(this){
return _c('div',{
attrs:{"href":"xxxx"}},
["1111"]
)
}
}
看上面_c 源码,可以知道经过 _c 把参数传导,这样去构建 VNode
new VNode(tag, data, children, undefined, undefined, context);
这样就保存了 tag,data,children 和 context
2、组件
比如页面使用了test组件
<div> <test :name="name">1111</test> </div>
解析成渲染函数如下
with(this){
return _c('div',[
_c('test',
{attrs:{"name":name}},
["1111"]
)
],1)
}
看上面 _c 代码知道 ,_c 会先调用 createComponent
createComponent(Ctor, data, context, children, tag);}
createComponent 中也会调用 VNode 构造函数,生成VNode 并返回
function createComponent(
Ctor, data, context,
children, tag
) {
// extractPropsFromVNodeData 作用是把传入data的 attr 中属于 props的筛选出来
var propsData = extractPropsFromVNodeData(data, Ctor, tag);
var vnode = new VNode(
("vue-component-" + (Ctor.cid) + tag),
data, undefined, undefined, undefined,
context, {
Ctor: Ctor,
// 父组件给子组件绑定的props
propsData: propsData,
// 父组件给子组件绑定的事件
listeners: listeners,
tag: tag,
children: children
});
return vnode
}
9、VNode存放在哪里
主要是三个位置存了 vnode,分别是
parent ,_vnode ,$vnode
parent 上面已经说过,就先不提了,剩下两个全部是挂在 Vue 实例一级属性上的
打印一下组件的实例,可以很清楚看到这两个属性
下面来说说这两个东西
1、_vnode
_vnode 存放表示当前节点的 VNode
什么叫当前,也就是可以通过这个VNode 直接映射成 当前真实DOM
他的作用是什么呢?
用来比对更新,比如你的数据变化了,此时会生成一个新的 VNode,然后再拿到保存的_vnode 对比,就可以得到最小区域,从而只用更新这部分
所以, _vnode 存放的可以说是当前节点,也可以说是旧节点
另外,_vnode 中保存有一个 parent,这个parent 就是外壳节点,上面说 vnode 的时候已经说过了
在哪里赋值?
我们来完整地走一遍流程,涉及源码很多,但是我已经非常精简了,大概了解个流程
function Vue() {
...初始化组件选项等
mountComponent()
}
function mountComponent() {
....解析模板,生成渲染函数
// 用于生成VNode,生成DOM,挂载DOM
updateComponent = function() {
vm._update(vm._render());
};
// 新建 watcher,保存updateComponent为更新函数,新建的时候会立即执行一遍
new Watcher(vm, updateComponent)
}
function Watcher(vm, expOrFn) {
this.getter = expOrFn ;
this.getter()
}
// 执行前面解析得到的渲染函数,返回生成的 VNode
Vue.prototype._render = () {}
// 根据vnode,生成DOM 挂载
Vue.prototype._update = function(vnode) {
var prevVnode = vm._vnode;
vm._vnode = vnode;
if (不存在旧节点) { ...使用vnode创建DOM并直接挂载 }
else { ...存在旧节点,开始比对旧节点和新节点,然后创建DOM并挂载 }
}
2、$vnode
vnode 存放的是外壳节点,页面实例中是不存在 $vnode 的
本来也想走下流程的,无奈兜兜转转太多,涉及源码更多
在哪里进行赋值?
我就放最后一步 updateChildComponent
updateChildComponent 会在上个 _vnode 提到的 vm._update 执行流程中调用
function updateChildComponent(
vm, parentVnode
) {
vm.$options._parentVnode = parentVnode;
vm.$vnode = parentVnode;
if (vm._vnode) {
vm._vnode.parent = parentVnode;
}
}
10、vue源码-nextTick
a、什么是vue.nextTick();
官方文档解释为:在下次DOM更新循环结束之后执行的延迟回调。在修改数据之后立即使用该方法,获取更新后的DOM。
我们也可以简单的理解为:当页面中的数据发生改变了,就会把该任务放到一个异步队列中,只有在当前任务空闲时才会进行DOM渲染,当DOM渲染完成以后,该函数就会自动执行。
b、2.1 更改数据后,进行节点DOM操作。
比如修改数据、修改节点样式、等操作。比如说我修改data中的一个属性数据后,如果我这个时候直接获取该html内容的话,它还是老数据的,那么此时此刻,我们可以使用 Vue.nextTick(), 在该函数内部获取该数据即可: 如下代码:
<!DOCTYPE html>
<html>
<head>
<title>vue.nextTick()方法的使用</title>
<meta charset="utf-8">
<script type="text/javascript" src="https://cn.vuejs.org/js/vue.js"></script>
</head>
<body>
<div id="app">
<template>
<div ref="list">{{name}}</div>
</template>
</div>
<script type="text/javascript">
new Vue({
el: '#app',
data: {
name: 'kongzhi111'
},
mounted() {
this.updateData();
},
methods: {
updateData() {
this.name = 'kongzhi222';
console.log(this.$refs.list.textContent); // 打印 kongzhi111
this.$nextTick(() => {
console.log('-------');
console.log(this.$refs.list.textContent); // 打印 kongzhi222
});
}
}
})
</script>
</body>
</html>
如上代码,页面初始化时候,页面显示的是 "kongzhi111"; 当页面中的所有的DOM更新完成后,我在mounted()生命周期中调用 updateData()方法,然后在该方法内部修改 this.name 这个数据,再打印 this.𝑟𝑒𝑓𝑠.𝑙𝑖𝑠𝑡.𝑡𝑒𝑥𝑡𝐶𝑜𝑛𝑡𝑒𝑛𝑡,可以看到打印的数据还是′𝑘𝑜𝑛𝑔𝑧ℎ𝑖111′;为什么会是这样呢?那是因为修改𝑛𝑎𝑚𝑒数据后,我们的𝐷𝑂𝑀还没有被渲染完成,所以我们这个时候获取的值还是之前的值,但是我们放在𝑛𝑒𝑥𝑡𝑇𝑖𝑐𝑘函数里面的时候,代码会在𝐷𝑂𝑀更新完成后会自动执行𝑛𝑒𝑥𝑡𝑇𝑖𝑐𝑘()函数,因此这个时候我们再去使用𝑡ℎ𝑖𝑠.refs.list.textContent 获取该值的时候,就可以获取到最新值了。
c、2.2 在created生命周期中进行DOM操作。
在Vue生命周期中,只有在mounted生命周期中我们的HTML才渲染完成,因此在该生命周期中,我们就可以获取到页面中的html DOM节点,但是如果我们在 created生命周期中是访问不到DOM节点的。
在该生命周期中我们想要获取DOM节点的话,我们需要使用 this.$nextTick() 函数。
比如如下代码进行演示:
<!DOCTYPE html>
<html>
<head>
<title>vue.nextTick()方法的使用</title>
<meta charset="utf-8">
<script type="text/javascript" src="https://cn.vuejs.org/js/vue.js"></script>
</head>
<body>
<div id="app">
<template>
<div ref="list">{{name}}</div>
</template>
</div>
<script type="text/javascript">
new Vue({
el: '#app',
data: {
name: 'kongzhi111'
},
created() {
console.log(this.$refs.list); // 打印undefined
this.$nextTick(() => {
console.log(this.$refs.list); // 打印出 "<div>kongzhi111</div>"
});
},
methods: {
}
})
</script>
</body>
</html>
如上代码,在created生命周期内,我们打印 this.𝑟𝑒𝑓𝑠.𝑙𝑖𝑠𝑡值为𝑢𝑛𝑑𝑒𝑓𝑖𝑛𝑒𝑑,那是因为在𝑐𝑟𝑒𝑎𝑡𝑒𝑑生命周期内页面的ℎ𝑡𝑚𝑙没有被渲染完成,因此打印出为𝑢𝑛𝑑𝑒𝑓𝑖𝑛𝑒𝑑;但是我们把它放入𝑡ℎ𝑖𝑠.nextTick函数内即可 打印出值出来,这也印证了 nextTick 是在下次DOM更新循环结束之后执行的延迟回调。因此只有DOM渲染完成后才会自动执行的延迟回调函数。
Vue的特点之一就是能实现响应式,但数据更新时,DOM不会立即更新,而是放入一个异步队列中,因此如果在我们的业务场景中,需要在DOM更新之后执行一段代码时,这个时候我们可以使用 this.$nextTick() 函数来实现。
d、 Vue.nextTick的调用方式如下:
Vue.nextTick([callback, context]) 和 vm.$nextTick([callback]);
Vue.nextTick([callback, context]); 该方法是全局方法,该方法可接收2个参数,分别为回调函数 和 执行回调函数的上下文环境。
vm.$nextTick([callback]): 该方法是实列方法,执行时自动绑定this到当前的实列上。
e、vm.$nextTick 与 setTimeout 的区别是什么?
<!DOCTYPE html>
<html>
<head>
<title>vue.nextTick()方法的使用</title>
<meta charset="utf-8">
<script type="text/javascript" src="https://cn.vuejs.org/js/vue.js"></script>
</head>
<body>
<div id="app">
<template>
<div ref="list">{{name}}</div>
</template>
</div>
<script type="text/javascript">
new Vue({
el: '#app',
data: {
name: 'kongzhi111'
},
created() {
console.log(this.$refs.list); // 打印undefined
setTimeout(() => {
console.log(this.$refs.list); // 打印出 "<div>kongzhi111</div>"
}, 0);
}
})
</script>
</body>
</html>
如上代码,我们不使用 nextTick, 我们使用setTimeout延迟也一样可以获取页面中的HTML元素的,那么他们俩之间到底有什么区别呢?
通过看vue源码我们知道,nextTick 源码在 src/core/util/next-tick.js 里面。在vue中使用了三种情况来延迟调用该函数,首先我们会判断我们的设备是否支持Promise对象,如果支持的话,会使用 Promise.then 来做延迟调用函数。如果设备不支持Promise对象,再判断是否支持 MutationObserver 对象,如果支持该对象,就使用MutationObserver来做延迟,最后如果上面两种都不支持的话,我们会使用 setTimeout(() => {}, 0); setTimeout 来做延迟操作。
在比较 nextTick 与 setTimeout 的区别,其实我们可以比较 promise 或 MutationObserver 对象 与 setTimeout的区别的了,因为nextTick会先判断设备是否支持promise及MutationObserver 对象的,只要我们弄懂 promise 和 setTimeout的区别,也就弄明白 nextTick 与 setTimeout的区别了。
在比较promise与setTimeout之前,我们先来看如下demo。
<!DOCTYPE html>
<html>
<head>
<title></title>
<meta charset="utf-8">
</head>
<body>
<script type="text/javascript">
console.log(1);
setTimeout(function(){
console.log(2);
}, 0);
new Promise(function(resolve) {
console.log(3);
for (var i = 0; i < 100; i++) {
i === 99 && resolve();
}
console.log(4);
}).then(function() {
console.log(5);
});
console.log(6);
</script>
</body>
</html>
如上代码输出的结果是:1, 3, 4, 6, 5, 2; 首先打印1,这个我们能理解的,其实为什么打印3,在promise内部也属于同步的,只有在then内是异步的,因此打印 1, 3, 4 , 然后执行then函数是异步的,因此打印6. 那么结果为什么是 1, 3, 4, 6, 5, 2 呢? 为什么不是 1, 3, 4, 6, 2, 5呢?
我们都知道 Promise.then 和 setTimeout 都是异步的,那么在事件队列中Promise.then的事件应该是在setTimeout的后面的,那么为什么Promise.then比setTimeout函数先执行呢?
理解Event Loop 的概念
我们都明白,javascript是单线程的,所有的任务都会在主线程中执行的,当主线程中的任务都执行完成之后,系统会 "依次" 读取任务队列里面的事件,因此对应的异步任务进入主线程,开始执行。
但是异步任务队列又分为: macrotasks(宏任务) 和 microtasks(微任务)。 他们两者分别有如下API:
macrotasks(宏任务): setTimeout、setInterval、setImmediate、I/O、UI rendering 等。
microtasks(微任务): Promise、process.nextTick、MutationObserver 等。
如上我们的promise的then方法的函数会被推入到 microtasks(微任务) 队列中,而setTimeout函数会被推入到 macrotasks(宏任务) 任务队列中,在每一次事件循环中 macrotasks(宏任务) 只会提取一个执行,而 microtasks(微任务) 会一直提取,直到 microtasks(微任务)队列为空为止。
也就是说,如果某个 microtasks(微任务) 被推入到执行中,那么当主线程任务执行完成后,会循环调用该队列任务中的下一个任务来执行,直到该任务队列到最后一个任务为止。而事件循环每次只会入栈一个 macrotasks(宏任务), 主线程执行完成该任务后又会循环检查 microtasks(微任务) 队列是否还有未执行的,直到所有的执行完成后,再执行 macrotasks(宏任务)。 依次循环,直到所有的异步任务完成为止。
有了上面 macrotasks(宏任务) 和 microtasks(微任务) 概念后,我们再来理解上面的代码,上面所有的代码都写在script标签中,那么读取script标签中的所有代码,它就是第一个宏任务,因此我们就开始执行第一个宏任务。因此首先打印 1, 然后代码往下读取,我们遇到setTimeout, 它就是第二个宏任务,会将它推入到 macrotasks(宏任务) 事件队列里面排队。
下面我们继续往下读取,
遇到Promise对象,在Promise内部执行它是同步的,因此会打印3, 4。 然后继续遇到 Promise.then 回调函数,他是一个 microtasks(微任务)的,因此将他 推入到 microtasks(微任务) 事件队列中,最后代码执行 console.log(6); 因此打印6. 第一个macrotasks(宏任务)执行完成后,然后我们会依次循环执行 microtasks(微任务), 直到最后一个为止,因此我们就执行 promise.then() 异步回调中的代码,因此打印5,那么此时此刻第一个 macrotasks(宏任务) 执行完毕,会执行下一个 macrotasks(宏任务)任务。因此就执行到 setTimeout函数了,最后就打印2。到此,所有的任务都执行完毕。因此我们最后的结果为:1, 3, 4, 6, 5, 2;
我们可以继续多添加几个setTimeout函数和多加几个Promise对象来验证下,如下代码:
<script type="text/javascript">
console.log(1);
setTimeout(function(){
console.log(2);
}, 10);
new Promise(function(resolve) {
console.log(3);
for (var i = 0; i < 10000; i++) {
i === 9999 && resolve();
}
console.log(4);
}).then(function() {
console.log(5);
});
setTimeout(function(){
console.log(7);
},1);
new Promise(function(resolve) {
console.log(8);
resolve();
}).then(function(){
console.log(9);
});
console.log(6);
</script>
如上打印的结果为: 1, 3, 4, 8, 6, 5, 9, 7, 2;
首先打印1,这是没有任何争议的哦,promise内部也是同步代码,因此打印 3, 4, 然后就是第二个promise内部代码,因此打印8,再打印外面的代码,就是6。因此主线程执行完成后,打印的结果分别为:
1, 3, 4, 8, 6。 然后再执行 promise.then() 回调的 microtasks(微任务)。因此打印 5, 9。因此microtasks(微任务)执行完成后,就执行第二个宏任务setTimeout,由于第一个setTimeout是10毫秒后执行,第二个setTimeout是1毫秒后执行,因此1毫秒的优先级大于10毫秒的优先级,因此最后分别打印 7, 2 了。因此打印的结果是: 1, 3, 4, 8, 6, 5, 9, 7, 2;
总结: 如上我们也看到 microtasks(微任务) 包括 Promise 和 MutationObserver, 因此 我们可以知道在Vue中的nextTick 的执行速度上是快于setTimeout的。
我们从如下demo也可以得到验证:
<!DOCTYPE html>
<html>
<head>
<title>vue.nextTick()方法的使用</title>
<meta charset="utf-8">
<script type="text/javascript" src="https://cn.vuejs.org/js/vue.js"></script>
</head>
<body>
<div id="app">
<template>
<div ref="list">{{name}}</div>
</template>
</div>
<script type="text/javascript">
new Vue({
el: '#app',
data: {
name: 'kongzhi111'
},
created() {
console.log(this.$refs.list); // 打印undefined
setTimeout(() => {
console.log(this.$refs.list); // 打印出 "<div>kongzhi111</div>"
}, 0);
this.$nextTick(function(){
console.log('nextTick比setTimeout先执行');
});
}
})
</script>
</body>
</html>
如上代码,先打印的是 undefiend, 其次是打印 "nextTick比setTimeout先执行" 信息, 最后打印出 "
f、理解 MutationObserver
g、nextTick源码分析
7、详解vue的diff算法
8、
Vue 的核心思想:组件化。 这一部分是关于构建组件树,形成虚拟 dom ,以及非常重要的 patch 方法。
再来亿遍:
a、 原因:当修改某条数据的时候,这时候js会将整个DOM Tree进行替换,这种操作是相当消耗性能的。所以在Vue中引入了Vnode的概念:Vnode是对真实DOM节点的模拟,可以对Vnode Tree进行增加节点、删除节点和修改节点操作。这些过程都只需要操作VNode Tree,不需要操作真实的DOM,大大的提升了性能。修改之后使用diff算法计算出修改的最小单位,在将这些小单位的视图进行更新。
b、原理:data中定义了一个变量a,并且模板中也使用了它,那么这里生成的Watcher就会加入到a的订阅者列表中。当a发生改变时,对应的订阅者收到变动信息,这时候就会触发Watcher的update方法,实际update最后调用的就是在这里声明的updateComponent。 当数据发生改变时会触发回调函数updateComponent,updateComponent是对patch过程的封装。patch的本质是将新旧vnode进行比较,创建、删除或者更新DOM节点/组件实例。
9、Vue 源码patch过程详解
10、Q: vue自带的内置组件有什么?A: Vue中内置的组件有以下几种:
a、component
component组件:有两个属性—is inline-template 渲染一个‘元组件’为动态组件,按照’is’特性的值来渲染成那个组件
b、 transition
transition组件:为组件的载入和切换提供动画效果,具有非常强的可定制性,支持16个属性和12个事件
c、 transition-group
transition-group:作为多个元素/组件的过渡效果
d、 keep-alive
keep-alive:包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们
e、slot
slot:作为组件模板之中的内容分发插槽,slot元素自身将被替换
10、vue的编译过程