Vue必备小知识

目录

什么是MVVM?

MVVM的优缺点有哪些?

谈谈对Vue生命周期的理解?

在Vue中网络请求应该放在哪个生命周期中发起?

Vue组件之间如何进行通信?

computed和watch的区别是什么?

Vue双向绑定原理?

Object.defineProperty和Proxy的优缺点?

如何理解Vue的响应式系统?

基本原理

观察者模式

Vue中的key到底有什么用?

diff算法

key的常见应用场景


什么是MVVM?

Model-View-ViewModel 模式,最早在 2005 年微软推出的基于 Windows 的⽤户界⾯框架 WPF 中提出,而最早采用 MVVM 的前端框架是 2010 年发布的 Knockout。

Model 层

对应数据层的域模型,主要用来做域模型的同步。

通过 Ajaxfetch 等 API 完成客户端和服务端业务模型的同步。

在分层关系中,它主要⽤于抽象出 ViewModel 层中视图的 Model。

View 层

作为视图模板存在,其实在 MVVM 中整个 View 就是⼀个动态模板。

除了用于定义结构和布局之外,它还展示了 ViewModel 层的数据和状态。

View 层并不负责状态的实际处理,它只是做:数据绑定声明、 指令声明、 事件绑定声明。

ViewModel 层

负责暴露数据给 View 层,并对 View 层中的数据绑定声明、 指令声明、 事件绑定声明进行实际的业务逻辑。

ViewModel 底层会做好绑定属性的监听,当 ViewModel 中的数据变化时,View 层会自动进行更新;⽽当 View 中声明了数据的双向绑定(表单元素),框架也会监听 View 层(表单元素)值的变化,⼀旦变化,则 View 层绑定的 ViewModel 中的数据也会得到⾃动更新。

MVVM的优缺点有哪些?

优点

  1. 实现了视图(View)和模型(Model)的分离,降低代码耦合、提⾼视图或逻辑的复⽤性

⽐如:View 可以独⽴于 Model 变化和修改,⼀个 ViewModel 可以绑定于不同的 "View",当 View 发生变化时 Model 一定会随之改变,而当 Model 变化时则 View 可以不变。我们可以把⼀些视图逻辑放在⼀个 ViewModel ⾥,以此让多个 View 重⽤这段视图逻辑。

  1. 提⾼了可测试性:ViewModel 的存在可以帮助开发者更好地编写测试代码

  2. 能⾃动更新 DOM:利⽤双向绑定,数据更新后视图⾃动更新,让开发者从繁琐的⼿动操作 DOM 中解放出来

缺点

  1. Bug 很难被调试:因为使⽤了双向绑定的模式,当我们看到界⾯发生异常了,有可能是 View 的代码产生的 Bug,也有可能是Model 代码的问题。数据绑定使得⼀个位置的 Bug 被快速传递到别的位置,要定位原始出问题的地⽅就变得不那么容易了。另外,数据绑定的声明是指令式地写在 View 模版中的,它们没办法打断点进行 Debug

  2. 在⼀个⼤的模块中 Model 也会很⼤,虽然使⽤上来说⽅便了,也能很容易的保证了数据的⼀致性,但如果⻓期持有不释放内存,就会造成更多的内存消耗

  3. vue3如果你没做特别处理,它不帮你双向绑定

  4. 对于⼤型的图形应⽤程序,视图状态较多,ViewModel 的构建和维护的成本都会⽐较⾼

谈谈对Vue生命周期的理解?

生命周期的概念

每个 Vue 实例都有⼀个完整的⽣命周期:

  1. 开始创建

  2. 初始化数据

  3. 编译模版

  4. 挂载 DOM

  5. 渲染、更新数据 => 重新渲染

  6. 卸载

这⼀系列过程我们称之为 Vue 的⽣命周期。

各个生命周期的作用

生命周期执行时机
beforeCreate在组件实例被创建之初、组件的属性⽣效之前被调用
created在组件实例已创建完毕。此时属性也已绑定,但真实DOM还未⽣成,$el 还不可⽤
beforeMount在组件挂载开始之前被调⽤。相关的 render 函数⾸次被调⽤
mounted在 el 被新建的 vm.$el 替换并挂载到实例上之后被调用
beforeUpdate在组件数据更新之前调⽤。发⽣在虚拟 DOM 打补丁之前
updated在组件数据更新之后被调用
activited在组件被激活时调⽤(使用了 <keep-alive> 的情况下)
deactivated在组件被隐藏时调⽤(使用了 <keep-alive> 的情况下)
beforeDestory在组件销毁前调⽤
destoryed在组件销毁后调⽤(中断了数据的渲染)

生命周期示意图

在Vue中网络请求应该放在哪个生命周期中发起?

可以在 mountedcreated 这两个⽣命周期中调⽤发起网络请求。

beforeCreate也可以,发送请求,请求完可以访问vue渲染后的dom

div ref='dev'
beforeCreate(){
this.$nextTick(()=>{})
xxx().then(()=>{
   访问vue渲染后的dom
   this.$refs.dev
})
}

Vue组件之间如何进行通信?

ref传值

子组件标签加入ref属性 this.$refs.ref值===子组件的实例对象(this)

子组件内访问父组件 this.$parent

props$emit + v-on**

通过 props 将数据在组件树中进行⾃上⽽下的传递;

通过 $emitv-on 来作信息的向上传递。

组件传值
  props与emit
    父组件
        son  
  父传子(父传子属性)
     传:  son 属性名=值
     收:
        子组件内
           props:{
             属性名:{
             type:[String,Number],
             default:如果是原始值,直接定义,如果是引用值,()=>{return 引用值},
             required:true, 必传
             validator:(value)=>{
            return value>10
             return boolean值 true:正常使用,false:报错
             }
             }
           }
           属性特点:单向数据流(原始值不可修改,引用值,只要不修改它的引用地址,它的堆可以随便修改)
           栈不可修改,堆随便修改,只适用于vue2.0
           全不可修改 vue3.0
  子传父(子触发父方法)
      绑定  son  @子组件方法名=“父组件方法名”
      触发:子组件内触发
            this.$emit('子组件方法名',参数)
 

EventBus

可通过 EventBus 进⾏信息的发布与订阅。

用于所有组件间的传值

用法:
   1:Vue.prototype.$bus=new Vue()
   2:监听:
       this.$bus.$on('方法名',(形参)=>{...代码})
   3:触发:
       this.$bus.$emit('方法名',实参)
   4:销毁
      this.$bus.$off('方法名')

Vuex

全局状态管理库。可通过它来进行全局数据流的管理。

基本使用:
1:安装  npm i vuex
2:导入   import Vuex from 'vuex'
3:注册  Vue.use(Vuex)
4:实例化
import user from './modules/user'
   const store=new Vuex.Store({
   state:{},
   mutations:{},
   actions:{},
   getters:{},
   modules:{user}
   })
 5:暴露出去  export default store
 6:挂载
    new Vue({
    store
    })
​
1:src/store/modules/模块名.js
  模块名.js
  const state={
     userInfo:"老王"  
     // 基本使用:this.$store.state.user.userInfo
     // map使用
         1:导入  import {mapState} from 'vuex'
         2:定义
            computed:{
            ...mapState('模块名(user)',['字段名(userInfo)'])
            }
  }
  const mutations={
     方法名(setUserInfo)(state,value){
       state.userInfo=value
       this.commit('模块名/方法名2',参数值)
       this===this.$store
     },
     方法名2(state,value){
     
     }
     基本使用:this.$store.commit('模块名(user)/方法名(setUserInfo)',参数值)
     map使用: 1:导入  import {mapMutations} from 'vuex'
              2:定义
                 methods:{  ...mapMutations(['模块名/方法名'])}   this['模块名/方法名'](参数值)
                 methods:{  ...mapMutations(‘模块名’,['方法名'])}   this.方法名(参数值)
  
  }
  const actions={
       方法名getUserInfo(store,value){
       store:commit,dispatch,state,rootState,getters,rootGetters
         xxx().then(res=>{
         数据修改userInfo
         store.commit('setUserInfo',res)
         // 调用本模块的mutations  store.commit('本模块的mutations方法名',参数值)
         // 调其它模块的mutations   store.commit('模块名/其它模块mutations方法名',参数值,{root:true})
         // 调用本模块的actions  store.dispatch('本模块的actions方法名',参数值)
         // 调用其它模块的actions方法  store.dispatch('模块名/其它模块actions',参数值,{root:true})
         // 本模块数据  store.state
         // 其它模块数据  store.rootState.模块名.字段名
         // getters方法调用
              本模块getters  store.getters.方法名
              其它模块的getters  store.rootGetters['模块名/其它模块的getters方法名']
         })
       }
  
  
  
  
  }
    export default {
    namespaced:true,
    state,
    mutaions,
    actions
    }
 2:
​

$attrs$listeners

在 Vue 2.4 版本中加⼊的 $attrs$listeners 可以用来作为跨级组件之间的通信机制。

$attrs:非props属性,父组件传值子组件,子组件没有使用props接收的属性,它会加入到$attrs内
$listeners:子组件标签身上绑定的所有的方法
father  <son  xxx=123 @abc="..." />
son
   <sonson v-bind="$attrs" v-on="$listeners" />
    <div v-bind="$attrs" v-on="$listeners" ></div>

provideinject

由于 provideinject 可以允许⼀个祖先组件向它的所有⼦孙组件注⼊⼀个依赖(不论组件层次有多深),并在其上下游关系成⽴的时间⾥始终⽣效,因此这种机制也就成为了一种跨组件通信的手段。                     ​ ​ ​ ​

父辈向后代组件传值
用法:
  app.vue   div  v-if="bol"
      data(){
      return {
      bol:true,
      abc:{
        a:10
      }
      }
      }
      methods:{
      fn(参数值){
       ....
      },
      reLoad(){
      this.bol=false
      this.$nextTick(()=>{
      this.bol=true
      })
      }
      }
      provide(){
      return  {
        abc:this.abc,
        fn:this.fn,
        reLoad:this.reLoad
        }
      }
      }
      app.vue的provide属性访问的机会都没有
所有app.vue后代组件都可使用inject,单向数据流
    inject:['abc'],
    inject:{
      xxx:{
      from:'abc',
      default:默认值
      },
      fn:{
      default:...
      },
      reLoad:{
      default:...
      }
    }
    this.fn(123)
    this.reLoad()

另外还有一些方式使用场景有限,在此不介绍了。

可以阅读参考文章:Vue中的8种组件通信方式

computed和watch的区别是什么?

computed

  1. 它是计算属性。主要用于值的计算并一般会返回一个值。所以它更多⽤于计算值的场景

  2. 它具有缓存性。当访问它来获取值时,它的 getter 函数所计算出来的值会进行缓存,只有当它依赖的属性值发生了改变,那下⼀次再访问时才会重新调⽤ getter 函数来计算

  3. 它适⽤于计算⽐较消耗性能的计算场景

watch:某个值change事件

  1. 它更多的是起到 “观察” 的作⽤,类似于对数据进行变化的监听并执行回调。主要⽤于观察 props$emit 或本组件的值,当这些值发生变化时,执⾏回调

  2. 它不具有缓存性。当⻚⾯重新渲染时,即使值没发生变化也会执⾏

建议

  1. 当目的是进⾏数值计算,且依赖于其他数据,那么推荐使用 computed

  2. 当需要在某个数据发生变化的同时做⼀些稍复杂的逻辑操作,那么推荐使⽤ watch

Vue双向绑定原理?

在 Vue 2.x 中,利⽤的是 Object.defineProperty 去劫持对象的访问器(Getter、Setter),当对象属性值发⽣变化时可获取变化,然后根据变化来作后续响应;

数据更新了,修改视图,Object.defineProperty 对数据实现set监听,数据修改了就触发set,set就会将视图使用该数据的所有地方更新

视图更新了修改数据,dom绑定了相应事件做相应处理

vue2.x:新增属性无法实现双向绑定

在 Vue 3.0 中,则是通过 Proxy 代理对象进⾏类似的操作。

proxy:监听该对象的所有属性,proxy不修改原有数据,只是做了代理,产生了一个新的对象对原有数据进行代理

Object.defineProperty和Proxy的优缺点?

Proxy

  • 可以直接监听整个对象,⽽⾮是对象的属性

  • 可以直接监听数组的变化

  • 拦截⽅法丰富:多达13种,不限于 applyownKeysdeletePropertyhas 等。比 Object.defineProperty 强大很多

  • 返回的是⼀个新对象,可以在不影响原对象的情况下,只操作新对象来达到⽬的;⽽ Object.defineProperty 只能遍历原对象属性并直接修改原对象

  • 受到各浏览器⼚商的重点持续性能优化,能享受到作为新标准的性能红利

Object.defineProperty

  • 兼容性较好(可⽀持到 IE9)

如何理解Vue的响应式系统?

(考察MVVM) M: model数据模型, V:view视图模型, VM: viewModel视图数据模型

双向:

  1. 视图变化了, 数据自动更新 => 监听原生的事件即可, 输入框变了, 监听输入框input事件

  2. 数据变化了, 视图要自动更新 => vue2 和 vue3

基本原理

vue2.0 数据劫持: Object.defineProperty (es5)

vue3.0 数据劫持: Proxy (es6)

分析 :此题考查 Vue的 MVVM 原理

解答: Vue的双向绑定原理其实就是 MVVM 的基本原理, Vuejs官网已经说明, 实际就是通过 Object.defineProperty方法 完成了对于Vue实例中数据的 劫持, 通过对于 data中数据 进行set的劫持监听, 然后通过观察者模式, 通知 对应的绑定节点 进行节点数据更新, 完成数据驱动视图的更新

简单概述 : 通过Object.defineProperty 完成对于数据的劫持, 通过观察者模式, 完成对于节点的数据更新

观察者模式

观察者模式: 当对象间存在 一对多 关系时,则使用观察者模式(Observer Pattern)。

比如,当一个对象或者数据被修改时,则会自动通知依赖它的对象。

意图:定义对象间的一种 一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

拍卖会的时候,大家相互叫价,拍卖师(Dep)会观察最高标价(Object.defineProperty),

一旦最高价变化了, 然后通知给其它竞价者(watcher观察者 - 订阅者, 订阅了价格的变化),这就是一个观察者模式

下图为Vue框架在数据初始化中使用观察者模式的示意图:

Dep要进行 依赖收集,并通过一个subs数组, 记录观察者Watcher,

Watcher 分为渲染 watcher、计算属性 watcher、侦听器 watcher三种

收集依赖: 简单点说就是谁借了我的钱,我就把那个人 记下来 ,以后我的钱少了 我就通知他们说我没钱了

<div>
  <p>{{ msg }}</p>   // Watcher1(渲染), 依赖于msg
</div>

<div>
  <h1>{{ car }}</h1>   // Watcher2(渲染),  依赖于car
</div>

<div>
  <h1>{{ myMsg }}</h1>   // Watcher3(渲染), 依赖于myMsg
</div>

computed: {
  myMsg () {
    console.log('计算属性重新计算了')
    return this.msg + '20'  // Watcher4(计算属性中), 依赖于msg, msg变了重新计算
  }
}

watch: {
  msg (newValue) {
    console.log('新的msg', newValue) // Watcher5(侦听器), 将来msg变化, 这边要执行这个函数
  }
}

------------------------------------------------------------------

// 收集依赖 (dep结构有点类似于二维数组, (Map结构))   arr.type="msgDep"
dep: [
  msgDep: [Watcher5(侦听器依赖), Watcher4(计算属性依赖), Watcher1(渲染)],
  carDep: [Watcher2(渲染)],
  myMsgDep: [Watcher3(渲染)]
]


// Watcher
{
  callback: Function, (数据变化后, 需要执行的回调)
  isRenderWatcher: Boolean, (是否是render的watcher, 是否要触发视图的更新, 往后放, 最后统一虚拟dom对比, 统一更新)
  ...
}

比如: 假定数据 money 变了, 那么没有任何与money相关的观察者, 就不需要进行任何更新操作, 也不需要执行任何的监视函数

然而: 假定数据 msg 变了, 就会通知到相关的Watcher, 且优先通知侦听器Watcher和计算属性Watcher, 后进行统一的渲染更新

  1. 通知侦听器Watcher, 立刻执行配置的函数, console.log('新的msg', newValue)

  2. 通知计算属性Watcher, 计算属性依赖的值变了, 需要重新计算 且更新后, myMsg 变化了, 需要进行进行视图的渲染 (render) (--- 要更新, 等着---)

  3. 通过到watcher1, 渲染Watcher (---要更新---)

  4. 最后统一进行, 新旧虚拟dom的对比, 完成视图的更新

当数据状态发生改变时,会被 Object.defineProperty 监听劫持到, 会通知到 Dep, 并根据收集的依赖关系,

让订阅者Watcher进行数据更新(update)操作 , 派发更新

总结概述: vue采用的是观察者模式, 是一种一对多的关系, 一上来vue在解析渲染时, 会进行依赖收集, 会将渲染 watcher、计算属性 watcher、侦听器 watcher, 都收集到dep中,将来Object.defineProperty 监听到数据变化, 就根据依赖关系, 派发更新

Vue中的key到底有什么用?

key 是为 Vue 中的虚拟 DOM 节点(vNode)标记唯⼀性的 id。

作用: 给虚拟dom添加标识, (优化复用对比策略, 优化渲染性能)

主要考察:

  1. vue 的更新机制 (差异化更新) 对比新旧虚拟dom 进行更新视图

    为什么对比虚拟dom, 而不对比真实的dom ? 真实的dom太复杂, 对比起来性能太差

  2. 虚拟dom: 使用 js 对象的方式, 模拟真实的 dom 结构

    属性的量大大的减少了, 没有真实dom的那么多无效的属性, 对比起来性能高很多

  3. diff 算法: 默认的对比(diff) 机制, 同层兄弟元素, 是按照下标进行对比的, 但是加了 key, 就相当于给虚拟dom加了个标识

    对比策略, 就是对相同key的元素进行对比了, 在列表v-for中, key的使用尤为常见, 可以用于优化渲染性能

diff算法

diff 算法的基本策略 (对比新旧虚拟dom差异的一种算法):

  1. 由于dom结构是一个树形结构, 就算是对比新旧虚拟dom, 一样是在对比树形结构

    而树形结构, 每往下都一层, 遍历成本就越高

  2. 为了避免一些无效的对比, diff算法, (tree diff) 优先比较树的根节点

    如果根节点元素类型(组件), 是一致的, 才考虑复用元素内部的结构

    如果根节点元素类型(组件), 不一致, 直接销毁, 重新构建新的结构

    旧
    <div>
        <span>我是内容</span>
    </div>
    ​
    新
    <li>
        <a href="#">我是内容</a>
        <span>我是内容</span>
    </li>

大大的提升了对比的效率

  1. 同一根节点下, 同级兄弟元素, 默认的对比策略: 按照下标对比

    旧
    <div class="box">
        <h1>大标题</h1>
        <p>内容</p>
    </div>
    ​
    新
    <div class="message">
        <h1>测试标题</h1>
        <p>内容</p>
    </div>
  2. 同一根节点下, 同级兄弟元素, 如果配置 key 属性, 那么对比的策略, 就不是按照下标, 而是按照key进行 一一对比

    旧
    <div class="box">
        <h1 key="201">大标题</h1>
        <p key="202">内容</p>
    </div>
    ​
    新
    <div class="message">
        <h1 key="200">大标题</h1>   // 新结构
        <p key="202">内容</p>    // 对比出来, 原来结构有key="202", 进行对比, 完成了复用
    </div>

    如果你希望一个结构, 并不进行复用, 就是希望某个组件某个结构, 能够重新构建, 可以设置一个不同的 key

key的常见应用场景

key 的常见应用场景 => v-for, v-for 遍历的列表中的项的顺序, 非常的容易改变

1 往后面加, 默认的对比策略, 按照下标, 没有任何问题

// 旧
<ul>
  <li>张三</li>
  <li>李四</li>
</ul>
​
// 新
<ul>
  <li>张三</li>
  <li>李四</li>
  <li>王五</li>
</ul>

2 往前面加, 由于下标变了, 如果按照之前的下标对比, 元素是混乱的, 策略: 加上key

一旦加上了key, 就是按照 key 进行新旧dom的对比了

// 旧 
<ul>
  <li key="17">张三</li>
  <li key="31">李四</li>
</ul>
​
// 新  [ { id: 17, name: '张三' }, ... ]
<ul>
  <li key="52">王五</li>
  <li key="17">张三</li>
  <li key="31">李四</li>
</ul>

总结: key 就是给 虚拟dom 添加了一个 标识, 优化了对比策略!!!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值