Appearance

【面试题】Vue2

sqs2023/5/19面试题vue2

01. 谈谈对MVVM的理解

什么是MVC?

  1. MVC是Model-View-Controller 的缩写,即 模型—视图—控制器
    • Model:从数据库中查询到的数据对象
    • View:动态显示模型对象数据的页面,后台渲染(jsp)
    • Controller:接受用户提交的请求参数,操作数据库生成动态数据并产生模型对象
  2. MVC是 单向通信 。即View和Model,必须通过Controller来承上启下。

什么是MVVM?

  1. MVVM由Model、View、ViewModel(同步View和Model的对象) 三部分构成
  2. MVVM模式:不需要用户手动的操作dom的,主要是实现数据双向绑定
  3. 利用双向绑定技术,使得 Model 变化时,ViewModel 会自动更新

02. 谈谈对Vue的理解*

  1. Vue.js是一个用于创建用户界面的渐进式JavaScript框架
  2. 特点
    1. MVVM模式的数据驱动(谈谈MVVM模式)
    2. 组件化开发(灵活,低耦合,提高维护性)
    3. 声明式编码(简化DOM操作,数据驱动视图)
    4. 虚拟DOM+Diff算法(虚拟DOM+Diff算法)
  3. ie8版本一下不支持(Object.definePrototype在ie8下不支持)

03. 区别v-if与v-show

v-ifv-show
控制dom添加与删除控制元素的样式
会到导致重排与重绘会引起重绘,不会引起重排
首次加载快,不频繁切换的场景如果是频繁切换场景需要使用

04. 为什么v-for与v-if不能一起用

  1. v-for的优先级比v-if的优先级高
  2. 重复计算的问题
  3. 解决:
    1. v-if放在外层
    2. 使用computed进行计算后,再v-for

05. computed与watcher以及method的区别*

  • method
    • 没有缓存,多次计算显示多次
  • computed
    • 有缓存,多次读取
    • 只能写同步代码
    • 只能监视使用到的数据
    • 场景:根据现有数据同步计算新的数据
  • watch
    • 可以写异步代码
    • 可以进行深度监视
    • 场景:深度监视,或者监视某个数据变化的操作

优先级:props==>methods===>data===>computed===>watch

可以从以下角度说明:

  1. 缓存角度
  2. 同步异步
  3. 数据监视
  4. 优先级

06. 说说vue的常用指令

  • v-text
  • v-html
  • v-show
  • v-if / v-else / v-else-if
  • v-for
  • v-on
  • v-once
  • v-bind
  • v-model
  • v-slot
  • v-pre

vue自定义指令:

07. Vue单个组件的生命周期(对比Vue3)*

vue2生命周期:

image-20230527184647374

另外加上:

  • activated:被keep-alive缓存的组件激活时调用
  • deactivated:keep-alive缓存的组件失活时调用
  • errorCaptured:在捕获一个来自后代组件的错误时被调用。

vue2与vue3生命周期的区别:

  1. beforeDestroy改为了beforeUnmount
  2. new Vue() 改为 createApp()
  3. 判断el配置项和vm.$mount()在vue3中没有了

组合式API形式的生命周期钩子:

  • beforeCreate===>setup()
  • created=======>setup()
  • beforeMount ===>onBeforeMount
  • mounted=======>onMounted
  • beforeUpdate===>onBeforeUpdate
  • updated =======>onUpdated
  • beforeUnmount ==>onBeforeUnmount
  • unmounted =====>onUnmounted

08. Vue父子组件的生命周期

  • 初始化:
    • beforeCreate
    • created
    • beforeMount
      • --child beforeCreate
      • --child created
      • --child beforeMount
      • --child mounted
    • mounted
  • 更新:
    • beforeUpdate
      • --child beforeUpdate
      • --child updated
    • updated
  • 死亡:
    • beforeDestroy
      • -- child beforeDestroy
      • -- child destroyed
    • destroyed

09. 区别组件的钩子函数actived与mounted*

  • mounted:组件实例被挂在到dom上时被调用
  • actived:该钩子仅在keep-alive组件内部使用,组件被激活时使用

10. 说说动态组件、缓存组件与异步组件*

  • 动态组件:
    • 通过的is属性动态加载一个组件
  • 缓存组件:
    • 默认路由组件离开或动态组件被切换, 组件都会立即死亡
    • keepAlive来缓存组件,其中使用include和exclude来控制缓存哪些组件
  • 异步组件:
    • 在引入组件时使用import动态引入: const Home = () => import('./Home.vue')
    • 组件会被单独打包, 且只有在第一次访问时才会请求加载对应的打包文件 ==> 减小首屏打包文件打小

11. 说说递归组件的理解

递归组件: 组件内部有自己的子组件标签 应用场景: 用于显示树状态结构的界面

12. Vue组件间的通信方式*

  1. props
  2. 自定义事件
  3. 全局事件总线
  4. v-model
  5. .sync
  6. $refs
  7. $children与$parent
  8. $attrs与$listeners
  9. provide与inject
  10. vuex
  11. pinia
  12. v-slot

v-model原理:

<CustomInput v-model='name'/>
// 等价于
<CustomInput :value='name' @input='name = $event'/>
<input type='text' :value='value' @input='$emit('input',$event.target.value)'/>
props: ['value']
// el-ui中表单项相关组件都用到了v-model:Input/Select/checkoutbox

.sync原理:

<child :money.sync='total'/>
// 等价于
<child :money='total' @update:money='total = $event'/>
<button @click='$emit('updata:money',money - 100)'>花钱<button/>
// element-ui在有显示隐藏的组件上: Dialog / Drawer

13. Vuex的五大属性

  1. state: state 为单一状态树,在 state 中需要定义我们所需要管理的数组、对象、字符串等等
  2. getters: 类似 Vue.js 的计算属性, 当我们需要从 store 的 state中派生出一些状态,那么我们就需要使用 getter,getter 会接收 state 作为第一个参数,而且 getter 的返回值会根据它的依赖被缓存起来,只有 getter 中的依赖值(state 中的某个需要派生状态的值)发生改变的时候才会被重新计算
  3. mutations: 更改 store 中 state 状态的唯一方法就是提交 mutation,就很类似事件。每个 mutation 都有一个字符串类型的事件类型和一个回调函数,我们需要改变 state 的值就要在回调函数中改变。我们要执行这个回调函数,那么我们需要执行一个相应的调用方法:store.commit。
  4. actions: action 可以提交 mutation,在 action 中可以执行 store.commit,而且 action 中可以有任何的异步操作。在页面中如果我们要嗲用这个 action,则需要执行 store.dispatch。
  5. Module: module 其实只是解决了当 state 中很复杂臃肿的时候,module 可以将 store 分割成模块,每个模块中拥有自己的 state、mutation、action和 getter。
import {reqSearchInfo} from "@/api";

const actions = {
    async getSearchInfo(miniStore, searchParams) {
        let result = await reqSearchInfo(searchParams);
        if (result.code === 200) {
            miniStore.commit('SAVE_SEARCH_INFO', result.data);
        } else {
            alert(result.message);
        }
    }
};

const mutations = {
    SAVE_SEARCH_INFO(state, info) {
        state.searchInfo = info;
    }
};

const state = {
    searchInfo:{}
};

const getters = {
    goodsList(state){
        return state.searchInfo.goodsList
    },
    attrsList(state){
        return state.searchInfo.attrsList
    },
    trademarkList(state){
        return state.searchInfo.trademarkList
    },
    total(){
        return state.searchInfo.total;
    }
};

export default {
    actions,
    mutations,
    state,
    getters
};
this.$store.dispatch('getSearchInfo', this.searchParams);

14. Vuex的数据结构图*

vuex结构图

15. Vuex的mutation可以执行异步操作吗

不能执行异步任务,异步会导致内部状态难以追踪,devtool难以追踪state状态

16. Vuex状态数据的响应式原理

vuex本质上是将state值绑定到了一个vue对象上(然后说说vue响应式原理watcher)

17. Vue的对象响应式与数组响应式

数组响应式:Vue 将被侦听的数组的变更方法进行了包裹(在Array.prototype的7个方法做了拦截重写),所以它们也将会触发视图更新。方法:

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()

对象响应式:Vue 无法检测 property 的添加或移除。由于 Vue 会在初始化实例时对 property 执行 getter/setter 转化,所以 property 必须在 data 对象上存在才能让 Vue 将它转换为响应式的。 对于已经创建的实例,可以使用 Vue.set() 方法向嵌套对象添加响应式 property。(说说响应式)

18. new Vue的内部流程

vue生命周期

  1. 初始化(_init方法)
    1. props、data、生命周期,事件机制的初始化
    2. 例如data初始化就是创建observer对象,该对象与data绑定,通过Object.defineProperty...
  2. 模板解析
    1. 数据变化触发页面重新渲染
    2. vue会将HTML解析成一个AST描述对象(该对象是通过children和parent链接而成的树形结构,完整地描述了HTML标签的所有信息)
    3. vue根据AST对象生产render函数
  3. 先虚后实
    1. 得到render函数后,vue不会直接渲染DOM树,而是通过render函数得到vNode
    2. Vue在渲染之前对比vNode(减少Dom操作)
    3. 根据diff之后的结果,执行真正的dom节点插入更新删除操作,同时触发vue实例的生命周期钩子
    4. 之后就是观察数据变化,决定是否需要重新渲染页面

19. 虚拟DOM理解及diff算法*

为什么要有Virtual DOM?

  • 前端优化性能的秘诀就是减少dom操作,因为变动dom会造成浏览器的回流和重绘
  • 对更新的内容进行更新,对于没有改变的内容不做任何处理
  • 更好的跨平台,nodejs就没有dom,如果想实现SSR(服务端渲染),那么就需要借助vDom,因为vDom本质就是js对象

new Vue()的的render参数里面:h函数主要用来产生vNode

diff算法?

  • 用js对象结构表示dom数的结构,然后用这个树构建真正的dom树,插入到文档中
  • 当状态变化时,重新构造一个vNode树,然后把新vNode树和旧的vNode树进行比较,记录两棵树的差异
  • 把差异应用到真正的dom树上,视图就更新了

diff算法是通过同层级树节点进行比较而不是对树进行逐层搜索遍历,时间复杂度只有后O(n),很高效。

diff算法是如何判断是同一个vNode呢?

源码是比较了key相同,tag(标签)相同等数据,所以不能用下标作为列表渲染的key

  • 只做同层比较,确定要比较的新旧虚拟节点
  • 没有key: 依次比较,多出的虚拟DOM, 直接创建新的真实DOM
  • 有key: 找同名的key比较,没有找到, 直接创建新的真实DOM
  • 比较标签名,如果不同, 直接创建新的真实DOM
  • 复用原来对应的真实DOM => 如果数据内容有变化, 更新真实DOM内部内容
  • 简单流程: 同层比较 => 比较key => 比较标签名 => 复用

20. 说说数据代理与数据劫持*

数据代理:让程序员更加方便的读取到_data中属性

  1. new Vue(options)时传入的data,被vue放在了vm上名为_data
  2. 使用时,需要通过_data.xxx来读取和修改,很麻烦
  3. 通过数据代理,将_data上的数据循环添加到vm上,页面直接读取
let data = {
  a: 1,
  b: 2
}

let vm = new Vue({
  el: 'app',
  data
})

console.log(vm._data === data)  // true
// 底层实现
Object.definePrototype(vm, 'a', {
  get(){
    return _data.a
  },
  set(value){
    _data.a = value
  }
})

数据劫持:为了捕获到数据改变,进而重新解析模板

修改vm上的属性,会发生下面的过程:

vm.name='hello' ==> name(proxySetter) ==> vm._data.name='hello' ==> 页面变化

最后一步_vm上数据变化,为什么页面也会同步变化呢?

  1. 原理:_data上数据Object.definePrototype
  2. _data上每个数据都有对于的:reactiveSetter、reactiveGetter
let _data = {}

Object.definePrototype(_data, 'name', {
  get(){
    return 'xxx'
  },
  set() {
    console.log('修改数据,然后重新去解析页面')
  }
})

21. 说说Vue的响应式原理(发布订阅模式)*

Vue的响应式原理

22. 说说nextTick异步的原理*

nextTick

23. 说说$set原理*

24. 说说keep-alive原理*

25. history与hash路由的区别及原理

区别:

  • history
    • 路径无#,刷新会携带路由路径,默认会出现404问题,需要配置返回首页
    • 解决:
      • 开发环境:脚手架项目本身就配置好
        • webpack ==> devServer: { historyApiFallback : true}
      • 生产环境:配置nginx
        • location / { try_files $uri $uri/ /index.html; # 所有404的请求都返回index页面 }
  • hash
    • 路径有#,刷新不会携带路由路径,请求的总是根路径,返回首页没有404

原理:

  • history
    • 内部利用的是history对象的pushState()和replaceState() (H5新语法)
  • hash
    • 内部利用的是location对象的hash语法
      • 写hash路径 location.hash = '#/xxx'
      • 读hash路径: location.hash
      • 监视hash路径的变化: window.onhashchange = () => {}

26. 如何实现登录自动跳转要访问路由

  • 在全局前置守卫中, 强制跳转到登陆页面时携带目标路径的redirect参数
if (userInfo.name) { 
  next()
} else { 
  // 如果还没有登陆, 强制跳转到login 
  next('/login?redirect='+to.path) // 携带目标路径的参数数据
}
  • 在登陆成功后, 跳转到redirect参数的路由路径上
await this.$store.dispatch('login', {mobile, password})
 // 成功了, 跳转到redirect路由 或 首页 
const redirect = this.$route.query.redirect
this.$router.replace(redirect || '/')

27. 路由导航守卫的理解与使用*

  • 作用:
    • 监视路由跳转
    • 控制路由跳转
  • 应用:
    • 跳转页面前,进行用户权限检查限制(如是否已经登录/是否有访问路由权限)
  • 分类:
    • 全局守卫:针对任意路由跳转
      • 全局前置守卫 router.beforeEach((to, from, next) => { })
      • 全局后置守卫 router.afterEach((to, from) => {})
    • 路由独享守卫
      • 前置守卫 { path: '/foo', component: Foo, beforeEnter: (to, from, next) => {} },
    • 组件守卫:只针对当前组件的路由跳转
      • 进入:beforeRouteEnter (to, from, next) { next((comp) => { }) },
      • 更新:beforeRouteUpdate (to, from, next) {}
      • 离开:beforeRouteLeave (to, from, next) {}

28. 比较Pinia与Vuex*

  1. vuex有五大核心概念,pinia只有三个
    1. state
    2. getters
    3. actions
    4. mutations(vuex独有)
    5. modules(vuex独有)
  2. pinia更好的支持ts
  3. pinia不再需要注入,导入函数,自动补全,使用方便
  4. pinia是扁平架构,不具有命名空间,对于每一个store就是一个命名空间

pinia的基本使用

import { defineStore } from "pinia";

export const useUserStore = defineStore("userInfo", {
    /**
     * 类似于组件data,用来保存全局状态
     */
    state: () => {
        return {
            count: 100,
            foo: "bar"
        };
    },
    /**
     * 类似于组件的computed,用来封装计算属性,有缓存功能
     */
    getters: {
        compCount(state) {
            console.log('计算属性,有缓存');
            return state.count * 10;
        }
    },
    /**
     * 类似组件的methods,封装业务逻辑,修改state
     */
    actions: {
        changeUser(num: number) {
            this.count += num;
            this.foo = "hello";
        }
    }
});
import { useUserStore } from "../store";
import { storeToRefs } from "pinia";

const userStore = useUserStore();
const { count, foo, compCount } = storeToRefs(userStore);

const handlerClick = () => {
  // 1. 直接修改
  // userStore.count++
  // userStore.foo = 'hello'

  // 2. 通过$patch传入对象修改
  // userStore.$patch({
  //   count: userStore.count + 1
  //   foo: 'hello'
  // });

  // 3. 通过$patch传入函数修改
  // userStore.$patch((state) => {
  //   state.count++;
  //   state.foo = 'hello'
  // });

  // 4. 调用action修改
  userStore.changeUser(10);
};

29. Vue项目如何进行性能优化*

Last Updated 2023/5/30 00:42:18