面试题--Vue

1. 区别v-if与v-show

  • 隐藏: v-if干掉标签, v-show通过样式来隐藏
  • 重新显示: v-if需要重新创建标签对象, v-show只需要修改样式显示出来就可以
  • v-show重新显示更快, 但隐藏时还占用着内存空间: 以空间换时间
  • v-show更适合切换频繁/需要隐藏的DOM结构比较大
  • 使用v-if解决模板中初始解析undefined的bug 比如: {{a.b.c}} a初始为一个空对象

2. 为什么v-for与v-if不适合一起使用

  • 对遍历的item数据进行限制判断
    • 问题: 如果使用v-if, 每个数组元素都会解析指令来判断 ==> 效率低
    • 解决: 不使用v-if, 使用计算属性, 过滤产生一个子数组 ==> 效率高
  • 根据外部的数据判断
    • 问题: 如果在当前标签上用v-if, 执行n次 ==> 效率低
    • 解决: 添加一个/在父标签, 使用v-if, 执行1次 ==> 效率高

3. computed与watcher以及method的区别

  • computed与watch的区别
    • 计算属性必须同步返回计算结果, 而watch中可以在异步操作后更新数据显示
    • watch可以深度监视, 计算属性只是监视了使用到的数据
    • 选择:
      • 如果是根据现在的数据同步计算就可以确定要显示的另一个数据 ==> computed
      • 如果涉及到异步操作/深度监视 ==> watch
      • 一旦一个数据变化, 我们需要做一系列操作 ===> watch
  • computed与method的区别
    • 计算属性有缓存, 多次读取显示只计算一次
    • method, 多处显示计算多次

4. 说说Vue的常用指令

  • v-text
  • v-html
  • v-show
  • v-if / v-else / v-else-if
  • v-for
  • v-on
  • v-bind
  • v-model
  • v-slot: 插槽
  • v-once: 只初始渲染一次, 用于优化更新性能

5. 单个Vue组件的生命周期

  • 初始化:
    • beforeCreate: 不能通过this读取data数据和调用methods中的方法
    • 执行了一些初始化的准备工作
    • created: 可以通过this读取data数据和调用methods中的方法
    • 编译模板
    • beforeMount: 不能通过ref读取到页面中内容
    • 挂载编译好的模板, 显示页面
    • mounted: 能通过ref读取到页面中内容
  • 更新: this.msg += ‘–’
    • beforeUpdate (在数据更新后, 界面更新前调用): 读取的数据是最新的, 但页面是老的
    • 更新页面
    • updated: 读取的数据和页面都是新的
  • 死亡: $destroy()
    • beforeDestroy: 做一些收尾的工作, 比如: 清除定时器/解绑监听/…
    • destroyed

6. 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

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

  • mounted:初始化执行一次
  • activated: 初始化mounted之后 / 每次再回到当前路由

8. 说说对动态组件、缓存组件与异步组件的理解

  • 动态组件
    • 通过的is属性动态加载一个组件
    • is属性初始为A组件名, 加载A组件, 切换为B组件名, 加载B组件
  • 缓存组件
    • 默认路由组件离开或动态组件被切换, 组件都会立即死亡
    • 能让原本要死亡的组件不死亡, 在背后缓存起来, 后面需要时, 直接使用缓存的
    • 可以通过include与exclude属性来控制哪些组件要缓存或不缓存
  • 异步组件
    • 在引入组件时使用import动态引入: const Home = () => import(‘./Home.vue’)
    • 组件会被单独打包, 且只有在第一次访问时才会请求加载对应的打包文件 ==> 减小首屏打包文件打小
    • 除了组件, 其它模块也可以异步懒加载

9. 说说对递归组件的理解

  • 递归组件: 组件内部有自己的子组件标签

  • 应用场景: 用于显示树状态结构的界面

  • 注意: 递归组件必须有name

  • 编码: 实现一个简单的可开关的树状结构界面的 Tree 组件

    <template>
      <ul> 
        <li v-for="(item, index) in data" :key="index" @click.stop="handleClick(item)">
          {{item.text}}
          <Tree2 v-if="item.expend" :data="item.children"/>
        </li>
      </ul>
    </template>
    
    <script> 
    export default {  // 
      name: 'Tree2', // 作为内部的标签名
      props: ['data'],
      methods: {
       handleClick (item) {
         if (item.hasOwnProperty('expend')) {
           item.expend = !item.expend
         } else {
           this.$set(item, 'expend', true)
         }
       }
      }
    }
    </script>
    

10. Vue组件间有哪些通信方式

  • 根据组件间关系分类

    1. 父向子
      props(非函数)
      v-model
      $refs, $children
      插槽
    2. 子向父
      props(函数)
      vue自定义事件
      v-model
      .sync
      $parent
      作用域插槽
    3. 祖孙间
      a t t r s 与 attrs与 attrslisteners 与v-bind/v-on配合使用
      provide与inject
    4. 兄弟或其它/任意
      全局事件总线
      Vuex
  • 另一种分类方式

    1. 属性相关

    ​ props

    ​ v-model

    a t t r s 与 attrs与 attrslisteners

    ​ 作用域插槽(子向父传递)

    1. 自定义事件相关

    ​ 自定义事件

    ​ 全局事件总线

    ​ v-model

    ​ .sync

    1. 其它

    ​ $refs, $children, $parent

    ​ provide与inject

    ​ 插槽

    ​ vuex

11. 子向父通信有哪些方式

  • props(函数)
  • vue自定义事件
  • v-model
  • .sync
  • $parent
  • 作用域插槽

12. 说说Vue的自定义事件与全局事件总线

  • vue自定义事件

    • 实现子组件向父组件通信

    • 相关语法:

      • 父组件中绑定自定义事件监听:

        <Child @eventName=“callback($event)”>

        child.$on(‘eventName’, callback)

      • 子组件中分发事件

        this.$emit(‘eventName’, 2)

    • 应用:
      elment-ui的组件的事件监听语法都用的是自定义事件 <el-button @click=“test”>
      我们项目中的组件也用了不少自定义事件

  • 全局事件总线

    • 实现任意组件间通信
    • 编码
      将入口js中的vm作为全局事件总线对象:
      beforeCreate() {
      Vue.prototype.KaTeX parse error: Expected 'EOF', got '}' at position 20: …= this }̲ 传递数据的组件分发事…bus. e m i t ( ′ e v e n t N a m e ′ , d a t a ) 接收数据的组件处理监听 : t h i s . emit('eventName', data) 接收数据的组件处理监听: this. emit(eventName,data)接收数据的组件处理监听:this.bus.$on(‘eventName’, (data) => {})
    • 应用:
      前台项目中使用全局事件总线
    • 理解: 为什么将vm放到Vue的原型对象上, 所有组件都可见呢?
      • VueComponent.prototype = Object.create(Vue.prototype);
      • VC的原型对象的原型对象就是Vue的原型对象

13. Vue响应式数据原理

  • 简洁表达

    • 对象: 通过Object.defineProperty()添加setter方法来监视属性数据的改变 + 订阅-发布

    • 数组: 重写更新数组元素的一系列方法 + 订阅-发布

      • 调用原生的对应对数组元素进行相应的操作
      • 更新界面去
  • 详细表达(主要说对象的)

    • 初始化

      • 实现数据代理
        • 通过defineproperty给vm定义与data中属性对应的带getter/setter的属性
        • 在getter中, 读取data中对应的属性值返回 => 读取this.msg => 读取的是data中msg属性值
        • 在setter中, 将最新值保存到data对应的属性上 => this.msg = ‘abc’ => 'abc’会保存到data的msg上
      • 创建observer
        • 目标: 对data中所有层次的属性进行监视/劫持
        • 通过defineproperty给data中所有层次属性, 都重新定义, 加上getter与setter
          • getter: 用来建立dep与watcher的关系
          • setter: 用来当data数据发生改变去更新界面
        • 为data中所有层次的属性创建一个对应的dep ==> 用来将来更新界面的
      • 创建compile
        • 目标1: 实现界面的初始化显示 / 目标2: 为将更新做准备
          • 为模板中每个包含表达式(事件表达式除外)的节点创建一个对应的watcher
          • 给watcher绑定用于更新对应节点的回调函数
          • 将watcher添加到n个对应的dep中
    • 更新

      • this.msg = ‘abc’
      • 由于有数据代理 ==> data的msg更新为了’abc’
      • 由于有数据劫持 ==> data中msg的setter调用了
      • 在setter中, 通过对应的dep去通知所对应的watcher去更新对应的节点 ==> 使用了订阅发布模式
  • Vue数据响应式原理结构图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vhB1MyRF-1676984728734)(images/image-20220225180944565.png)]

14. Vue双向数据绑定原理

  • 通过v-model来实现双向数据绑定
  • v-model的本质
    • 将动态的data数据通过value属性传给input显示 ==> data到view的绑定
    • 给input标签绑定input监听, 一旦输入改变读取最新的值保存到data对应的属性上 ==> view到data的绑定
  • 双向数据绑定是在单向数据绑定(data–>view)的基础, 加入input事件监听(view ==> data)

15. s e t 和 set和 setnextTick的使用场景

  • 项目功能: 列表项点击动态显示输入框, 并自动获取焦点
  • 编码实现:
    • 动态给列表项数据对象添加edit属性为true ==> 标识显示输入框
    • 获取当前input对象, 调用focus() ==> 获取焦点
  • 问题
    • 向响应式对象上直接点添加属性不是响应式 ==> 输入框不会显示 ==> 使用$set添加edit属性
    • 得不到input, 调用focus会报错 ==> 因为界面还没有更新, 还没有input ==> 使用$nextTick指定在DOM更新后才去执行回调, 在回调中获取input

16. 区别MVVM与MVC

  • MVVM: 前台的技术
    • M: Model模型, 也就是包含数据的js对象 ==> data对象
    • V: View视图,动态显示模型对象中的数据的页面(前台渲染) ==> 模板页面
    • VM: ViewModel视图模型, 通过vm读取model中的数据显示到view上, 同时view输入数据改变, vm也可以将输入数据保存到model中 ==> Vue/组件的实例
  • MVC: 后台的技术
    • M: Model(模型)包含从数据库中查询得到的数据的对象
    • V: View(视图)动态显示模型对象中的数据的页面(后台渲染)
    • C: Controller(控制器)接收用户提交的请求参数, 操作数据库生成动态数据并产生模型对象

17. Vue.use()做了什么

  • 对象插件: 调用插件对象install方法(传入Vue)来安装插件(执行定义新语法的代码)
  • 函数插件: 直接将其作为install来调用(传入Vue)来安装插件(执行定义新语法的代码)

18. 说说Vue的mixin技术

  • 用来复用多个组件中相同的js代码的技术
  • 将多个组件相同的js代码提取出来, 定义在一个mixin中配置对象
  • 在多个组件中通过mixins配置引入mixin中的代码, 会自动合并到当前组件的配置中

19. Vue的组件data为什么必须是一个函数

  • 同一个组件的多个组件实例的data必须是不同的对象(内容初始数据可以相同)
  • 如果是data是对象, 组件的多个实例共用一个data对象
  • 如果是函数, 组件对象通过调用函数得到的一个新的data对象

20. vuex的5大属性

  • state
  • mutations
  • actions
  • getters
  • modules
  • namespaced: true

21. vuex的数据流结构图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SY3eoPoZ-1676984728736)(images/vuex.png)]

22. vuex中的mutation可以执行异步操作吗?

  • 功能可以 ==> 异步更新数据后界面确实会自动更新
  • 问题 ==> vuex的调试工具监视不到mutation中的异步更新, 工具记录还是更新前的数据(不对)
  • 扩展: 工具如何记录数据变化? ==> 每次mutation函数执行完后, 立即记录当前的数据 ==> 在mutation中同步更新state, 才能被记录到

23. vuex中的状态数据的响应式的原理?

  1. 创建了一个vm对象

  2. state中的数据都是vm的data数据(是响应式的)

  3. 组件中读取的state数据本质读取的就是data中的数据

  4. 一旦更新了state中的数据, 所有用到这个数据的组件就会自动更新

    new Vue({
    	data: {
            home: {
                categoryList: [],
                xxx: {}
            },
            user: {
                userInfo: {}
            }
    	}
    })
    

24. vuex数据刷新丢失的问题

  • 绑定事件监听: 在卸载前保存当前数据
window.addEventListener('beforeunload', () => { // 当页面刷新时, 页面卸载前的事件回调
	sessionStorage.setItem('CART_LIST_KEY', 
		JSON.stringify(this.$store.state.shopCart.cartList))
})

window.removeEventListener('beforeunload')
  • 在初始时读取保存数据作为状态的初始值
cartList: JSON.parse(sessionStorage.getItem('CART_LIST_KEY')) || [],

25. 路由组件间通信方式

  • query参数
  • params参数
  • props(需要配置, 而不是标签属性)
    • true ==> 将路由的params参数映射成props ==> 只能传递param参数
    • 对象 ==> 将对象中的属性映射成props ==> 只能传递自定义的参数
    • route => {} ==> 将函数返回的对象中的属性映射成props ==> 能会传递params和query参数和自定义的
  • meta
  • vuex

26. 跳转携带的参数, 刷新就丢失了

​ 如果注册没有指定/:xxx的点位, 而跳转时通过params配置携带的参数数据, 刷新时就会丢失

​ 因为url中没有携带的参数数据路径

27. 编程式路由跳转到当前路由, 参数不变, 会报出错误?

  • 前一个项目没这个问题, 后一个项目有问题

  • 3.1.0版本(2019.8)没这个问题, 3.1.0这后才有这个问题

    • 3.1.0之前: 返回值为undefined
      • push(location)
      • push(location, () => {}, () => {})
    • 3.1.0之后: 如果没有指定回调函数返回promise对象
      • push(location).then(() => {}).catch(() => {})
    • vue-router在3.1.0版本(2019.8)引入了push()的promise的语法, 如果没有通过参数指定回调函数就返回一个promise来指定成功/失败的回调, 且内部会判断如果要跳转的路径和参数都没有变化, 会抛出一个失败的promise
    • 说明文档: https://github.com/vuejs/vue-router/releases?after=v3.3.1
  • 解决:

    • 办法1: 在每次push时指定回调函数或catch错误

      push('/xxx', () => {})   ===> 
      push('/xxx').catch(() => {})
      
    • 办法2: 重写VueRouter原型上的push方法 (比较好)

      • 1). 如果没有指定回调函数, 需要调用原本的push()后catch()来处理错误的promise
      • 2). 如果传入了回调函数, 本身就没问题, 直接调用原本的push()就可以
    const originPush = VueRouter.prototype.push
    VueRouter.prototype.push = function (location, onComplete, onAbort) {
      console.log('push()', onComplete, onAbort)
      // 判断如果没有指定回调函数, 通过call调用源函数并使用catch来处理错误
      if (onComplete===undefined && onAbort===undefined) { // 使用的新语法
        return originPush.call(this, location).catch(() => {})
      } else { // 如果有指定任意回调函数, 通过call调用源push函数处理
        return originPush.call(this, location, onComplete, onAbort)
      }
    }
    
  • 扩展问题

    声明式路由跳转之所有没有问题, 是因为默认传入了成功的空回调函数

28. history与hash路由的区别和原理

  • 区别:

    • history: 路由路径不#, 刷新会携带路由路径, 默认会出404问题, 需要配置返回首页

      • 404:

        • history有: 刷新请求时会携带前台路由路径, 没有对应的资源返回
        • hash没有: 刷新请求时不会携带#路由路径
      • 解决:

        • 开发环境: 如果是脚手架项目本身就配置好

          ==> webpack ==> devServer: {historyApiFallback : true}

          当使用 HTML5 History API 时, 所有的 404 请求都会响应 index.html 的内容

      • 生产环境打包运行:

        • 配置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 = () => {}

29. 如何实现登陆后, 自动跳转到前面要访问的路由界面

  • 在全局前置守卫中, 强制跳转到登陆页面时携带目标路径的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 || '/')
    

30. 路由导航守卫的理解和使用

导航守卫是什么?

  • 导航守卫是vue-router提供的下面2个方面的功能
    • 监视路由跳转 -->回调函数
    • 控制路由跳转 --> 放行/不放行/强制跳转到指定位置 next(path)
  • 应用
    • 在跳转到界面前, 进行用户权限检查限制(如是否已登陆/是否有访问路由权限)
    • 在跳转到登陆界面前, 判断用户没有登陆才显示

导航守卫分类

  • 全局守卫: 针对任意路由跳转

    • 全局前置守卫

      router.beforeEach((to, from, next) => {
        // ...
      })
      
    • 全局后置守卫

      router.afterEach((to, from) => {})

  • 路由独享守卫

    • 前置守卫

      {
      	path: '/foo',
      	component: Foo,
      	beforeEnter: (to, from, next) => {}
      },
      {
      	path: '/ff',
      	component: Foo,
      },
          
      
  • 组件守卫: 只针对当前组件的路由跳转

    • 进入

      beforeRouteEnter (to, from, next) {
        // 不能使用this, this是undefined
        next((comp) => { // 延迟到组件对象创建后才执行
            // comp就是组件对象
        })
      },
      
  • 更新:

    beforeRouteUpdate (to, from, next) {}

  • 离开

    beforeRouteLeave (to, from, next) {}

31. 请说明key的作用和原理

1. 虚拟DOM的key的作用?
    1). 简单的说: key是虚拟DOM对象的标识, 在更新显示时key起着极其重要的作用
    2). 详细的说: 当列表数组中的数据发生变化生成新的虚拟DOM后, 进行新旧虚拟DOM的diff比较
        a. 有一个对应的key
            item数据没变, 直接使用原来的真实DOM
            item数据变了, 对原来的真实DOM进行数据更新
        b. 没有一个对应的key
           根据item数据创建新的真实DOM显示
2. key为index的问题
    1). 添加/删除/排序 => 产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低
    2). 如果item界面还有输入框 => 产生错误的真实DOM复用 ==> 不仅效率低, 界面效果也有问题
    注意: 如果不存在添加/删除/排序操作, 用index没有问题
3. 解决:
    使用item数据的标识数据作为key, 比如id属性值

32. 理解Vue的虚拟DOM

一个较轻的普通JS对象    ==> 真实DOM是一个较重的对象   => 轻重看对象内部属性多少
虚拟DOM对象有时也称为虚拟节点对象(vNode), 它包含了用于生成一个真实DOM/Node的必要信息, 比如:
标签结构:
    <ul id="list" name="xxx">
      <li key="1">abc1</li>
      <li key="2">abc2</li>
    </ul>
虚拟DOM/节点    
   {
   	  tag: 'ul',
   	  props: {
   	  	id: 'list',
   	  	name: 'xxx'
   	  }
   	  children: [
   	  	{tag: 'li', props: {}, key: 1, children: ['abc1']},
   	  	{tag: 'li', props: {}, key: 2, children: ['abc2']}
   	  ],
   	  key: undefined
   }

33. 说说Vue的 Diff算法

 目标: 
 	比较的结果是要确定: 哪些原来真实DOM可以复用(但内部内容可能要更新), 要创建哪些真实DOM
 处理流程
  	只做同层比较 => 虚拟DOM也是一个倒立树状态结构, 只进行同层比较, 这样比较次数少,效率高
  	确定要比较的新旧虚拟节点
    	有相同的key, 确定为对应的虚拟节点
    	没有相同的key, 直接创建一个新的真实DOM
    	key如果没有指定, 值为undefined, 按顺序对应比较
  	先比较标签名
    	如果不同, 直接创建新的真实DOM
    	如果相同, 复用原来对应的真实DOM => 如果数据内容有变化, 更新真实DOM内部内容
 简单流程: 同层比较 => 比较key => 比较标签名 => 复用

34. nextTick的作用和原理

是什么?
	官方描述: 在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM
    在数据更新后, 调用nextTick(callback),在callback中才可以读取到更新后的DOM
使用场景?
	更新数据后, 想操作更新后的DOM
	项目功能1: 使用swiper实现动态轮播
	项目功能2: 动态显示input输入框后, 需要自动获取焦点
原理?
	更新数据后, DOM不会立即更新, 而是在所有数据都变化完后, 将DOM更新作为一个异步任务统一执行
	理解Tick: 取出队列中的一个回调任务到调用栈中执行就是一个tick, 有时也指定队列中的一个回调任务
	nextTick(callback): 将callback指定为下一个异步回调任务执行
	为什么要先更新数据再调用nextTick()呢?  
		第一个数据更新才触发将DOM更新放入任务队列
		这样nextTick指定的callback就是放在DOM更新任务之后
		这样callback中才能读取到更新后的DOM
	nextTick中用的是哪个异步技术呢?
  		简单说: 优先使用微任务, 如果不支持才选择宏任务
  		详细说: promise => MutationObserver => setImmediate => setTimeout
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值