一. Vue基础

1. Composition API学习

1) reactive ref

reactive使用代理Proxy使普通对象(Object)和内置对象(Map,Set)响应式;

  1. reactive出的是一个深度响应的Proxy对象;它所有的属性都是响应式的;但一旦被取出就不是响应式的了

    1. const state = reactive({
    2. foo: {
    3. count: 1
    4. },
    5. bar:2
    6. });
    7. state.foo.count++; // 响应式的
    8. const b = state.bar;
    9. b++; // 不是响应式的了
  2. 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

  1. `ref`包装一个值,使之成为响应式的
  2. 1. `ref`包装一个原始值,变成一个对象;value指向原始值,value属性具有响应式;
  3. ```javascript
  4. const count = ref(1)
  5. count.value++ // 响应式的
  1. ref包装一个对象,对象变成Proxy类型,value指向这个Proxy对象;value指向的Proxy对象具有深度响应

    1. const count = ref({
    2. foo: {
    3. bar: 1
    4. }
    5. })
    6. count.value.foo // Proxy对象
  2. 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 // 父元素

// 子元素

  1. <a name="hHJFF"></a>
  2. ### 二. 进阶
  3. <a name="JQFVm"></a>
  4. #### 1. 理解MVC和MVVM
  5. <a name="TtSp7"></a>
  6. ##### 1) MVC
  7. MVC将应用程序分为三个角色:Model,View和Controller;<br />Model也就是模型用于处理应用程序**数据逻辑**部分,通常模型对象负责存取数据。<br />View也也就是视图负责处理**数据显示**的部分,视图通常依据模型对象创建。<br />Controller也就是控制器负责处理**用户交互**的部分;控制器通常从视图读取数据,控制用户输入并向模型发送数据。
  8. Controller和View之间使用策略模式;用户操纵View生成一个事件(如按钮点击事件);Controller对象接收并解释事件,即应用策略模式实现不同的响应
  9. 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)
  10. <a name="bpC7e"></a>
  11. ##### 2) MVVM
  12. MVVM指Model,View和ViewModel<br />Model指用于处理数据到模型;<br />View指视图;<br />ViewModel是连接View和Model的桥梁,他有两个方向:
  13. 1. 将模型Model转换为视图View;即将后端传递的数据转换成看到的页面,通过**数据绑定**实现
  14. 1. 将视图View转化为模型Model;即将页面转化为后端的数据,通过**DOM事件监听**实现。
  15. 两个方向都是实现了可以称之为数据的双向绑定<br />参考:[Vue 的 MVVM 思想](https://juejin.cn/post/6879300070962003982)
  16. <a name="a2prE"></a>
  17. #### 2. date为什么是函数
  18. 为了防止组件复用时data共享的问题。
  19. - data是组件原型上的一个属性`MyComponent.prototype.data`;
  20. - 如果data是对象,所有的组件实例共享data对象;
  21. - 如果data是函数返回一个对象,那么每次创建组件实例data方法会返回一个新的对象。
  22. <a name="xTCrE"></a>
  23. #### 3. 组件间通信的方式
  24. 1. `props和emits`。父组件通过props向子组件传递数据;子组件通过emits注册事件,父组件监听该事件完成传值/ 通信
  25. 在Options API中是`props emits`;在Composition API中是`defineProps defineEmits`;在组件实例上是`$props $emit()`
  26. ```html
  27. <ChildComp :message="msg" @response="getResponse" />
  1. $parent和$children,$refs,直接获取组件的父组件实例,子组件实例,或某个组件的实例;vue3没有提供$children

    1. <input ref="input1">
    2. <script>
    3. ...
    4. this.$refs.input1
    5. </script>
  2. provide inject,通过依赖注入的方式可以向子孙组件传值,Options API和Composition API方法名相同 ```javascript // 父组件setup中 import { provide } from ‘vue’; provide(‘a’, 1);

// 子孙组件中,多种形式 inject: { b: { from: ‘a’ } }, inject: [‘a’]

  1. 4. `$attrs`fallthrough属性,父组件传递给子组件,但是子组件没有在propsemits上声明的属性
  2. vue2中使用`$attrs``$listeners`实现多层嵌套传值
  3. ```javascript
  4. // 父组件中
  5. <HelloWorld class="red" />
  6. // 子组件中
  7. console.log('this.$attrs :>> ', this.$attrs);
  8. // this.$attrs :>>
  9. // Proxy {class: 'red', __vInternal: 1}
  10. // ...
  11. // [[Target]]: Object
  12. // class: "red"
  13. // ...
  1. vuex状态管理

vue可以使用vuex集中状态管理,下面是vue2中使用vue3的教程

  1. 安装vuex

    1. npm i -S vuex@3
  2. 创建src/store/index.js文件,在里面创建vuex插件,并导出Vuex.Store实例store。

state用于存储状态,对应的辅助函数mapState,可以放在computed中
getters是修饰器,辅助函数是mapGetters,可以放在computed中
mutations用于修改状态,使用commit触发,必须是同步函数,辅助函数是mapMutations,可以放在methods中
actions提交mutations,可以是异步函数;使用dispatch触发,辅助函数是mapActions,可以放在methods中

  1. import Vue from 'vue';
  2. import Vuex from 'vuex';
  3. // Vue使用Vuex插件
  4. Vue.use(Vuex);
  5. const store = new Vue.Store({
  6. state: {
  7. number: 1
  8. },
  9. getters: {
  10. getHelloNumber(state) {
  11. return `hello ${state.number}`;
  12. }
  13. },
  14. mutations: {
  15. setNumber(state, payload) {
  16. state.number = payload.number;
  17. }
  18. },
  19. actions: {
  20. setNumberAsync(content, payload) {
  21. return new Promise((resolve) => {
  22. setTimeout(() => {
  23. content.commit('setNumber', payload);
  24. resolve();
  25. }, 1500)
  26. })
  27. }
  28. }
  29. });
  30. export default store;
  1. main.js导入store,并在new Vue的时候加入store,以便能全局使用store,this.$store.state

    1. import store from '@/store/index.js';
    2. new Vue({
    3. store // 可以全局this.$store使用vuex
    4. }).mount('#app');
  2. vuex在组件中的用法

    1. // 普通的使用方法
    2. this.$store.state.number
    3. this.$store.getters.getHelloNumber
    4. this.$store.commit('setNumber', {number: 2}) // 后面只允许一个参数,建议用对象payload
    5. this.$store.dispatch('setNumberAsync', {number: 3}) //后面只允许一个参数
  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;

  1. 参考:[手把手教你使用Vuex](https://juejin.cn/post/6928468842377117709)
  2. 6. EventBus事件总线
  3. 可以创建一个新的Vue实例作为事件总线,在上面注册和监听事件;有两种创建EventBus的方法:作为Vue原型链上的属性或作为一个模块。<br />事件总线的缺点:
  4. 1. 无法确定由谁触发事件,导致混乱
  5. 1. 某个页面刷新后可能导致与之相关的EventBus被移除,其他组件无法监听到事件
  6. 1. 重复操作某个页面可能导致EventBus重复触发
  7. ```javascript
  8. // 第一种方式 main.js中
  9. Vue.prototyep.$EventBus = new Vue();
  10. // 第二种方式 EventBus.js中
  11. import Vue from 'vue';
  12. export default new Vue();
  13. // 注册事件
  14. this.$EventBus.$emit('hi', 1);
  15. import EventBus from './EventBus';
  16. EventBus.$emit('hello', 2);
  17. // 监听事件
  18. this.$EventBus.$on('hi', handler);
  19. import EventBus from './EventBus';
  20. EventBus.$on('hello', handler);

4. Vue3的生命周期

vue生命周期是组件从创建到销毁的过程;在这一过程中Vue提供一些生命周期钩子函数,让开发者在组件不同的阶段添加自己的代码逻辑。
Vue2和Vue3 Options API的生命周期钩子的名称基本相同,除了destory -> unMount
Composition API使用setup替代了beforeCreatedcreate
vue3生命周期Options API调用源码位置

  1. beforeCreate在实例初始化后,数据观测(data observer)和watch/event事件配置之前被调用。此时data,methods,watch,computed上的数据和方法都访问不到
  2. created在实例创建完成后调用,完成以下配置:inject,methods,data,computed,watch,provide选项函数的解析。此时组件的属性和方法已经可以访问。但是DOM还不可以。可以使用this.$nextTick回调
  3. beforeMount在render函数首次被调用,DOM被挂载前被调用。服务端渲染期间不被调用
  4. mounted在挂载完成后发生,真实DOM完成挂载,数据完成双向绑定。
  5. beforeUpdate在响应式属性更新,DOM被更新前触发。在这个钩子中进一步更改状态不会触发附加的重新渲染
  6. updatedDOM更新完成。应该避免在这里修改响应式数据,可能导致无限循环更新。服务端渲染期间不被调用
  7. beforeUnmount/beforeDestory卸载组件实例前被调用;实例仍然可用,可以在此期间清除定时器等
  8. unmounted/destoryed卸载组件实例后被调用
  9. activedkeep-alive专属,在组件被激活时调用
  10. deactivedkeep-alive专属,在组件被未被激活时调用

问题:异步请求在哪里调用?
答:可以在created,beforeMount和mounted内进行。这是data被初始化完成,方法也可以调用
参考:Vue3生命周期详解
Vue 的生命周期之间到底做了什么事清?(源码详解,带你从头梳理组件化流程)

5. v-if和v-for的区别

  1. v-if是会被编译成三元表达式,条件不满足组件不会渲染

v-show会被编译成指令,条件不满足元素的display:none

  1. v-if条件转变元素会在真实DOM中插入和移除,适合不需要频繁切换的场景
  2. v-show用display控制元素显隐,适合频繁切换的场景

    6. 如何理解vue单向数据流

    所有的props形成的父子props单行向下流动;父组件props的更新会向下流动到子组件中,但是子组件不行,这样防止子组件意外更新父级组件状态,导致数据流向的困惑。
    在vue中子组件修改props会收到警告;子组件一般通过事件响应的机制和父组件沟通。

    7. watch和computed的区别

  3. computed是计算属性,依赖其他响应式数据更新值;computed是有缓存的;只有当其他响应式数据发生变化的时候才会更新。

watch监听到响应式数据的值发生变化就会去执行对应的回调。

  1. 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
  2. {{ todo.name }}

// 代替方案

  1. <a name="NlDhr"></a>
  2. #### 9. Vue2响应式原理
  3. 整理思路:数据劫持+观察者模式
  4. 1. Observer:使用`defineProperty`对对象属性递归劫持get,set。用于收集依赖和派发更新
  5. 1. dep:用于收集当前响应式对象的依赖关系,每个对象包含子对象都有一个dep实例(`dep.subs`是watcher实例数组)。当数据变化时触发set;通过`dep.notify()`通知各个watcher更新。这是**发布订阅模式**
  6. 1. watcher:观察的对象,分为渲染watcher,计算属性watcher和侦听器watcher三种;组件会在渲染的过程中创建相应的Watcher实例记录依赖的数据属性(收集依赖)。当依赖项改动,setter触发`dep.notify()`,触发watcher的`update`。从而使关联的组件重新渲染,(触发comptuted,watch回调)
  7. 1. 重写数组原型上的7种方法(`pop, push, shift, unshift, splice, sort, reverse`),调用以上方法触发`dep.notify`达到响应式的目的。
  8. 1. `defineProperty`无法观测到对象、数组的增加,删除。所以Vue新增了`set, delete`方法确保新增和删除(更新也可以使用)能触发响应式。
  9. ```javascript
  10. /**
  11. * @name Vue数据双向绑定(响应式系统)的实现原理
  12. */
  13. // observe方法遍历并包装对象属性
  14. function observe(target) {
  15. // 若target是一个对象,则遍历它
  16. if (target && typeof target === "Object") {
  17. Object.keys(target).forEach((key) => {
  18. // defineReactive方法会给目标属性装上“监听器”
  19. defineReactive(target, key, target[key]);
  20. });
  21. }
  22. }
  23. // 定义defineReactive方法
  24. function defineReactive(target, key, val) {
  25. const dep = new Dep();
  26. // 属性值也可能是object类型,这种情况下需要调用observe进行递归遍历
  27. observe(val);
  28. // 为当前属性安装监听器
  29. Object.defineProperty(target, key, {
  30. // 可枚举
  31. enumerable: true,
  32. // 不可配置
  33. configurable: false,
  34. get: function () {
  35. return val;
  36. },
  37. // 监听器函数
  38. set: function (value) {
  39. dep.notify();
  40. },
  41. });
  42. }
  43. class Dep {
  44. constructor() {
  45. this.subs = [];
  46. }
  47. addSub(sub) {
  48. this.subs.push(sub);
  49. }
  50. notify() {
  51. this.subs.forEach((sub) => {
  52. sub.update();
  53. });
  54. }
  55. }

参考:你可以手写Vue2的响应式原理吗?

10. Vue3响应式原理

使用Proxy代理对象,在set中track收集依赖,在get中trigger触发更新。
Vue有一个WeakMap对象targetMap用于收集依赖;key是对象target,value是Map对象depsMap;depsMap的key是对象target的属性,value是一个Set存放收集到的副作用函数。
四. 前端整理 Vue框架 - 图1

11. Vue父子组件生命周期钩子函数执行顺序

  1. 加载渲染:父beforeCreate -> 父created -> 父 beforeMount -> 子beforeCreate -> 子created -> 子beforeMount -> 子 mounted -> 父mounted
  2. 子组件更新:父beforeUpdate -> 子beforeUpdate -> 子updated -> 父updated
  3. 父组件销毁:父beforeDestory/beforeUnmount -> 子beforeDestory/beforeUnmount -> 子destoryed/unmounted ->父destoryed/unmounted

    12. 虚拟DOM的优缺点

    优点:配合数据双向绑定无需手动操作DOM;在无需手动操纵DOM的基础上保证性能下限;跨平台
    缺点:首次渲染大量DOM时虚拟DOM计算量大;无法极致优化

    13. v-model原理

    v-model是一些标签的数据绑定和事件的语法糖。

  4. 用于基础的HTML标签

<input type="text"><textarea></textarea>:value属性和input事件
<input type"radio"><input type="checkbox">:checked属性和change事件;radio值为value,checkbox值为checked
<select>选中的值和change事件

  1. 用于组件就是<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算法

VNode的diff算法都是深度优先,同级节点比较

1) React diff算法
  1. 使用key来判断新子节点列表nextChildren和旧子节点列表prevChildren中的节点是不是同一个节点
  2. 使用嵌套的双循环遍历两个列表nextChildrenprevChildren,这里就是从nextChildren中取出一个nextVNode,在prevChildren列表中寻找是否存在;能找到,则patch这个节点,记prevVNode的下标为j。
  3. 先说新节点在旧列表中能找到的情况,我们维护一个变量lastIndex,代表循环中新的节点newVNode在旧的列表prevChildren中找到的旧节点prevVNode的下标的最大值;初始值是0。
  4. 如果j > lastIndex,则不需要移动,更新lastIndexj这代表了当前新节点比之前新节点在旧节点列表中更靠后
  5. 如果j < lastIndex,则需要移动。将DOM插入到当前新节点nextChildren[i]的前一个节点nextChildren[i-1]的下一个兄弟的前面

    1. const refNode = nextChildren[i-1].el.nextSibling;
    2. container.insertBefore(preVNode.el, refNode);
  6. 如果没有找到,说明是新增的节点,应该创建新节点挂载。

  7. 最后遍历旧节点列表,找到在新节点列表中不存在的节点删除。

    1. /**
    2. * 记录lastIndex
    3. * 遍历新的children
    4. * 如果当前节点旧的index < lastIndex 则需要移动该节点
    5. * 如果当前节点旧的index > lastIndex 则更新lastIndex
    6. * 多余的节点删除,少的节点插入到合适的位置
    7. */
    8. let lastIndex = 0;
    9. for (const i in nextChildren) {
    10. const nextVNode = nextChildren[i];
    11. let find = false;
    12. for (const j in prevChildren) {
    13. const prevVNode = prevChildren[j];
    14. if (nextVNode.key === prevVNode.key) {
    15. find = true;
    16. patch(prevVNode, nextVNode, container);
    17. if (j < lastIndex) {
    18. /**
    19. * 移动节点
    20. * 找到新children种需要移动节点a的前一个节点
    21. * 找到它的后继节点b
    22. * 将旧children种需要移动的节点插入b之前
    23. */
    24. const refNode = nextChildren[i - 1].el.nextSibling;
    25. container.insertBefore(prevVNode.el, refNode);
    26. break;
    27. } else {
    28. /** 更新节点 */
    29. lastIndex = j;
    30. }
    31. break;
    32. }
    33. }
    34. if (!find) {
    35. /** 多余的节点应该挂载 */
    36. const refNode = (i - 1 < 0)
    37. ? prevChildren[0].el
    38. : prevChildren[i - 1].el.nextSibling;
    39. mount(nextChildren[i], container, false, refNode);
    40. }
    41. }
    42. /** 移除已经不存在的节点 */
    43. for (const i in prevChildren) {
    44. const prevChild = prevChildren[i];
    45. const has = nextChildren.find((nextChild) =>
    46. nextChild.key === prevChild.key
    47. );
    48. if (!has) {
    49. container.removeChild(prevChild.el);
    50. }
    51. }

    2) Vue2双端比较

    Vue2采用双端比较:

  8. 获取新旧节点列表开头和结尾的下标,对应的VNode。oldStartIdx,oldEndIdx,newStartIdx,newEndIdx

  9. 循环比对,直到start下标超过end。四个节点互相对比,1. [oldStart, newStart],2. [oldStart, newEnd],3. [oldEnd, newStart],4. [oldEnd, newEnd]。如果是start节点匹配到则++后移,end节点匹配到则迁移
  10. 如果是1和4这两种情况相匹配,不需要移动,直接patch;并将新旧节点的下标后移一位(1),或前移一位(4)
  11. 如果是2. [oldStart, newEnd]匹配,首先patch,然后将oldStartVnode移动到oldEndVnode后面;也就是移到当前列表的最后一位;最后更新下标和对应节点

    1. patch(oldStartVnode, newEndVnode, container);
    2. container.insertBefore(oldStartVnode.el, oldEndVnode.el.nextSibling);
  12. 如果是3. [oldEnd, newStart],匹配,首先patch,然后将oldEndVnode移动到newEnd前面;也就是移动到当前列表第一位;最后更新下标和对应节点

    1. patch(oldEndVnode, newStartVnode, container);
    2. container.insertBefore(oldendVnode.el, oldStartVnode.el);
  13. 上面四种情况都没有成功就是非理想情况;此时在旧列表中找到与newStartVnode相同的节点,patch,移动到当前旧列表的最前面;如果没有找到,说明是新节点需要创建挂载;最后更新newStartVnode

    1. const i = prevChildren.findIndex(
    2. (node) => node.key === newStartVnode.key
    3. )
    4. /** 找到了就patch,移动到当前列表最前端 */
    5. patch(prevChildren[i], newStartVnode, container);
    6. container.insertBefore(prevChildren[i].el, newStartVnode.el);
    7. /** 将移动好的节点删除 */
    8. prevChildren[i] = undefined;
    9. /** 更新节点 */
    10. newStartVnode = nextChildren(++newStartIdx)
  14. 最后添加新节点,如果oldIdx先越界,创建挂载新节点

  15. 删除移除的节点,如果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) { /**

  1. * 0.1 旧的第一个元素是undefined
  2. */
  3. oldStartVNode = prevChildren[++oldStartIdx];
  4. } else if (!oldEndVNode) {
  5. /**
  6. * 0.2 酒得最后一个元素是undefined
  7. */
  8. oldEndVNode = prevChildren[--oldEndIdx];
  9. } else if (oldStartVNode.key === newStartVNode.key) {
  10. /**
  11. * 1. 旧的第一个元素和新的第一个元素key相同
  12. */
  13. patch(oldStartVNode, newStartVNode, container);
  14. oldStartVNode = prevChildren[++oldStartIdx];
  15. newStartVNode = nextChildren[++newStartIdx];
  16. } else if (oldEndVNode.key === newEndVNode.key) {
  17. /**
  18. * 2. 旧的最后一个元素和新的最后一个元素key相同
  19. */
  20. patch(oldEndVNode, newEndVNode, container);
  21. oldEndVNode = prevChildren[--oldEndIdx];
  22. newEndVNode = nextChildren[--newEndIdx];
  23. } else if (oldStartVNode.key === newEndVNode.key) {
  24. /**
  25. * 3. 旧的第一个元素和新的最后一个元素key相同
  26. */
  27. patch(oldStartVNode, newEndVNode, container);
  28. container.insertBefore(oldStartVNode.el, oldEndVNode.el.nextSibling);
  29. oldStartVNode = prevChildren[++oldStartIdx];
  30. newEndVNode = nextChildren[--newEndIdx];
  31. } else if (oldEndVNode.key === newStartVNode.key) {
  32. /**
  33. * 4. 旧的最后一个元素和新的第一个元素key相同
  34. */
  35. patch(oldEndVNode, newStartVNode, container);
  36. container.insertBefore(oldEndVNode.el, oldStartVNode.el);
  37. oldEndVNode = prevChildren[--oldEndIdx];
  38. newStartVNode = nextChildren[++newStartIdx];
  39. } else {
  40. /**
  41. * 5. 非理想情况,旧的某一个中间元素和新的第一个元素key相同
  42. */
  43. const idxInOld = prevChildren.findIndex(
  44. (node) => node.key === newStartVNode
  45. );
  46. if (idxInOld >= 0) {
  47. /** 将找到的节点移动到oldStartVNode前面,并将prevChildren[idxInOld]设置为undefined */
  48. const vnodeToMove = prevChildren[idxInOld];
  49. patch(vnodeToMove, newStartVNode, container);
  50. container.insertBefore(vnodeToMove.el, oldStartVNode.el);
  51. prevChildren[idxInOld] = undefined;
  52. } else {
  53. /**
  54. * 全新节点 需要挂载
  55. */
  56. mount(newStartVNode, container, false, oldStartVNode.el);
  57. }
  58. newStartVNode = nextChildren[++newStartIdx];
  59. }

} 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); } }

  1. <a name="XJkz5"></a>
  2. ##### 3) Vue3的优化
  3. 1. 事件缓存:元素已经添加的事件会被缓存,复用
  4. 1. 静态标记与复用:不会变的静态元素会在创建时被标记静态节点。patch的时候跳过,渲染的时候复用
  5. 1. 头和头,尾和尾比较,这里相同的元素直接patch;
  6. 未比较的元素,按未比较的新`nextChildren`的长度创建一个source数组,初始值为-1;从`prevChildren`中找出他们的下标放在source数组中。<br />遍历完后寻找最长递增子序列,这里的节点不需要移动,移动其他节点。数组中-1代表该位置的元素是新增的,
  7. <a name="dJDbi"></a>
  8. #### ath16. 路由守卫
  9. 路由钩子的执行顺序:<br />全局`beforeEach`<br />router中定义的`beforeEnter`<br />重用的组件里调用`beforeRouteUpdate`<br />组件中定义的`beforeRouteEnter`<br />全局组件的`beforeResolve`<br />全局的`afterEach`<br />DOM更新<br />`beforeRouteEnter`中`next`的回调
  10. <a name="RvRuj"></a>
  11. #### 17. Vue scoped css原理
  12. 在元素和css选择器上添加唯一attribute`data-v-hash`
  13. <a name="Z91ds"></a>
  14. #### 18. 动态路由
  15. 把path匹配到的路由映射到同一个组件上,使用`:`表示需要被匹配的字段
  16. ```javascript
  17. path: '/user/:id'
  18. this.$route.params ===> {id: 'jay'} // 组件中

动态路由的组件被复用的时候,如从/user/jay导航到/user/wang,组件被复用导致组件复用,生命周期钩子不会触发,可以使用watch监听路由或给路由加key

  1. watch: {
  2. '$route.params.id': function(){}
  3. }
  4. <router-view :key="$route.fullPath"></router-view>

19. 为什么$nextTick要用微任务队列?

防止页面频繁更新