一. Vue基础
1. Composition API学习
1) reactive ref
reactive
使用代理Proxy使普通对象(Object)和内置对象(Map,Set)响应式;
reactive
出的是一个深度响应的Proxy
对象;它所有的属性都是响应式的;但一旦被取出就不是响应式的了const state = reactive({
foo: {
count: 1
},
bar:2
});
state.foo.count++; // 响应式的
const b = state.bar;
b++; // 不是响应式的了
reactive
出的Proxy
对象,值为对象的属性也是一个Proxy
对象;具有响应式 ```javascript const proxy = reactive({foo: { bar: 1}}) console.log(proxy.foo) // proxy类型的对象 console.log(proxy.foo.bar === 1) // true
const raw = {} proxy.nested = raw console.log(proxy.nested === raw) // false
`ref`包装一个值,使之成为响应式的
1. `ref`包装一个原始值,变成一个对象;value指向原始值,value属性具有响应式;
```javascript
const count = ref(1)
count.value++ // 响应式的
ref
包装一个对象,对象变成Proxy
类型,value指向这个Proxy
对象;value指向的Proxy
对象具有深度响应const count = ref({
foo: {
bar: 1
}
})
count.value.foo // Proxy对象
Ref
类型的值在<template>
中自动unwrap,即可以不用value获取它的原始值;任何地方都可以自动解包装 ```javascript
4. `Ref`类型在`reactive`对象中也自动解包装
javascript
const count = ref(2)
const state = reactive({ count })
console.log(state.count) // 2
5. `Ref`类型在数组Array和集合Collections(Map,Set)中不会自动解包装
javascript
const count = ref(2)
const state = reactive([ count ])
console.log(state[0].value)
<a name="lwZ02"></a>
#### 2. v指令
1. `v-bind`:绑定响应式数据
1. `v-on`:绑定方法
1. `v-model`:input标签中`:value`和`@input`的缩写
html
// 等价
4. `v-if v-else-if v-else`:判断是否渲染元素;如果为false元素不会添加到DOM中
4. `v-for`:循环,最好加上`:key`
<a name="uBxn0"></a>
#### 3. 计算属性computed
计算属性会收集函数中的依赖;当依赖改变的时候触发回调函数
<a name="OCvgn"></a>
#### 4. 声明周期钩子
![](https://cdn.nlark.com/yuque/0/2022/png/23168078/1648625111815-22b0eb5c-9833-4627-bed0-bd800a3fbcd8.png#clientId=ud3b729da-9fd6-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=1001&id=u5048856c&margin=%5Bobject%20Object%5D&originHeight=2002&originWidth=1266&originalType=url&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=ub07b55f3-c3f0-46c1-9734-51c3c8fdc5f&title=&width=633)
<a name="Gu3Cv"></a>
#### 5. 监听器watch
响应式数据发生变化时触发的回调
<a name="Iia9K"></a>
#### 6. 父组件向子组件传递数据props
Composition API提供了运行时的宏命令`defineProps`
<a name="xdHVn"></a>
#### 7. 子组件向父组件传递数据events
Composition API提供了运行时的宏命令`defineEmits`声明自定义事件
javascript
const emit = defineEmits([‘response’]);
emit(‘response’, ‘hi’);
<a name="ycIRL"></a>
#### 8. 插槽slot
插槽会替换`<slot>`标签内的内容,显示传递进来的元素
html
// 父元素
// 子元素
<a name="hHJFF"></a>
### 二. 进阶
<a name="JQFVm"></a>
#### 1. 理解MVC和MVVM
<a name="TtSp7"></a>
##### 1) MVC
MVC将应用程序分为三个角色:Model,View和Controller;<br />Model也就是模型用于处理应用程序**数据逻辑**部分,通常模型对象负责存取数据。<br />View也也就是视图负责处理**数据显示**的部分,视图通常依据模型对象创建。<br />Controller也就是控制器负责处理**用户交互**的部分;控制器通常从视图读取数据,控制用户输入并向模型发送数据。
Controller和View之间使用策略模式;用户操纵View生成一个事件(如按钮点击事件);Controller对象接收并解释事件,即应用策略模式实现不同的响应
View和Model之间使用观察者模式;View注册为Model的观察者,当Model发生变化时就能通知到View<br />![](https://cdn.nlark.com/yuque/0/2022/png/23168078/1648696723144-54b1d1ed-814c-4d96-a8e1-a6ab46676f4b.png#clientId=uecc2ceec-81d2-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u4d5d14b1&margin=%5Bobject%20Object%5D&originHeight=365&originWidth=601&originalType=url&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=uf2283826-9a87-46e4-bf35-6bf4d14bac1&title=)<br />参考:[浅谈 MVC 和 MVVM 模型](https://segmentfault.com/a/1190000020969313)
<a name="bpC7e"></a>
##### 2) MVVM
MVVM指Model,View和ViewModel<br />Model指用于处理数据到模型;<br />View指视图;<br />ViewModel是连接View和Model的桥梁,他有两个方向:
1. 将模型Model转换为视图View;即将后端传递的数据转换成看到的页面,通过**数据绑定**实现
1. 将视图View转化为模型Model;即将页面转化为后端的数据,通过**DOM事件监听**实现。
两个方向都是实现了可以称之为数据的双向绑定<br />参考:[Vue 的 MVVM 思想](https://juejin.cn/post/6879300070962003982)
<a name="a2prE"></a>
#### 2. date为什么是函数
为了防止组件复用时data共享的问题。
- data是组件原型上的一个属性`MyComponent.prototype.data`;
- 如果data是对象,所有的组件实例共享data对象;
- 如果data是函数返回一个对象,那么每次创建组件实例data方法会返回一个新的对象。
<a name="xTCrE"></a>
#### 3. 组件间通信的方式
1. `props和emits`。父组件通过props向子组件传递数据;子组件通过emits注册事件,父组件监听该事件完成传值/ 通信
在Options API中是`props emits`;在Composition API中是`defineProps defineEmits`;在组件实例上是`$props $emit()`
```html
<ChildComp :message="msg" @response="getResponse" />
$parent和$children,$refs
,直接获取组件的父组件实例,子组件实例,或某个组件的实例;vue3没有提供$children
<input ref="input1">
<script>
...
this.$refs.input1
</script>
provide inject
,通过依赖注入的方式可以向子孙组件传值,Options API和Composition API方法名相同 ```javascript // 父组件setup中 import { provide } from ‘vue’; provide(‘a’, 1);
// 子孙组件中,多种形式 inject: { b: { from: ‘a’ } }, inject: [‘a’]
4. `$attrs`,fallthrough属性,父组件传递给子组件,但是子组件没有在props或emits上声明的属性
vue2中使用`$attrs`和`$listeners`实现多层嵌套传值
```javascript
// 父组件中
<HelloWorld class="red" />
// 子组件中
console.log('this.$attrs :>> ', this.$attrs);
// this.$attrs :>>
// Proxy {class: 'red', __vInternal: 1}
// ...
// [[Target]]: Object
// class: "red"
// ...
- vuex状态管理
vue可以使用vuex集中状态管理,下面是vue2中使用vue3的教程
安装vuex
npm i -S vuex@3
创建
src/store/index.js
文件,在里面创建vuex插件,并导出Vuex.Store实例store。
state
用于存储状态,对应的辅助函数mapState,可以放在computed中getters
是修饰器,辅助函数是mapGetters,可以放在computed中mutations
用于修改状态,使用commit
触发,必须是同步函数,辅助函数是mapMutations,可以放在methods中actions
提交mutations,可以是异步函数;使用dispatch
触发,辅助函数是mapActions,可以放在methods中
import Vue from 'vue';
import Vuex from 'vuex';
// Vue使用Vuex插件
Vue.use(Vuex);
const store = new Vue.Store({
state: {
number: 1
},
getters: {
getHelloNumber(state) {
return `hello ${state.number}`;
}
},
mutations: {
setNumber(state, payload) {
state.number = payload.number;
}
},
actions: {
setNumberAsync(content, payload) {
return new Promise((resolve) => {
setTimeout(() => {
content.commit('setNumber', payload);
resolve();
}, 1500)
})
}
}
});
export default store;
在
main.js
导入store,并在new Vue
的时候加入store,以便能全局使用store,this.$store.state
等import store from '@/store/index.js';
new Vue({
store // 可以全局this.$store使用vuex
}).mount('#app');
vuex在组件中的用法
// 普通的使用方法
this.$store.state.number
this.$store.getters.getHelloNumber
this.$store.commit('setNumber', {number: 2}) // 后面只允许一个参数,建议用对象payload
this.$store.dispatch('setNumberAsync', {number: 3}) //后面只允许一个参数
modules
可以将一个store分割成多个模块 ```javascript const moduleA = {store…}; const moduleB = {store…}; modules: { a: moduleA, b: moduleB }
// 除了获取state外,其他使用方法不变,不需要加模块名 this.$store.state.a.count; this.$store.getters.getHelloNumber;
参考:[手把手教你使用Vuex](https://juejin.cn/post/6928468842377117709)
6. EventBus事件总线
可以创建一个新的Vue实例作为事件总线,在上面注册和监听事件;有两种创建EventBus的方法:作为Vue原型链上的属性或作为一个模块。<br />事件总线的缺点:
1. 无法确定由谁触发事件,导致混乱
1. 某个页面刷新后可能导致与之相关的EventBus被移除,其他组件无法监听到事件
1. 重复操作某个页面可能导致EventBus重复触发
```javascript
// 第一种方式 main.js中
Vue.prototyep.$EventBus = new Vue();
// 第二种方式 EventBus.js中
import Vue from 'vue';
export default new Vue();
// 注册事件
this.$EventBus.$emit('hi', 1);
import EventBus from './EventBus';
EventBus.$emit('hello', 2);
// 监听事件
this.$EventBus.$on('hi', handler);
import EventBus from './EventBus';
EventBus.$on('hello', handler);
4. Vue3的生命周期
vue生命周期是组件从创建到销毁的过程;在这一过程中Vue提供一些生命周期钩子函数,让开发者在组件不同的阶段添加自己的代码逻辑。
Vue2和Vue3 Options API的生命周期钩子的名称基本相同,除了destory -> unMount
Composition API使用setup
替代了beforeCreated
和create
vue3生命周期Options API调用源码位置
beforeCreate
在实例初始化后,数据观测(data observer)和watch/event事件配置之前被调用。此时data,methods,watch,computed上的数据和方法都访问不到created
在实例创建完成后调用,完成以下配置:inject,methods,data,computed,watch,provide选项函数的解析。此时组件的属性和方法已经可以访问。但是DOM还不可以。可以使用this.$nextTick
回调beforeMount
在render函数首次被调用,DOM被挂载前被调用。服务端渲染期间不被调用mounted
在挂载完成后发生,真实DOM完成挂载,数据完成双向绑定。beforeUpdate
在响应式属性更新,DOM被更新前触发。在这个钩子中进一步更改状态不会触发附加的重新渲染updated
DOM更新完成。应该避免在这里修改响应式数据,可能导致无限循环更新。服务端渲染期间不被调用beforeUnmount/beforeDestory
卸载组件实例前被调用;实例仍然可用,可以在此期间清除定时器等unmounted/destoryed
卸载组件实例后被调用actived
keep-alive专属,在组件被激活时调用deactived
keep-alive专属,在组件被未被激活时调用
问题:异步请求在哪里调用?
答:可以在created,beforeMount和mounted内进行。这是data被初始化完成,方法也可以调用
参考:Vue3生命周期详解
Vue 的生命周期之间到底做了什么事清?(源码详解,带你从头梳理组件化流程)
5. v-if和v-for的区别
- v-if是会被编译成三元表达式,条件不满足组件不会渲染
v-show会被编译成指令,条件不满足元素的display:none
- v-if条件转变元素会在真实DOM中插入和移除,适合不需要频繁切换的场景
v-show用
display
控制元素显隐,适合频繁切换的场景6. 如何理解vue单向数据流
所有的props形成的父子props单行向下流动;父组件props的更新会向下流动到子组件中,但是子组件不行,这样防止子组件意外更新父级组件状态,导致数据流向的困惑。
在vue中子组件修改props会收到警告;子组件一般通过事件响应的机制和父组件沟通。7. watch和computed的区别
computed是计算属性,依赖其他响应式数据更新值;computed是有缓存的;只有当其他响应式数据发生变化的时候才会更新。
watch监听到响应式数据的值发生变化就会去执行对应的回调。
- computed适合用于渲染模板中;watch适合在数据发生改变时执行相应的业务逻辑
8. v-for和v-if优先级问题
vue2中v-for优先于v-if执行,会先循环渲染出节点后再用v-if判断,造成性能浪费;可以使用computed优化
vue3中v-if的优先级高于v-for;所以v-if不能访问v-for作用域中的值,会报错(访问不到todo);可以用template包裹 ```javascript - {{ todo.name }}
// 代替方案
<a name="NlDhr"></a>
#### 9. Vue2响应式原理
整理思路:数据劫持+观察者模式
1. Observer:使用`defineProperty`对对象属性递归劫持get,set。用于收集依赖和派发更新
1. dep:用于收集当前响应式对象的依赖关系,每个对象包含子对象都有一个dep实例(`dep.subs`是watcher实例数组)。当数据变化时触发set;通过`dep.notify()`通知各个watcher更新。这是**发布订阅模式**
1. watcher:观察的对象,分为渲染watcher,计算属性watcher和侦听器watcher三种;组件会在渲染的过程中创建相应的Watcher实例记录依赖的数据属性(收集依赖)。当依赖项改动,setter触发`dep.notify()`,触发watcher的`update`。从而使关联的组件重新渲染,(触发comptuted,watch回调)
1. 重写数组原型上的7种方法(`pop, push, shift, unshift, splice, sort, reverse`),调用以上方法触发`dep.notify`达到响应式的目的。
1. `defineProperty`无法观测到对象、数组的增加,删除。所以Vue新增了`set, delete`方法确保新增和删除(更新也可以使用)能触发响应式。
```javascript
/**
* @name Vue数据双向绑定(响应式系统)的实现原理
*/
// observe方法遍历并包装对象属性
function observe(target) {
// 若target是一个对象,则遍历它
if (target && typeof target === "Object") {
Object.keys(target).forEach((key) => {
// defineReactive方法会给目标属性装上“监听器”
defineReactive(target, key, target[key]);
});
}
}
// 定义defineReactive方法
function defineReactive(target, key, val) {
const dep = new Dep();
// 属性值也可能是object类型,这种情况下需要调用observe进行递归遍历
observe(val);
// 为当前属性安装监听器
Object.defineProperty(target, key, {
// 可枚举
enumerable: true,
// 不可配置
configurable: false,
get: function () {
return val;
},
// 监听器函数
set: function (value) {
dep.notify();
},
});
}
class Dep {
constructor() {
this.subs = [];
}
addSub(sub) {
this.subs.push(sub);
}
notify() {
this.subs.forEach((sub) => {
sub.update();
});
}
}
10. Vue3响应式原理
使用Proxy代理对象,在set中track收集依赖,在get中trigger触发更新。
Vue有一个WeakMap对象targetMap
用于收集依赖;key是对象target
,value是Map对象depsMap
;depsMap的key是对象target
的属性,value是一个Set
存放收集到的副作用函数。
11. Vue父子组件生命周期钩子函数执行顺序
- 加载渲染:父beforeCreate -> 父created -> 父 beforeMount -> 子beforeCreate -> 子created -> 子beforeMount -> 子 mounted -> 父mounted
- 子组件更新:父beforeUpdate -> 子beforeUpdate -> 子updated -> 父updated
父组件销毁:父beforeDestory/beforeUnmount -> 子beforeDestory/beforeUnmount -> 子destoryed/unmounted ->父destoryed/unmounted
12. 虚拟DOM的优缺点
优点:配合数据双向绑定无需手动操作DOM;在无需手动操纵DOM的基础上保证性能下限;跨平台
缺点:首次渲染大量DOM时虚拟DOM计算量大;无法极致优化13. v-model原理
v-model是一些标签的数据绑定和事件的语法糖。
用于基础的HTML标签
<input type="text">
,<textarea></textarea>
:value属性和input事件<input type"radio">
,<input type="checkbox">
:checked属性和change事件;radio值为value,checkbox值为checked<select>
选中的值和change事件
- 用于组件就是
<Child :someProp="prop" @update:someProp="(newVal) => prop = newVal" />
的语法糖<Child v-model:someProp="prop" />
or<Child v-model="prop"/>
在组件内就是要触发someProp
更新:this.$emit('update:someProps', "new value")
or this.$emit('someProps', "new value")
14.Vue事件绑定原理
原生事件是通过addEventListener
添加,自定义事件通过发布订阅模式$on $emit
绑定发布
15. diff算法
1) React diff算法
- 使用key来判断新子节点列表
nextChildren
和旧子节点列表prevChildren
中的节点是不是同一个节点 - 使用嵌套的双循环遍历两个列表
nextChildren
和prevChildren
,这里就是从nextChildren
中取出一个nextVNode
,在prevChildren
列表中寻找是否存在;能找到,则patch
这个节点,记prevVNode
的下标为j。 - 先说新节点在旧列表中能找到的情况,我们维护一个变量lastIndex,代表循环中新的节点
newVNode
在旧的列表prevChildren
中找到的旧节点prevVNode
的下标的最大值;初始值是0。 - 如果
j > lastIndex
,则不需要移动,更新lastIndex
为j
这代表了当前新节点比之前新节点在旧节点列表中更靠后 如果
j < lastIndex
,则需要移动。将DOM插入到当前新节点nextChildren[i]
的前一个节点nextChildren[i-1]
的下一个兄弟的前面const refNode = nextChildren[i-1].el.nextSibling;
container.insertBefore(preVNode.el, refNode);
如果没有找到,说明是新增的节点,应该创建新节点挂载。
最后遍历旧节点列表,找到在新节点列表中不存在的节点删除。
/**
* 记录lastIndex
* 遍历新的children
* 如果当前节点旧的index < lastIndex 则需要移动该节点
* 如果当前节点旧的index > lastIndex 则更新lastIndex
* 多余的节点删除,少的节点插入到合适的位置
*/
let lastIndex = 0;
for (const i in nextChildren) {
const nextVNode = nextChildren[i];
let find = false;
for (const j in prevChildren) {
const prevVNode = prevChildren[j];
if (nextVNode.key === prevVNode.key) {
find = true;
patch(prevVNode, nextVNode, container);
if (j < lastIndex) {
/**
* 移动节点
* 找到新children种需要移动节点a的前一个节点
* 找到它的后继节点b
* 将旧children种需要移动的节点插入b之前
*/
const refNode = nextChildren[i - 1].el.nextSibling;
container.insertBefore(prevVNode.el, refNode);
break;
} else {
/** 更新节点 */
lastIndex = j;
}
break;
}
}
if (!find) {
/** 多余的节点应该挂载 */
const refNode = (i - 1 < 0)
? prevChildren[0].el
: prevChildren[i - 1].el.nextSibling;
mount(nextChildren[i], container, false, refNode);
}
}
/** 移除已经不存在的节点 */
for (const i in prevChildren) {
const prevChild = prevChildren[i];
const has = nextChildren.find((nextChild) =>
nextChild.key === prevChild.key
);
if (!has) {
container.removeChild(prevChild.el);
}
}
2) Vue2双端比较
Vue2采用双端比较:
获取新旧节点列表开头和结尾的下标,对应的VNode。
oldStartIdx,oldEndIdx,newStartIdx,newEndIdx
- 循环比对,直到start下标超过end。四个节点互相对比,
1. [oldStart, newStart]
,2. [oldStart, newEnd]
,3. [oldEnd, newStart]
,4. [oldEnd, newEnd]
。如果是start节点匹配到则++后移,end节点匹配到则迁移 - 如果是1和4这两种情况相匹配,不需要移动,直接patch;并将新旧节点的下标后移一位(1),或前移一位(4)
如果是
2. [oldStart, newEnd]
匹配,首先patch,然后将oldStartVnode
移动到oldEndVnode
后面;也就是移到当前列表的最后一位;最后更新下标和对应节点patch(oldStartVnode, newEndVnode, container);
container.insertBefore(oldStartVnode.el, oldEndVnode.el.nextSibling);
如果是
3. [oldEnd, newStart]
,匹配,首先patch,然后将oldEndVnode
移动到newEnd
前面;也就是移动到当前列表第一位;最后更新下标和对应节点patch(oldEndVnode, newStartVnode, container);
container.insertBefore(oldendVnode.el, oldStartVnode.el);
上面四种情况都没有成功就是非理想情况;此时在旧列表中找到与
newStartVnode
相同的节点,patch,移动到当前旧列表的最前面;如果没有找到,说明是新节点需要创建挂载;最后更新newStartVnode
const i = prevChildren.findIndex(
(node) => node.key === newStartVnode.key
)
/** 找到了就patch,移动到当前列表最前端 */
patch(prevChildren[i], newStartVnode, container);
container.insertBefore(prevChildren[i].el, newStartVnode.el);
/** 将移动好的节点删除 */
prevChildren[i] = undefined;
/** 更新节点 */
newStartVnode = nextChildren(++newStartIdx)
最后添加新节点,如果
oldIdx
先越界,创建挂载新节点- 删除移除的节点,如果
newIdx
先越界,移除不存在的节点 ```javascript /**- 双端比较
- */ let oldStartIdx = 0; let oldEndIdx = prevChildren.length - 1; let newStartIdx = 0; let newEndIdx = nextChildren.length - 1;
let oldStartVNode = prevChildren[oldStartIdx]; let oldEndVNode = prevChildren[oldEndIdx]; let newStartVNode = nextChildren[newStartIdx]; let newEndVNode = nextChildren[newEndIdx];
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) { if (!oldStartVNode) { /**
* 0.1 旧的第一个元素是undefined
*/
oldStartVNode = prevChildren[++oldStartIdx];
} else if (!oldEndVNode) {
/**
* 0.2 酒得最后一个元素是undefined
*/
oldEndVNode = prevChildren[--oldEndIdx];
} else if (oldStartVNode.key === newStartVNode.key) {
/**
* 1. 旧的第一个元素和新的第一个元素key相同
*/
patch(oldStartVNode, newStartVNode, container);
oldStartVNode = prevChildren[++oldStartIdx];
newStartVNode = nextChildren[++newStartIdx];
} else if (oldEndVNode.key === newEndVNode.key) {
/**
* 2. 旧的最后一个元素和新的最后一个元素key相同
*/
patch(oldEndVNode, newEndVNode, container);
oldEndVNode = prevChildren[--oldEndIdx];
newEndVNode = nextChildren[--newEndIdx];
} else if (oldStartVNode.key === newEndVNode.key) {
/**
* 3. 旧的第一个元素和新的最后一个元素key相同
*/
patch(oldStartVNode, newEndVNode, container);
container.insertBefore(oldStartVNode.el, oldEndVNode.el.nextSibling);
oldStartVNode = prevChildren[++oldStartIdx];
newEndVNode = nextChildren[--newEndIdx];
} else if (oldEndVNode.key === newStartVNode.key) {
/**
* 4. 旧的最后一个元素和新的第一个元素key相同
*/
patch(oldEndVNode, newStartVNode, container);
container.insertBefore(oldEndVNode.el, oldStartVNode.el);
oldEndVNode = prevChildren[--oldEndIdx];
newStartVNode = nextChildren[++newStartIdx];
} else {
/**
* 5. 非理想情况,旧的某一个中间元素和新的第一个元素key相同
*/
const idxInOld = prevChildren.findIndex(
(node) => node.key === newStartVNode
);
if (idxInOld >= 0) {
/** 将找到的节点移动到oldStartVNode前面,并将prevChildren[idxInOld]设置为undefined */
const vnodeToMove = prevChildren[idxInOld];
patch(vnodeToMove, newStartVNode, container);
container.insertBefore(vnodeToMove.el, oldStartVNode.el);
prevChildren[idxInOld] = undefined;
} else {
/**
* 全新节点 需要挂载
*/
mount(newStartVNode, container, false, oldStartVNode.el);
}
newStartVNode = nextChildren[++newStartIdx];
}
} if (oldEndIdx < oldStartIdx) { / 添加多余的节点 */ for (let i = newStartIdx; i <= newEndIdx; i++) { mount(nextChildren[i], container, false, oldStartVNode.el); } } else if (newEndIdx < newStartIdx) { / 移除多余的节点 */ for (let i = oldStartIdx; i <= oldEndIdx; i++) { container.removeChild(prevChildren[i].el); } }
<a name="XJkz5"></a>
##### 3) Vue3的优化
1. 事件缓存:元素已经添加的事件会被缓存,复用
1. 静态标记与复用:不会变的静态元素会在创建时被标记静态节点。patch的时候跳过,渲染的时候复用
1. 头和头,尾和尾比较,这里相同的元素直接patch;
未比较的元素,按未比较的新`nextChildren`的长度创建一个source数组,初始值为-1;从`prevChildren`中找出他们的下标放在source数组中。<br />遍历完后寻找最长递增子序列,这里的节点不需要移动,移动其他节点。数组中-1代表该位置的元素是新增的,
<a name="dJDbi"></a>
#### ath16. 路由守卫
路由钩子的执行顺序:<br />全局`beforeEach`<br />router中定义的`beforeEnter`<br />重用的组件里调用`beforeRouteUpdate`<br />组件中定义的`beforeRouteEnter`<br />全局组件的`beforeResolve`<br />全局的`afterEach`<br />DOM更新<br />`beforeRouteEnter`中`next`的回调
<a name="RvRuj"></a>
#### 17. Vue scoped css原理
在元素和css选择器上添加唯一attribute`data-v-hash`
<a name="Z91ds"></a>
#### 18. 动态路由
把path匹配到的路由映射到同一个组件上,使用`:`表示需要被匹配的字段
```javascript
path: '/user/:id'
this.$route.params ===> {id: 'jay'} // 组件中
动态路由的组件被复用的时候,如从/user/jay
导航到/user/wang
,组件被复用导致组件复用,生命周期钩子不会触发,可以使用watch监听路由或给路由加key
watch: {
'$route.params.id': function(){}
}
<router-view :key="$route.fullPath"></router-view>
19. 为什么$nextTick
要用微任务队列?
防止页面频繁更新