Vue相关知识点

Vue的优点

说说你对SPA单页面的理解,它的优缺点是什么

SPA首屏加载速度慢怎么解决?

Vue框架的MVVM模式

 Vue中computed和watch的区别,以及适用场景

Vue生命周期有哪些?每个生命周期适合那些场景?

Vue第一次页面加载会触发那些钩子函数

请求数据放到created和mounted哪个阶段比较好?

父子组件生命周期的顺序

父组件如何监听子组件的生命周期

vue 组件如何通讯(常见方法)

10、v-show 和 v-if 的区别

11. 为何在 v-for 中使用 key

12、描述组件渲染和更新的过程

13、如何自己实现v-model?以及双向数据绑定v-model的实现原理

14、对MVVM 的理解

15、 computed有何特点

16、为何组件 data  必须是一个函数?

17、多个层级间重用相同的props时,避免每个层级重复定义相通的props,如何将组件所有props传递给子组件?

18、多个组件有相同的逻辑,如何抽离?

19、何时使用异步组件?

20、何时需要使用keep-alive

21、何时需要使用beforeDestory

22、什么是作用域插槽

23、vuex 中 action 和 mutation 区别

24、 Vue-router 常用的路由模式?以及他们实现的原理? hash 和 history两者有何区别?如何配置vue-router异步加载?

25、请用vnode 描述一个DOM结构?

26、监听data 变化的核心API是什么?如何深度监听、监听数组?有何缺点?

27、请描述响应式原理

28、diff算法时间复杂度?

29、简述diff算法过程

30、vue为何是异步渲染,$nextTick何用?

31、vue 常见的性能优化有哪些?

32、Vue2、Vue3和 React 三者的diff 算法有什么区别?

33、Vue React 为何循环时必须使用key?

34、你使用Vue 遇到过哪些坑?

35、如何统一监听Vue组件报错?

 

Vue的优点(优势)是什么?

  • 轻量级框架:大小只有几十kb
  • 简单易学:国人开发,中文文档,易于理解和学习
  • 双向数据绑定:通过MVVM思想实现数据的双向绑定
  • 组件化模式:提高代码利用率,且让代码更好维护
  • 使用虚拟DOM:原生js对DOM频繁操作时,浏览器会不停地渲染新的DOM,导致页面非常卡顿。vue是预先通过js进行各种计算把最总得DOM操作计算出来并优化,最后在计算完毕才真正将DOM操作变化反应到DOM树上
  • 单页面应用:提升用户体验,页面局部刷新,不用每次跳转都重新加载页面,加快了访问速度
  • vue第三方UI库很多,节省开发时间 (饿了吗的element-ui,有赞的vant,滴滴公司的cube-ui等等)

说说你对SPA单页面的理解,它的优缺点是什么

  SPA是什么?

    单页面应用SPA(single-page-application)是将所有的活动都局限于一个web页面中,在该页面初始化时加载相应的html、javascript和css。一旦加载完成,页面不会因为用户的操作而尽心页面的重载或者跳转,取而代之的是利用JavaScript动态变化HTML的内容,从而实现UI与用户交互

  SAP优点:快,内容的改变不需要重新加载整个页面。用户体验好

  SAP缺点:

    1.首次加载时间过长

    2.不利于SEO(搜索引擎)抓取。由于所有内容都是在一个页面中动态显示,所以在搜索引擎上有天然的弱势

Vue框架的MVVM模式

   1.M:模型(model): vue中的data,为一个对象
   2.V:视图(view):模板代码
   3.VM:视图模型(viewmodel):为Vue的实例,即new Vue()。所以我们通常把Vue的实例定义为vm 即const vm = new Vue()

  用网上一张图来表示会更清晰一些

    

    简单来说,数据Model变化会通过ViewModel处理(实现方法:数据绑定)后展示到页面上。操作页面DOM后,会通过ViewModel处理(实现方式:DOM监听)将数据Model进行改变

Vue中computed和watch的区别,以及适用场景

  一:computed:计算属性

    • computed 用于计算产生出新的数据
    • 支持缓存,只有依赖数据发生改变,才会重新进行计算(methods中没有缓存,多次调用仍会重新计算)
    • 不支持异步
    • 场景:
      • 当多个属性影响一个属性的时候
      • 一个数据需要经过复杂计算 

  二:watch:监听属性 

    • watch 用于监听现有数据
    • 不支持缓存,数据变化,会直接触发相应的操作
    • 支持异步操作
    • 场景:
      • 当一个属性变化影响了多个属性的变化
      • 一个属性发生变化后,需要执行某些具体业务逻辑操作,或者需要执行异步操作的情况 

Vue生命周期有哪些?每个生命周期适合那些场景?

  1.创建beforeCreate:vue实例创建前,此时props,data,computed,watch,methods上的方法均不能访问。

    适用场景:添加loading事件

  2.创建后created:vue实例创建完成,props,data,methods,watch,computed都初始化完成,可以访问了

    适用场景:1)请求数据2)结束loading事件

  3.挂载前beforeMount:模板挂载到页面前。此阶段是编译模板阶段,即调用render生成vdom,但还没有开始渲染DOM

  4.挂载后mounted:模板挂载到页面后,DOM渲染完成。可以后取到DOM节点

    适用场景:1)请求数据 2)获取dom节点

  5.更新前beforeUpdate:修改数据的时候,更新渲染视图之前会触发

  6.更新后updated:修改数据的时候,更新渲染视图之后触发

    注意: 不要在updated 中修改data,可能会导致死循环

  7.销毁前beforeUnmount(vue2中是beforeDestory):销毁组件之前,此时data,methods,cumputed,watch 都处于可用状态

    适合场景:销毁定时器

  8.销毁后unmounted(vue2中是destroyed):销毁组件后,此时组件中的数据data,methods,watch,computed都不可用

  <keep-alive> 组件还有两个周期:

  9、activated: 被 keep-alive 缓存的组件激活时调用

  10、deactivated:被keep-alive 缓存的组件隐藏时调用

   连环问1:Vue什么时候操作DOM比较合适?

      • mounted 和 updated 都不能保证子组件全部挂在完成
      • 在 mounted 中 使用 $nextTick 来操作DOM  

    连环问2:ajax 放在哪个生命周期合适?

      • created 和 mounted 都可以
      • 理论上created 确实更快一些,但从created到 mounted速速非常快,肉眼看不出差距
      • 从代码的执行上来看,放到created中会一边执行组件渲染一边触发网络请求,并行
      • 而mounted 就是等待DOM渲染完成再执行网络请求,串行,好理解        

    连环问3: Composition API 生命周期有何不同 

      • setup 代替了 beforeCreated 和 created
      • 换成了Hooks 函数的形式,如 mounted 换成了 onMounted(()=>{})        

vue第一次页面加载会触发哪些钩子函数

  beforeCreated 、created 、beforeMounted 、mounted

请求数据放到created和mounted那个阶段比较好?

  请求数据放到created阶段比较好,因为这个时候数据观测完毕。若在mounted阶段可能会发出现闪屏问题

父子组件生命周期的顺序

  1.渲染过程

    父 beforeCreate -> 父 created ->父 beforeMount ->子 beforeCreated -> 子 cerated -> 子 beforeMount ->子mounted ->父 mounted

  2.更新过程

    父 beforeUpate -> 子 beforeUpdate -> 子 updated ->父 updated

  3.销毁过程

    父 beforeDestory ->子 beforeDestory -> 子 destoryed -> 父 destoryed

  注:规律就是父组件要等子组件加载完成才能完,所以开始阶段是父组件先加载开始执行,然后等到子组件执行完成,父组件收尾。

    如果是子组件是异步组件的话,他们的执行书序会发生变化。会先执行完父组件的生命周期然后再执行子组件的生命周期。

父组件如何监听子组件的生命周期

  方法一:子组件通过$emit 发布事件,父组件绑定自定义事件监听

    子组件:

export default{
             ...
            mounted(){
             this.$emit('mounted','mounted 触发了')
       }
 }

 

    父组件:

<child @mounted="doSomething">
  export default{
      ...
      methods:{
          doSomething(data){
              console.log('监听到子组件的生命周期钩子函数mounted时,触发该回调',data)
          }
      },
      components:{
          child
      }
  }

  方法二:使用@hook:钩子函数名=“函数名” 

    @hook可以监听到子组件的任何生命周期

  父组件:

<child @hook:mounted="doSomething">
export default{
    ...
    methods:{
         doSomething(){
             console.log('监听到子组件的生命周期钩子函数mounted时,触发该回调')
           }
     },
     components:{
           child
      }
}

十、v-show 和 v-if 的区别

    • v-if 只有条件为true时才会渲染;v-show无论条件真假都会被渲染,只是为元素添加了display:none
    • 切换时,v-if条件区块内的事件监听和组件都会被销毁或者重建;而v-show是通过css中的display 控制显示和隐藏
    • v-if 由false变为true时,触发组件的beforeCreate、create、beforeMount、mounted等钩子。有true 变为false时,触发了beforeDestory、destoryed等钩子方法
    • v-show 切换不会触发组件的生命周期  
    • v-if 有更高的切换消耗;v-show 有更高的初始渲染消耗 

十一、为何在 v-for中用 key

    • 必须用key,且不能是index 和 random
    • diff 算法中通过 tag和 key来判断是否是相同的节点 sameNode
    • 相同的节点,则只移动元素即可,不是相同的节点会删除重建  
    • 可以减少渲染次数,提升渲染性能 

十五、computed 有何特点

  • 缓存,data不变不会重新计算
  • 提高性能

十六、为何组件 data 必须是一个函数

    • 避免数据共享
      • 我们知道在JS中,对象是引用类型,变量保存的是对象的内存地址
      • 所以如果data是对象,多个组件实例会共享同一份数据,实例间修改数据将互相影响
    • 数据隔离和独立性
      • 我们知道在JS中,函数返回值在每次调用函数都会返回新的内存空间
      • 所以,如果data是函数,则每个组件实例都调用data函数,返回全新的对象。从而确保每个组件实例都拥有自己的独立数据,实现数据隔离

十七、多个层级间重用相同的props时,避免每个层级重复定义相通的props,如何将组件所有props传递给子组件?

  如果想将一个组件的所有props传递给子组件,可以使用  v-bind="$attrs"

    • 使用 v-bind="$attrs"
      <template>
        <ChildComponent v-bind="$attrs" />
      </template>
    • 该方法在某些情况下很有用,比如想在父组件中定义一些props,然后在子组件中将这些props传递给下一个子组件
    • 如果子组件也接收了相同的prop,那么父组件传递给子组件的prop将会覆盖子组件内部定义的prop  

 十八、多个组件有相同的逻辑,如何抽离?

十九、何时使用异步组件?

    • 加载大组件时使用异步组件
    • 在路由中使用异步加载    

二十、何时需要使用keep-alive

    • 缓存组件,不需要重复渲染
    • 多个静态 tab页的切换  
    • 使用keep-alive 可以优化性能  

二十一、何时需要使用beforeDestory

    • 解绑自定义事件 event.$off
    • 清除定时器
    • 解绑自定义的DOM事件,如window scroll 等等    

二十三、vuex 中 action 和 mutation 区别

    • action 中处理异步,mutation不可以 
    • mutation 做原子操作,即每次只可以做一个操作
    • action 可以整合多个 mutation   

二十七、请描述响应式原理

  vue.js 响应式原理是其核心之一,它使得数据和视图能自动保持同步。vue实现这一功能主要通过以下几个关键技术:

  • 1. 数据劫持(Observer)
    • new Vue 一个实例时,会将 data 选项返回的对像进行初始化,vue会遍历所有属性,使用Object.defineProperty()方法将它们转化为getter/setter
    • 这样当属性被访问或者修改时就会触发响应的依赖更新 
  • 2.依赖收集(Dep)
    • 组件在渲染过程中,会执行render函数生成Vnode
    • 执行render函数的同时,每当遇到一个模板中用到的响应式数据, 会触发getter。
    • 在getter中,会调用dep.depend() 实现依赖收集
    • vue 就会将当前的Watcher(一个观察者对象)添加到这个数据的依赖集合中。这个过程称之为依赖收集
  • 3.派发更新(Watcher)
    • 当响应式数据发生变化时,其中setter会被触发
    • 在setter 中会调用 dep.notify() 通知依赖于该数据的watcher
    • watcher 会执行它们绑定的回调函数,通常是组件的重新渲染
  • 4.虚拟DOM (virtual DOM)
    • vue 使用虚拟DOM来优化DOM的更新过程
    • 当组件数据发生变化,执行重新渲染函数,会生成一个新的虚拟DOM  3
    • 新旧虚拟DOM树进行diff比较,计算出最小更新范围
  • 5.组件更新队列(Next Tick
    • 为了优化性能,vue维护了一个队列来收集需要更新的组件
    • 在某个时间点,例如事件循环的下一个tick,vue 会批量执行这些更新操作,可以避免不必要的多次重绘和回流  

二十八、diff算法时间复杂度?

    • diff算法降低了复杂度,从O(n^3) 降低到O(n)
    • 就提优化方案:
      • 只比较同一层级,不跨级比较
      • tag不相同 或者 tag相同但key不同 ,则认为是不同的节点,删掉重新创建
      • tag和key都相同,则认为是相同节点。不在深度比较  

三十、vue为何是异步渲染,$nextTick何用?

  • 异步渲染可以合并多次 data修改,统一更新,提高了渲染性能 
  • $nextTick 在DOM 更新完成之后触发回调,这样就可以获取到新的DOM元素了 

三十一、vue 常见的性能优化有哪些?

    • 合理使用v-if 和 v-show
    • 合理使用computed,computed有缓存功能提高性能
    • v-for 加key,以及避免与v-if同时在同一个元素中使用
    • 自定义事件、自定义DOM事件要及时销毁。否则会出现内存泄漏
    • 合理使用异步组件,大组件可以使用异步组件
      • vue2中可以使用 Vue.component 或 Vue.extend 注册组件。使用import()动态导入
        Vue.component('my-component', () => import('./components/MyComponent'))

        在组件内部定义:

        export default {
             components: {
                    'my-component': () => import('./components/MyComponent.vue')
            }
        }
      • vue3与vue2类似,有一些改进和新特性。推荐使用import 结合 defineAsyncComponent
        import { defineAsyncComponent } from 'vue'
        const Mycomponent = defineAsyncComponent(() => import('./components/MyComponent.vue))

        defineAsyncComponent 提供了更多的配置

        const AsyncComp = defineAasyncComponent({
            loader: () => import('./components/MyComponent'),
            loadingComponent:LoadingComponent, // 加载时显示的组件
            errorComponent: ErrorComponent, // 加载失败时显示的组件
            delay: 200, //延迟显示加载组件,单位毫秒
            timeout: 3000 //超过此时间则显示错误组件,单位毫秒
        })
    • 路由懒加载
      • 通过动态导入语法import()拆分路由模块
      • vue2中
        const router = new VueRouter({
            routes: [
                 {
                       path: '/my-component',
                       component: () => import('./MyComponent.vue')
                 }
            ]
        })
      • vue3中
        import {createRouter, createHistory} from 'vue-router'
        const router = createRouter({
             history: createHistory(),
             routes: [
                  {
                        path: 'my-component',
                        component: ()=> import('./MyComponent.vue') //动态导入
                   }
             ]
        })    
    • 组件懒加载(组件动态导入)
      • 使用import() 延迟加载非立即渲染的组件
      • components:{
          MyComponent: () => import('./MyComponent.vue')  
        }
      • 在模板中结合动态组件<component :is="MyComponent">  或 通过条件渲染触发加载      

      • 前端通用的性能优化,图片懒加载
        1. 原生HTML 属性实现图片懒加载
          • 使用<img> 标签的 loading="lazy" 属性,浏览器会自动延迟加载非视口图片
          • 代码实现:
            <img src="placeholder.jpg" data-src="real-image.jpg" loading="lazy">
          • 优点:简单,无需额外代码
          • 缺点: 兼容行有限(部分浏览器不支持)  
        2. 监听scroll 事件 + getBoundingClientRect
          • 原理: 图片默认使用占位符,真是URL 存储在data-src上。滚动时计算元素是否进入视口,动态替换src
          • 代码实现:
            function lazyload() {
                const imgs = document.querySelectorAll('img[data-src]')
                let viewHeight = document.documentElement.clientHeight || document.body.clientHeight
                imgs.forEach( img =>{
                      const rect = img.getBoundingClientRect();
                      if(rect.top < viewHeight){ //元素出现在视口中
                            img.src = img.dataset.src;
                            img.removeAttribute('data-src')
                      }
                })
            }
            window.addEventListener('scroll', lazyload)
        3. IntersectionObserver API (推荐)
          • 现代浏览器提供的异步监听 元素与视窗口交叉状态的 原生API,并在交叉变化时执行回调函数,回调函数中可接收到元素与视口交叉的具体信息
          • IntersectionObserver 基于浏览器渲染线程独立工作,避免主线程阻塞,性能更优
          • 基本用法:
            const observer = new IntersectionObserver(callback,options)

            callback:当元素可见比例达到指定阈值后触发,并传入两个参数

            • entries: 是一个数组,每个entry 对象表示一个被观察元素与视口或跟元素的交叉状态的信息
              • target: 被观察的目标元素,即出发了交叉事件的元素
              • isIntersecting: 布尔值,下列两种操作均会触发callback:
                • 1. 如果目标元素在root可视区,返回true
                • 2. 如果目标元素从root可视区域小石城,返回false   
            • observer: 观察器本身的引用,通常不需要使用 

                options: 配置对象(可选,不传时使用默认配置)

            •  root: 根元素,用于指定一个容器元素,设为null 时会默认使用视口作为根元素
            • rootMargin:跟元素的边距范围,用作root元素和 target 元素发生交集的时候计算交集的区域范围,用于扩大或缩小可视区域
            • threshold: 触发回调的阈值数组,按生序排列。当监听对象的任何阈值被越过时,都会触发callback。默认值为0

               操作方法:

            • observe(target) : 将目标元素添加到观察器中
            • unobserve(target): 停止监测目标元素的交叉状态
            • disconnect():停止监测所有目标元素的交叉状态,可以在不需要观察器的时候使用                          
        • 代码实现:
          const observer = new IntersectionObserver((entries) =>{ 
            //当 交叉状态变化时 触发该回调函数 entries.forEach(entry
          => { if(entry.isIntersecting) { // 目标元素是否可见 const img = entry.target // 被观察的目标元素 img.src = img.dataset.src; observer.unobserve(img) // 停止检测目标元素 } }) }) document.querySelectorAll('img[data-src]').forEach( img =>{ observer.observe(img) // 将目标元素添加到观察器中,开始检测交叉状态 })  
      1. 使用插件: vue-lazyload
        • 在vue中的使用
          import Vue from 'vue'
          import VueLazyload from 'vue-lazyload'
          Vue.use(VueLazyload, {
              perLoad: 1.3 // 预加载高度,默认为1.3 倍视口高度
              loading: 'loading-image.gif', //加载中时显示的图片
              error: 'error-image.png',// 加载失败时显示的图片
              attempt: 3,// 加载失败时的重试次数 
          })

          在模板中 使用 v-lazy指令 替换src属性

          <img v-lazy="imageUrl" alt="description">
    • 合理使用keep-alive,不需要重复渲染的时候可以使用,例如静态的tab页切换效果
    • data层级不要太深,尽量扁平一些
    • 使用vue-loader 在开发环境做模板编译(预编译)
    • webpack层面的优化
    • 使用SSR服务端渲染,速度更快,对SEO友好。Nuxt.js框架,但使用和调试成本高,现在基本上用SSR的少  

三十二、Vue2、Vue3和 React 三者的diff 算法有什么区别?Vue3.0使用的diff算法 比 Vue2.0中的双端比较有哪些优势?

Vue2、Vue3 和React 底层都用到DOM diff,他们的相同点都是同层比较,复杂度差不多;

不同点是,指针的移动方向:

  • React diff 特点是: 仅向右移动
    • 遍历新集合,index 表示新集合中元素的下表
    • 找到当前节点在老集合中的下表oldIndex
    • 如果 oldIndex < index 时,将节点移动到index的位置,否则不动(元素只能右移动不能向左移动) 

     如下图:

  • Vue2 diff 特点是: 双端指针比较
    • 新旧各有两个指针
    • 分别进行头头、尾尾、头尾、尾头进行比较
    • 指针从两边向中间移动 

     

  • Vue3 diff特点是: 在vue2 diff 算法基础上增加了 最长递增子序列 
    • 新旧个有2个指针
    • 只对比头头和尾尾,如果能够匹配上,就跟2.0是一致的;如果没匹配上,就会通过最长递增子序列的算法计算
    • 就是在新的Vdom寻找依次递增的有哪些元素,找到之后,这些元素它的顺序就是固定的
    • 再去寻找不再这些列表里面的元素,与老的Vdom进行对比
    • 再进行移动、删除或者创建 

    

    例子,根据上图具体步骤如下:

      • 通过“头头”比较,找到不开始不变的节点【A,B】
      • 通过“尾尾”比较,找到末尾不变的节点【G】
      • newChildren中剩余有变化的节点【F,C,D,E,H】,找到在oldChildren中各节点对应的index【5,2,3,4,-1】(-1表示没有找到)
      • 计算最长递增子序列为【2,3,4】,即对应的节点【C,D,E】可以不变
      • 剩余的节点【F,H】,根据index进行移动、新增、删除等操作 

vue3.0 使用的 diff算法相比 vue2.0 双端比较有哪些优势:

  •   vue3.0 diff算法采用了最长递增子序列算法,能够减少不必要的DOM操作,提升了性能
  •   vue3.0中 编译器会对静态节点进行标记,在更新时可以直接跳过这些静态节点,减少DOM操作
  •   vue3.0中,每次更新时会将新旧 VNode数组缓存起来,只对数组中不同的VNode进行对比,减少对比次数
  •   vue3.0中,对于动态删除操作,采用了一部队列的方式进行,能够将多个删除操作合并为一个,减少DOM操作
  •   总的来说,vue3.0 的diff 算法相比 vue2.0更加高效,减少了不必要的DOM操作,提升了性能       

三十三、Vue React 为何循环时必须使用key?

  • vdom diff 算法会根据key判断元素是否要删除
  • 匹配了key,则只移动元素-性能好
  • 未被匹配,则删除重建-性能较差  

三十四、你使用Vue 遇到过哪些坑?

  • 全局事件、自定义事件要在组件销毁时解除绑定
    • 有内存泄漏风险
    • 全局事件(如 window.resize)不解除,则会继续监听,而且组件再次创建时会重复绑定
  • vue2中,无法监听data属性的新增和删除,以及数组的部分修改
    • 新增data 属性,需要用Vue.set
    • 删除data属性,需要用Vue.delete
    • 不能arr[index] = value,要使用arr.spliceAPI 方式
  • 在一个新闻列表页下滑到一定位置,点击进入详情页,在返回列表页,此时会 scroll 到顶部,并重新渲染列表页。所有的 SPA 都会有这个问题,并不仅仅是 Vue 。
    • 在列表页面缓存数据和scrollTop
    • 返回列表页时(用Vue-router 导航守卫,判断from),使用缓存数据渲染页面,然后scrollTo(scrollTop)  

三十五、如何统一监听Vue组件报错?

  • errorCaptured (vue3 Composition API 可以使用 onErrorCaptured)生命周期钩子
    • onErrorCaptured 可以捕获组件中子组件抛出的错误
    • 可以返回false阻止向上传播,因为可能会有多个上级节点都监听错误
      <script setup>
           import {onErrorCaptured} from 'vue'
           onErrorCaptured((err, instance,info)=>{
                // err 错误对象
               // instance 抛出错误的组件实例
               // info 错误来源的额外信息
               // 可以在这里进行错误处理,比如记录日志、发送通知等
      return false //阻止错误继续向上冒泡
      }) </script>
  • errorHandler 全局错误监听

    • 所有组件的报错都会汇报到这里来(如果errorCaptured 返回 false 则不会到这里)

      const app = createApp(App)
      app.config.errorHandler = (err,instance,info) =>{
        console.log('errHandler-------',err,instance,info)  
      }
  • window.onerror 监听其他的JS错误 

  使用建议:

    •  一些重要的、复杂的、有运行风险的组件,可以使用errorCaptured重点监听
    • 然后用errorHandler window.onerror 候补全局监听,避免意外情况      

 

    

 

  

  

posted @ 2023-01-10 15:26  yangkangkang  阅读(21)  评论(0)    收藏  举报