前端开发基础——Vue

文章目录

  • Vue的特点
  • MVVM
  • 常用指令
  • v-model原理
  • Vue监视数据(双向绑定)
  • computed和watch
  • 绑定样式
  • v-if和v-show
  • key
  • new Vue()后的流程
  • 生命周期
  • 组件通信
  • nextTick
  • 插槽
  • Vuex
  • VueRouter
  • keep-alive
  • 路由守卫
  • 路由器hash和history工作模式
  • Vue中diff算法理解

Vue的特点

  • 采用组件化模式,提高代码复用率,且让代码更好维护。
  • 声明式编码,让编码人员无需直接操作DOM,提高开发效率。
  • 使用虚拟DOM+优秀的Diff算法,尽量复用DOM结点。

MVVM

  • 什么是MVVM
    • M——Model,代表数据模型,也可以在Model中定义数据修改和操作的业务逻辑。
    • V——View,代表UI组件,它负责将数据模型转化为UI展现出来。
    • VM——ViewModel,是View和Model间的桥梁,以数据绑定的方式观察模型数据的变化并对视图对应内容进行更新,以DOM监听的方式监听视图的变化并通知模型相应数据变化。
  • 优点
    • 低耦合:视图(View)可以独立于Model变化和修改,一个ViewModel可以绑定到不同的View上,当View变化的时候Model可以不变,当Model变化的时候,View也可以不变。
    • 可重用性:可以把一些视图逻辑放到一个ViewModel里面,让很多View重用这段视图逻辑。
    • 独立开发:开发人员可以专注于业务逻辑和数据的开发(ViewModel),设计人员可以专注于页面设计。
    • 可测试:界面素来是比较难于测试的,而现在测试可以针对ViewModel来写。

前端开发基础——Vue_第1张图片

常用指令

  • v-bind:xxx:单向绑定,可简写为:xxx,数据只能从data流向页面。
  • v-model:xxx:双向绑定,v-model:value可简写为v-model,一般应用在表单类元素上(如input、select等),数据不仅能从data流向页面,还可以从页面流向data。
  • v-on:xxx:绑定事件,可简写为@xxx,事件的回调配置在methods对象中。
    Vue中的事件修饰符:prevent:阻止默认事件(常用);stop:阻止事件冒泡(常用);once:事件只触发一次(常用);capture:使用事件的捕获模式;self:只有event.target是当前操作的元素时才触发事件;passive:事件的默认行为立即执行,无需等待事件回调执行完毕。
  • v-for="(item, index) in xxx" :key="yyy":用于展示列表数据,可遍历数组、对象、字符串(用的很少)、指定次数(用的很少)。

v-model原理

Vue项目中主要使用v-model指令在表单input、textarea、select等元素上创建双向数据绑定。v-model本质上是语法糖,默认情况下相当于v-bind:value和v-on:input。v-model在内部为不同的输入元素使用不同的属性并抛出不同的事件:

  • text和textarea元素使用value属性和input事件。
  • radio和checkbox使用checked属性和change事件。对于radio,若给标签配置value值,则使用value属性;对于checkbox,若给标签配置value值,当v-model初始值是非数组时,使用checked属性,当v-model初始值是数组时,使用value组成的数组。
  • select字段将value作为prop并将change作为事件。

Vue监视数据(双向绑定)

当一个Vue实例创建时,Vue会遍历data对象中的属性,用 Object.defineProperty将它们添加到Vue实例上,并为每一个添加的属性指定一个getter/setter,在内部追踪相关依赖,在属性被访问和修改时通知变化。每个组件实例都有相应的watcher程序实例,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的setter被调用时,会通知watcher重新计算,从而致使它关联的组件得以更新。

computed和watch

  • computed是计算属性
    computed的结果在getter执行后会缓存,当computed中的函数所依赖的属性没有发生改变的时候,那么调用当前函数的时候结果会从缓存中读取;只有在它依赖的属性改变之后,获取computed的结果才会重新调用对应的getter来计算。
  • watch是监视(侦听)属性
    当被监视的属性变化时,回调函数自动调用,进行相关操作。
  • 使用场景
    需要进行数值计算,而且依赖于其他数据时,使用computed;需要在某个数据变化时做一些事情,使用watch来监视这个数据变化。computed能完成的功能,watch都能完成;watch能完成的功能,computed不一定能完成,例如watch可以进行异步操作。

绑定样式

  • class样式
    写法::class="xxx" xxx可以是字符串、对象、数组。
    字符串写法适用于:类名不确定,要动态读取。
    对象写法适用于:要绑定多个样式,个数不确定,名字也不确定。
    数组写法适用于:要绑定多个样式,个数确定,名字也确定,但不确定用不用。
  • style样式
    :style="{fontSize: xxx}" 其中xxx是动态值。
    :style="[a, b]" 其中a、b是样式对象。

v-if和v-show

  • v-if
    写法:
    v-if="表达式"
    v-else-if="表达式"
    v-else="表达式"
    适用于:切换频率较低的场景。
    特点:不展示的DOM元素直接被移除。
  • v-show
    写法:v-show="表达式"
    适用于:切换频率较高的场景。
    特点:不展示的DOM元素未被移除,仅仅是使用样式隐藏掉。

key

key主要用在Vue的虚拟DOM算法,作为VNode的标识,在新旧nodes对比时辨识VNodes。如果不使用key,Vue会使用一种最大限度减少动态元素并且尽可能的尝试就地修改/复用相同类型元素的算法。而使用key时,它会基于key的变化重新排列元素顺序,并且会移除key不存在的元素。

  • 对比规则
    ① 旧虚拟DOM中找到了与新虚拟DOM相同的key:
    若虚拟DOM中内容没变,直接使用之前的真实DOM。
    若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM。
    ② 旧虚拟DOM中未找到与新虚拟DOM相同的key:
    创建新的真实DOM,随后渲染到页面。
  • 用index作为key可能会引发的问题
    ① 若对数据进行逆序添加、逆序删除等破坏顺序操作:
    会产生没有必要的真实DOM更新——界面效果没问题,但效率低。
    ② 如果结构中还包含输入类的DOM:
    会产生错误DOM更新——界面有问题。
  • 如何选择key
    ① 最好使用每条数据的唯一标识作为key,比如id、手机号、身份证号、学号等唯一值。
    ② 如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的。

new Vue()后的流程

  • Init Events & Lifecycle——初始化生命周期、事件,但数据代理还未开始。
  • Init injections & reactivity——初始化数据监测、数据代理。
  • Compile template into render function/Compile el’s outerHTML as template——Vue开始解析模板,生成虚拟DOM(内存中),页面还不能显示解析好的内容。
  • Create vm.$el and replace “el” with it——将内存中的虚拟DOM转为真实DOM插入页面。
  • Virtual DOM re-render and patch——根据新数据,生成新的虚拟DOM,随后与旧的虚拟DOM进行比较,最终完成页面更新,即完成了Model→View的更新。
  • Teardown watchers, child components and event listeners

生命周期

又名生命周期回调函数、生命周期函数、生命周期钩子。是Vue在关键时刻调用的一些特殊名称的函数。生命周期函数中的this指向是vm或组件实例对象。

  • beforeCreate——无法通过vm访问data中的数据、methods中配置的方法。
  • created——可以通过vm访问data中的数据、methods中配置的方法。
  • beforeMount——页面呈现的是未经Vue编译的DOM结构。所有对DOM的操作,最终都不奏效。
  • mounted——页面中呈现的是经过Vue编译的DOM。对DOM的操作均有效(尽可能避免)。至此初始化过程结束,一般在此进行:开启定时器、发送网络请求、订阅消息、绑定自定义事件等初始化操作。
  • beforeUpdate——数据是新的,但页面是旧的,即页面尚未和数据保持同步。
  • updated——数据是新的,页面也是新的,即页面和数据保持同步。
  • beforeDestroy——vm中所有的data、methods、指令等等,都处于可用状态,马上要执行销毁过程,一般在此阶段:关闭定时器、取消订阅消息、解绑自定义事件等收尾操作。
  • destroyed

组件通信

  • 父组件→子组件
    • 使用props
      父组件中定义需要的数据,在子组件标签中用:或v-bind绑定;子组件props属性中接收。
  • 子组件→父组件
    • 使用props
      父组件中定义回调函数,在子组件标签中用:或v-bind绑定;子组件props属性中接收,然后调用接收的函数传值。
    • 使用$emit
      父组件中定义事件回调函数,在子组件标签中用@或v-on绑定,或者给子组件标签添加ref,在mounted钩子中用this.$refs.xxx.$on()绑定;子组件使用$emit触发事件进行传值,使用$off解绑事件。
  • 兄弟组件→兄弟组件
    • 使用全局事件总线(GlobalEventBus)(适用于任意组件间通信)
      在new Vue中在beforeCreate钩子中用Vue.prototype.$bus = this安装全局事件总线;A组件想接收数据,则在A组件中定义事件回调函数,在mounted钩子中用this.$bus.$on()绑定事件(最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件);B组件传递数据,则用this.$bus.$emit触发事件传值。
    • 使用第三方库
      例如消息订阅与发布(pubsub),适用于任意组件间通信。在A、B组件中import pubsub from 'pubsub-js';A组件想接收数据,则在A组件中定义订阅回调函数,在mounted钩子中用this.pid = pubsub.subscribe()订阅消息(最好在beforeDestroy钩子中,用pubsub.unsubscribe(pid)去取消订阅);B组件传递数据,则用pubsub.publish()发布消息。

nextTick

  • 语法
    this.$nextTick(回调函数)
  • 作用
    在下一次DOM更新结束后执行其指定的回调。
  • 应用场景
    当改变数据后,要基于更新后的DOM进行某些操作时,要在nextTick所指定的回调函数中执行。
  • 说明
    Vue在更新DOM时是异步执行的。只要侦听到数据变化,Vue将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个watcher被多次触发,只会被推入到队列中一次。nextTick方法会在队列中加入一个回调函数,确保该函数在前面的dom操作完成后才调用。

插槽

  • 作用
    让父组件可以向子组件指定位置插入html结构,也是一种组件间通信的方式,适用于父组件 ===> 子组件。
  • 分类
    默认插槽、具名插槽、作用域插槽

默认插槽

父组件中:

  
html结构
子组件中:

具名插槽

父组件中:

  

  

子组件中:

作用域插槽
理解:数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。(games数据在Category组件中,但使用数据所遍历出来的结构由App组件决定)

父组件中:

  

  

子组件中:



Vuex

前端开发基础——Vue_第2张图片

  • 概念
    在Vue中实现集中式状态(数据)管理的一个Vue插件,对Vue应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间的通信方式,且适用于任意组件间通信。
  • 应用场景
    多个组件需要共享数据时。Vuex一般用于中大型web单页应用中对应用的状态进行管理,对于一些组件间关系较为简单的小型应用,使用Vuex的必要性不是很大,因为完全可以用组件prop属性或者EventBus来完成组件之间的通信,Vuex更多地用于解决跨组件通信以及作为数据中心集中式存储数据。
  • 5个属性
    • state
      Vuex使用单一状态树,即每个应用将仅仅包含一个store实例,存放的数据状态,不可以直接修改里面的数据。
    • mutations
      mutations定义的方法动态修改state。
    • getters
      类似Vue的计算属性,主要用来过滤一些数据。
    • actions
      actions与mutations类似,但其可以异步操作数据。
    • module
      当应用变得庞大复杂时,拆分store为具体的module模块。
  • 搭建Vuex环境
    创建文件:src/store/index.js
    在main.js中引入store,创建vm时传入store配置项
  • 基本使用

① 初始化数据、配置actions、配置mutations,操作文件store.js

//引入Vue核心库
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
//应用Vuex
Vue.use(Vuex)

const actions = {
  //响应组件中加的动作
  add(context, value) {
    context.commit("ADD", value)
  }
}

const mutations = {
  //执行加
  ADD(state, value) {
    state.sum += value
  }
}

//初始化数据
const state = {
  sum: 0
}

//创建并暴露store
export default new Vuex.Store({
  actions,
  mutations,
  state
})

② 组件中读取vuex中的数据:$store.state.sum
③ 组件中修改vuex中的数据:$store.dispatch('actions中的方法名', 数据)或$store.commit('mutations中的方法名', 数据)
:若没有网络请求或其他业务逻辑,组件中也可以越过actions,即不写dispatch,直接编写commit。

  • getters的使用

概念:当state中的数据需要经过加工后再使用时,可以使用getters加工。

使用:
① 在store.js中追加getters配置

......
const getters = {
  bigSum(state) {
    return state.sum * 10
  }
}

//创建并暴露store
export default new Vuex.Store({
  ......
  getters
})

② 组件中读取数据:$store.getters.bigSum

  • 四个map方法的使用

mapState方法:用于映射state中的数据为计算属性

computed: {
  //借助mapState生成计算属性,对象写法
  ...mapState({sum: 'sum', school: 'school', subject: 'subject'})

  //借助mapState生成计算属性,数组写法
  ...mapState(['sum', 'school', 'subject'])
}

mapGetters方法:用于映射getters中的数据为计算属性

computed: {
  //借助mapGetters生成计算属性,对象写法
  ...mapGetters({bigSum: 'bigSum'})

  //借助mapGetters生成计算属性,数组写法
  ...mapGetters(['bigSum'])
}

mapActions方法:用于生成与actions对话的方法,即包含$store.dispatch(xxx)的函数

methods:{
  //借助mapActions生成,对象写法
  ...mapActions({incrementOdd: 'jiaOdd', incrementWait: 'jiaWait'})

  //借助mapActions生成,数组写法
  ...mapActions(['jiaOdd', 'jiaWait'])
}

mapMutations方法:用于生成与mutations对话的方法,即包含$store.commit(xxx)的函数

methods:{
  //借助mapMutations生成,对象形式
  ...mapMutations({increment: 'JIA', decrement: 'JIAN'})

  //借助mapMutations生成,数组形式
  ...mapMutations(['JIA', 'JIAN'])
}

备注:mapActions与mapMutations在使用时,若需要传递参数,则需要在模板中绑定事件时传递好参数,否则参数是事件对象。

  • 模块化+命名空间
    目的:让代码更好维护,让多种数据分类更加明确。
//修改store.js
const countAbout = {
  namespaced: true,//开启命名空间
  state: { ... },
  mutations: { ... },
  actions: { ... },
  getters: { ... }
}

const personAbout = {
  namespaced: true,//开启命名空间
  state: { ... },
  mutations: { ... },
  actions: { ... }
}

const store = new Vuex.store({
  modules: {
    countAbout,
    personAbout
  }
})

① 开启命名空间后,组件中读取state数据:

//方式一:直接读取
this.$store.state.personAbout.list
//方式二:借助mapState读取
...mapState('countAbout', ['sum', 'school', 'subject'])

② 开启命名空间后,组件中读取getters数据:

//方式一:直接读取
this.$store.getters['personAbout/firstPersonName']
//方式二:借助mapGetters读取
...mapGetters('countAbout', ['bigSum'])

③ 开启命名空间后,组件中调用dispatch:

//方式一:直接dispatch
this.$store.dispatch('personAbout/addPersonWang', person)
//方式二:借助mapActions
...mapActions('countAbout', {incrementOdd: 'jiaOdd', incrementWait: 'jiaWait'})

④ 开启命名空间后,组件中调用commit:

//方式一:直接commit
this.$store.commit('personAbout/ADD_PERSON', person)
//方式二:借助mapMutations
...mapMutations('countAbout', {increment: 'JIA', decrement: 'JIAN'})

VueRouter

  • 基本使用

① 安装vue-router,应用插件Vue.use(VueRouter)
② 编写router配置项:

//引入VueRouter
import VueRouter from 'vue-router'
//引入组件
import About from '../components/About'
import Home from '../components/Home'

//创建router实例对象,去管理一组一组的路由规则
const router = new VueRouter({
  routes: [
    {
      path: '/about',
      component: About
    },
    {
      path: '/home',
      component: Home
    }
  ]
})

//暴露router
export default router

③ 实现切换(active-class可配置高亮样式)
About
④ 指定展示位置


① 路由组件通常存放在pages文件夹,一般组件通常存放在components文件夹。
② 通过切换,“隐藏”了路由组件,默认是被销毁掉的,需要的时候再去挂载。
③ 每个组件都有自己的route属性,里面存储着自己的路由信息。
④ 整个应用只有一个router,可以通过组件的$router属性获取到。

  • 嵌套路由(多级路由)

① 配置路由规则,使用children配置项:

routes: [
  {
    path: '/about',
    component: About,
  },
  {
    path: '/home',
    component: Home,
    children: [ //通过children配置子级路由
      {
        path: 'news', //此处一定不要写:/news
        component: News,
      },
      {
        path: 'message', //此处一定不要写:/message
        component: Message,
      }
    ]
  }
]

② 跳转(要写完整路径):
News

  • 路由的query参数

① 传递参数


跳转


跳转

② 接收参数:

$route.query.id
$route.query.title
  • 命名路由
    作用:可以简化路由的跳转。

① 给路由命名:

{
  path: '/demo',
  component: Demo,
  children: [
    {
      path: 'test',
      component: Test,
      children: [
        {
          name: 'hello', //给路由命名
          path: 'welcome',
          component: Hello
        }
      ]
    }
  ]
}

② 简化跳转:


跳转


跳转


跳转
  • 路由的params参数

① 配置路由,声明接收params参数

{
  path: '/home',
  component: Home,
  children: [
    {
      path: 'news',
      component: News,
    },
    {
      path: 'message',
      component: Message,
      children: [
        {
          name: 'xiangqing',
          path: 'detail/:id/:title', //使用占位符声明接收params参数
          component: Detail
        }
      ]
    }
  ]
}

② 传递参数


跳转


跳转

注意:路由携带params参数时,若使用to的对象写法,则不能使用path配置项,必须使用name配置!
③ 接收参数

$route.params.id
$route.params.title
  • 路由的props配置
    作用:让路由组件更方便的收到参数
{
  name: 'xiangqing',
  path: 'detail',
  component: Detail,
  //第一种写法:props值为对象,该对象中所有的key-value的组合最终都会通过props传给Detail组件
  // props: {a: 900}

  //第二种写法:props值为布尔值,布尔值为true,则把路由收到的所有params参数通过props传给Detail组件
  // props: true

  //第三种写法:props值为函数,该函数返回的对象中每一组key-value都会通过props传给Detail组件
  props($route) {
    return {
      id: $route.query.id,
      title: $route.query.title
    }
  }
}
  • 的replace属性
    • 作用:控制路由跳转时操作浏览器历史记录的模式
    • 浏览器的历史记录有两种写入方式:分别为push和replace,push是追加历史记录,replace是替换当前记录。路由跳转时候默认为push
    • 如何开启replace模式:News
  • 编程式路由导航
    作用:不借助实现路由跳转,让路由跳转更加灵活
this.$router.push({
  name: 'xiangqing',
  params: {
    id: xxx,
    title: xxx
  }
})

this.$router.replace({
  name: 'xiangqing',
  params: {
    id: xxx,
    title: xxx
  }
})
this.$router.forward() //前进
this.$router.back() //后退
this.$router.go() //可前进也可后退

keep-alive

  • 定义及作用
    keep-alive是Vue内置的一个组件,可以使被其包含的组件保持挂载,不被销毁,避免重新渲染。一般结合路由/动态组件一起使用,用于缓存组件。
  • 属性
属性 说明
include 字符串或正则表达式 只有名称匹配的组件会被缓存
exclude 字符串或正则表达式 任何名称匹配的组件都不会被缓存
max 数字 最多可以缓存多少组件实例
  • 生命周期钩子
    • activated,组件被激活时触发。
    • deactivated,组件失活时触发。

路由守卫

  • 作用
    对路由进行权限控制
  • 分类
    全局守卫、独享守卫、组件内守卫

全局守卫

//全局前置守卫:初始化时执行、每次路由切换前执行
router.beforeEach((to, from, next) => {
  if(to.meta.isAuth) { //判断当前路由是否需要进行权限控制
    if(localStorage.getItem('school') === 'xxx') { //权限控制的具体规则
      next() //放行
    } else {
      alert('暂无权限查看')
    }
  } else {
    next() //放行
  }
})

//全局后置守卫,初始化时执行、每次路由切换后执行
router.afterEach((to, from) => {
  if(to.meta.title) {
    document.title = to.meta.title //修改网页的title
  } else {
    document.title = 'vue_test'
  }
})

独享守卫

beforeEnter(to, from, next) {
  if(to.meta.isAuth) { //判断当前路由是否需要进行权限控制
    if(localStorage.getItem('school') === 'xxx') {
      next()
    } else {
      alert('暂无权限查看')
    }
  } else {
    next()
  }
}

组件内守卫

//进入守卫:通过路由规则,进入该组件时被调用
beforeRouterEnter(to, from, next) {
},
//离开守卫:通过路由规则,离开该组件时被调用
beforeRouterLeave(to, from, next) {
}

路由器hash和history工作模式

  • 对于一个url来说,#及其后面的内容就是hash值。
  • hash值不会包含在HTTP请求中,即hash值不会带给服务器。
  • 在路由器配置文件中使用mode: 'hash'mode: 'history'来设置路由器工作模式,默认为hash模式。
  • hash模式
    ①地址中永远带着#号,不美观。
    ②若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法。
    ③兼容性较好。
  • history模式
    ①地址干净,美观。
    ②兼容性和hash模式相比略差。
    ③应用部署上线时需要后端人员支持,解决刷新页面服务端404问题。

Vue中diff算法理解

diff算法是一种通过同层的树节点进行比较的高效算法,在Vue中作用于虚拟DOM渲染成真实DOM的新旧VNode比较。

  • 比较策略
    深度优先,同层比较。
    ① 只会在同层级间比较,不会跨层级比较。
    ② 比较过程中,循环从两边向中间收拢。
    ③ 如果tag不同,则直接删掉重建,不再深度比较。
    ④ 如果tag和key都相同,则认为是相同节点,不再深度比较。
  • 具体原理
    • 当数据发生改变时,set()方法会调用Dep.notify()通知所有订阅者Watcher,订阅者就会调用patch()给真实的DOM打补丁,更新相应的视图。
    • patch()函数对新旧VNode做简单判断,其前两个参数为oldVnode和Vnode,分别代表旧VNode和新VNode。
      ① 判断Vnode是否存在。若Vnode不存在,则直接删除旧节点。
      ② Vnode存在,判断oldVnode是否存在。若oldVnode不存在,则新增整个节点。
      ③ Vnode和oldVnode都存在,通过sameVnode()判断两者是否为相同节点。若是相同节点,则调用patchVnode()进行比较。
      ④ 若不是相同节点,则创建新节点,删除旧节点。
    • patchVnode()函数对新旧VNode进行比较。
      ① 判断oldVnode和Vnode是否为相等节点。若是相等节点,则结束。
      ② 更新节点属性。
      ③ 判断Vnode是否为文本节点。若是文本节点,则更新节点文本内容。
      ④ 若不是文本节点,则判断oldVnode和Vnode是否有子节点。
      a. 若两者都没有子节点,若oldVnode是文本节点,则清空内容。
      b. 若Vnode有子节点,oldVnode没有子节点,则新增所有子节点。
      c. 若Vnode没有子节点,oldVnode有子节点,则删除所有旧子节点。
      d. 若两者都有子节点但不完全一致,则调用updateChildren()进行比较。
    • updateChildren()对新旧VNode的子节点进行比较。oldCh为oldVnode的子节点数组,newCh为Vnode的子节点数组。在两数组中设置如下变量:oldStartVnode为旧头VNode,oldEndVnode为旧尾VNode,newStartVnode为新头VNode,newEndVnode为新尾VNode;oldStartIdx为旧头索引,newStartIdx为新头索引,oldEndIdx为旧尾索引,newEndIdx为新尾索引。
      ① 若oldStartVnode和newStartVnode相同,则对二者执行patchVnode(),且oldStartIdx和newStartIdx都加1。
      ② 若不相同,则比较oldEndVnode和newEndVnode。若oldEndVnode和newEndVnode相同,则对二者执行patchVnode(),且oldEndIdx和newEndIdx都减1。
      ③ 若不相同,则比较oldStartVnode和newEndVnode。若oldStartVnode和newEndVnode相同,则对二者执行patchVnode(),且把oldStartVnode移动到oldEndVnode后面,oldStartIdx加1,newEndIdx减1。
      ④ 若不相同,则比较oldEndVnode和newStartVnode。若oldEndVnode和newStartVnode相同,则对二者执行patchVnode(),且把oldEndVnode移动到oldStartVnode前面,oldEndIdx减1,newStartIdx加1。
      ⑤ 若不相同,则建立以oldCh中VNode对应key值为key,对应index为value的映射表,然后将newStartVnode的key与映射表中的对比。若有匹配的,则对newStartVnode和匹配的VNode执行patchVnode(),且把匹配的VNode移动到oldStartVnode前面。若没有匹配的,则创建一个新节点放到newStartIdx位置。
      ⑥ newStartIdx加1,下一轮循环。
      最终,oldStartIdx>oldEndIdx或者newStartIdx>newEndIdx表明oldCh和newCh中至少有一个已经遍历完了,结束比较。

你可能感兴趣的:(Vue,前端,vue.js,前端,前端框架)