Vue相关知识点
13、如何自己实现v-model?以及双向数据绑定v-model的实现原理
17、多个层级间重用相同的props时,避免每个层级重复定义相通的props,如何将组件所有props传递给子组件?
23、vuex 中 action 和 mutation 区别
24、 Vue-router 常用的路由模式?以及他们实现的原理? hash 和 history两者有何区别?如何配置vue-router异步加载?
26、监听data 变化的核心API是什么?如何深度监听、监听数组?有何缺点?
28、diff算法时间复杂度?
32、Vue2、Vue3和 React 三者的diff 算法有什么区别?
34、你使用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-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 有更高的初始渲染消耗
-
- 必须用key,且不能是index 和 random
- diff 算法中通过 tag和 key来判断是否是相同的节点 sameNode
- 相同的节点,则只移动元素即可,不是相同的节点会删除重建
- 可以减少渲染次数,提升渲染性能
- 缓存,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
- 使用 v-bind="$attrs"
-
- 使用mixin混入 进行逻辑抽离
- mixin 并不是完美的解决方案,会有一些问题
-
- 加载大组件时使用异步组件
- 在路由中使用异步加载
-
- 缓存组件,不需要重复渲染
- 多个静态 tab页的切换
- 使用keep-alive 可以优化性能
-
- 解绑自定义事件 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算法降低了复杂度,从O(n^3) 降低到O(n)
- 就提优化方案:
- 只比较同一层级,不跨级比较
- tag不相同 或者 tag相同但key不同 ,则认为是不同的节点,删掉重新创建
- tag和key都相同,则认为是相同节点。不在深度比较
- 异步渲染可以合并多次 data修改,统一更新,提高了渲染性能
- $nextTick 在DOM 更新完成之后触发回调,这样就可以获取到新的DOM元素了
-
- 合理使用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 //超过此时间则显示错误组件,单位毫秒 })
- vue2中可以使用 Vue.component 或 Vue.extend 注册组件。使用import()动态导入
- 路由懒加载
- 通过动态导入语法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"> 或 通过条件渲染触发加载
-
- 前端通用的性能优化,图片懒加载
- 原生HTML 属性实现图片懒加载
- 使用<img> 标签的 loading="lazy" 属性,浏览器会自动延迟加载非视口图片
- 代码实现:
<img src="placeholder.jpg" data-src="real-image.jpg" loading="lazy">
- 优点:简单,无需额外代码
- 缺点: 兼容行有限(部分浏览器不支持)
- 监听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)
- IntersectionObserver API (推荐)
- 现代浏览器提供的异步监听 元素与视窗口交叉状态的 原生API,并在交叉变化时执行回调函数,回调函数中可接收到元素与视口交叉的具体信息
- IntersectionObserver 基于浏览器渲染线程独立工作,避免主线程阻塞,性能更优
- 基本用法:
const observer = new IntersectionObserver(callback,options)callback:当元素可见比例达到指定阈值后触发,并传入两个参数
- entries: 是一个数组,每个entry 对象表示一个被观察元素与视口或跟元素的交叉状态的信息
- target: 被观察的目标元素,即出发了交叉事件的元素
- isIntersecting: 布尔值,下列两种操作均会触发callback:
- 1. 如果目标元素在root可视区,返回true
- 2. 如果目标元素从root可视区域小石城,返回false
- observer: 观察器本身的引用,通常不需要使用
- entries: 是一个数组,每个entry 对象表示一个被观察元素与视口或跟元素的交叉状态的信息
- 原生HTML 属性实现图片懒加载
- 前端通用的性能优化,图片懒加载
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) // 将目标元素添加到观察器中,开始检测交叉状态 })
- 代码实现:
- 使用插件: 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">
- 在vue中的使用
-
- 合理使用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操作,提升了性能
- vdom diff 算法会根据key判断元素是否要删除
- 匹配了key,则只移动元素-性能好
- 未被匹配,则删除重建-性能较差
- 全局事件、自定义事件要在组件销毁时解除绑定
- 有内存泄漏风险
- 全局事件(如 window.resize)不解除,则会继续监听,而且组件再次创建时会重复绑定
- vue2中,无法监听data属性的新增和删除,以及数组的部分修改
- 新增data 属性,需要用Vue.set
- 删除data属性,需要用Vue.delete
- 不能arr[index] = value,要使用arr.spliceAPI 方式
- 在一个新闻列表页下滑到一定位置,点击进入详情页,在返回列表页,此时会 scroll 到顶部,并重新渲染列表页。所有的 SPA 都会有这个问题,并不仅仅是 Vue 。
- 在列表页面缓存数据和scrollTop
- 返回列表页时(用Vue-router 导航守卫,判断from),使用缓存数据渲染页面,然后scrollTo(scrollTop)
- 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 候补全局监听,避免意外情况


浙公网安备 33010602011771号