vue面试(二)

本文深入探讨Vue面试中的关键知识点,包括v-once的用途、组件间通信的各种方式、Vuex状态管理、Vue响应式原理、路由懒加载和axios封装。同时,文章涵盖面试常见问题,如如何处理Vuex数据丢失、事件指令、路由监听、prop参数传递以及ESLint和render函数的作用。
摘要由CSDN通过智能技术生成

1.v-once的作用和用法?

v-once这个指令不需要任何表达式,它的作用就是定义它的元素或组件只会渲染一次,包括元素或者组件的所有字节点。首次渲染后,不再随着数据的改变而重新渲染。也就是说使用v-once,那么该块都将被视为静态内容。

<!--v-once示例-->
<template>
  <div>
    <div v-once>{{count}}</div>
    <button v-on:click="addCount">加个一吧</button>
  </div>

</template>

<script>
  export default {
    name: "Vonce",
    data() {
      return {
        count: 10
      }
    },
    methods: {
      addCount: function () {
        this.count += 1;
        console.log('this.count:'+this.count);
      }
    }
  }
</script>

点击按钮,控制台会依次加一,但是页面还是10,不会再次渲染
这个指令在实际的业务场景中是很少使用的,只有我们想进一步去优化性能的时候,可能会用到

2.Vue中在子组件能不能修改props数据

  1. 父组件通过标签属性的形式向子组件传递数据
  2. 传递的数据类型: 基本数据类型,引用数据类型

可以修改,会有警告
追问:Vue子组件修改props的数据会不会影响父组件的状态值

  1. 如果传递的基本数据类型,不会影响
  2. 如果传递的引用数据类型,会影响

3.vue单页面应用刷新网页后vuex的state数据丢失怎么解决
产生原因

  1. Vuex数据保存在运行内存中,vue实例初始化的时候为其分配内存
  2. 当刷新页面的时候重新初始化Vue实例,所以重新为Vuex分配内存导致之前保存的数据丢失
    如何解决
  3. Vuex的数据都是每次组件加载时候动态请求获取数据保存
    a) 优点: 保证数据不会丢失
    b) 缺点: 性能差,因为网络问题可能有网络延迟
  4. 将Vuex中的数据每次同步更新保存到sessionStorage中
    a) 优点: 每次页面刷新后从sessionStorage中获取保存的数据,不会丢失
    b) 缺点: state中的数据是动态的,就需要一直要同步到sessionStorage中,性能差
  5. 在页面刷新之前获取Vuex的数据,将数据保存在sessionStorage中,页面加载后从sessionStorage中获取
    a) 优点: 减少动态更新sessionStorage的次数,性能好
    b) 重点: 给window绑定beforeupload事件监听
    beforeunload这个事件在页面刷新时先触发的。那这个事件应该在哪里触发呢?我们总不能每个页面都监听这个事件,所以我选择放在app.vue这个入口组件中,这样就可以保证每次刷新页面都可以触发。
  created () {
    //在页面加载时读取sessionStorage里的状态信息
    if (sessionStorage.getItem("store") ) {
        this.$store.replaceState(Object.assign({}, this.$store.state,JSON.parse(sessionStorage.getItem("store"))))
    } 

    //在页面刷新时将vuex里的信息保存到sessionStorage里
    window.addEventListener("beforeunload",()=>{
        sessionStorage.setItem("store",JSON.stringify(this.$store.state))
    })
  }

4.常用的事件指令有哪些?
v-if,v-else-if,v-else、v-show、v-model、v-bind、v-html、v-text、v-for、v-on

5. r o u t e 和 route和 routerouter是什么?区别是什么?

  • 而route是正在跳转的这个路由的局部对象,可以获取这个正在跳转的路由的name,path,params,query,hash等等
    router其实是VueRouter的一个实例,所以它是一个全局对象,包括了路由的跳转方法(push,replace),钩子函数等

5.VUE中响应式原理?
Vue 的响应式原理是核心是通过 Object.defindeProperty中的 get 和 set 方法,当读取 data 中的数据时自动调用 get 方法,当修改 data 中的数据时,自动调用 set 方法,检测到数据的变化,会通知观察者 Wacher,观察者 Wacher自动触发,重新渲染当前组件(子组件不会重新渲染),生成新的虚拟 DOM 树,Vue 框架会遍历并进行新旧DOM对比,然后记录下来,最后,加载操作,将所有记录的不同点,局部修改到真实 DOM 树上

6.项目做过哪些优化?
1.首先最基本的,CSS样式表放在页面头部Head内且link链式引入,javascript放在底部body结束标签前避免阻塞。
2. 图片懒加载
3. 减少DOM元素数量,减少DOM的操作:
3.1减少 DOM 元素数量,合理利用:after、:before等伪类,避免页面过深的层级嵌套;
3.2优化javascript性能,减少DOM操作次数(或集中操作),能有效规避页面重绘/重排; 防抖和节流
4.静态资源CDN分发
5. 减少http请求次数:
5.1合并JS和CSS文件,利用项目构建工具webpack,做到JS或者CSS文件的合并,减少http请求的开销
5.2.充分利用浏览器缓存,强缓存:不会发起HTTP请求,直接从浏览器缓存中读取文件。协商缓存:向服务器发起HTTP请求,如果资源文件并未更新,response响应码即为304,随后从浏览器缓存中下载该文件,并不会从服务器下载。
5.3.利用雪碧图,使用字体图标

6.模块懒加载
7.使用keep-alive

Vue 基础

组件间通信的方式

根据通信的2个组件间的关系来选择一种通信方式
父子
	props
	vue自定义事件
	v-model
	.sync
	$ref, $children与$parent
	插槽 ==> 作用域插槽
祖孙
	$attrs与$listeners
	provide与inject
兄弟或其它/任意
	全局事件总线
	Vuex
方式1: props
1). 实现父向子通信: 属性值是非函数
2). 实现子向父通信: 属性值是函数
应用: 最基本, 用得最多的方式
方式2: vue自定义事件
1). 用来实现子组件向父组件通信
2). 相关语法:
    父组件中绑定自定义事件监听:
      <Child @eventName="callback">
    子组件中分发事件
      this.$emit('eventName', data)
应用: elment-ui的组件的事件监听语法都用的是自定义事件
      我们项目中的组件也用了不少自定义事件
方式3: 全局事件总线 ===> 消息订阅与发布
1). 实现任意组件间通信
2). 编码:
    将入口js中的vm作为全局事件总线对象: 
        beforeCreate() {
            Vue.prototype.$bus = this
        }
    分发事件/传递数据的组件: this.$bus.$emit('eventName', data)
    处理事件/接收数据的组件: this.$bus.$on('eventName', (data) => {})
应用: 前台项目中使用全局事件总线
方式4: v-model
1). 实现父子之间相互通信/同步
2). 组件标签上的v-model的本质: 动态value属性与自定义input监听来接收子组件分发的数据更新父组件数据
    父组件: 
        <CustomInput v-model="name"/>
        <!-- 等价于 -->
        <CustomInput :value="name" @input="name=$event"/>
    子组件: 
        <input type="text" :value="value" @input="$emit('input', $event.target.value)">
        props: ['value']
应用: element-ui中的表单项相关组件都用了v-model: Input / Select / Checkbox / Radio
方式5: .sync
1). 实现父子之间相互通信/同步(在原本父向子的基础上增加子向父)
2). 组件标签的属性上使用.sync的本质: 通过事件监听来接收子组件分发过来的数据并更新父组件的数据
    父组件:
        <child :money.sync="total"/>
        <!-- 等价于 -->
        <Child :money="total" @update:money="total=$event"/>

        data () {
          return {
            total: 1000
          }
        },
    子组件:
        <button @click="$emit('update:money', money-100)">花钱</button>
        props: ['money']
应用:  
    element-ui在有显示隐藏的组件上: Dialog / Drawer
方式6: a t t r s 与 attrs与 attrslisteners
1). $attrs
    实现当前组件的父组件向当前组件的子组件通信
    它是包含所有父组件传入的标签属性(排除props声明, class与style的属性)的对象
    使用: 通过 v-bind="$attrs" 将父组件传入的n个属性数据传递给当前组件的子组件
2). $listeners
    实现当前组件的子组件向当前组件的父组件通信
    $listeners是包含所有父组件传入的自定义事件监听名与对应回调函数的对象
    使用: 通过v-on="$listeners" 将父组件绑定给当前组件的事件监听绑定给当前组件的子组件
应用: 利用它封装了一个自定义的带hover文本提示的el-button
方式7: $refs & $children & $parent
1). $refs
    实现父组件向指定子组件通信
    $refs是包含所有有ref属性的标签对象或组件对象的容器对象
    使用: 通过 this.$refs.child 得到子组件对象, 从而可以直接更新其数据或调用其方法更新数据
2). $children
    实现父组件向多个子组件通信
    $children是所有直接子组件对象的数组
    使用: 通过this.$children 遍历子组件对象, 从而可以更新多个子组件的数据
3). $parent
    实现子组件向父组件通信
    $parent是当前组件的父组件对象
    使用: 通过this.$parent 得到父组件对象, 从而可以更新父组件的数据
应用: 在后台管理项目中使用了$refs
方式8: provide与inject
1). 实现祖孙组件间直接通信
2). 使用
	在祖组件中通过provide配置向后代组件提供数据
	在后代组件中通过inject配置来声明接收数据
3). 注意:
	不太建议在应用开发中使用, 一般用来封装vue插件
	provide提供的数据本身不是响应式的 ==> 父组件更新了数据, 后代组件不会变化
	provide提供的数据对象内部是响应式的 ==> 父组件更新了数据, 后代组件也会变化
应用: element-ui中的Form组件中使用了provide和inject
方式9: vuex
  • vuex用来统一管理多个组件共享的状态数据

  • 任意要进行通信的2个组件利用vuex就可以实现

    A组件触发action或mutation调用, 将数据保存到vuex的状态中

    B组件读取vuex中的state或getters数据, 得到最新保存的数据进行显示

  • 面试题

    1. mutation负责同步修改状态数据的,能不能异步修改

      可以异步修改

      如果异步修改的话会导致Vuex的调试工具失效,无法检测异步修改数据

    2. 设计的时候为什么建议mutation同步修改状态数据,而新增action负责异步

      Vuex的作用是给多个组件共享数据

      如果支持mutation异步修改数据,又因为异步的特性,会导致store对象中state数据发生错乱甚至是报错

      为了数据的安全

    3. Vuex刷新页面,数据丢失问题

      方案一:Vuex的数据都是每次组件加载时候动态请求获取数据保存(性能差,因为网络问题可能有网络延迟)

      方案二:将Vuex中的数据每次同步更新保存到sessionStorage中( state中的数据是动态的,就需要一直要同步到sessionStorage中,性能差)

      方案三:在页面刷新之前获取Vuex的数据,将数据保存在sessionStorage中,页面加载后从sessionStorage中获取

      // beforeunload 页面即将刷新之前调用
      window.addEventListener('beforeunload', () => {
        sessionStorage.setItem('test2', JSON.stringify(this.personArr))
      })
      // 读取sessionStorage中是否有之前缓存的数据
      let personArr = sessionStorage.getItem('test2')
      // 如果有: 更新Vuex中状态数据
      personArr && this.changePersonArrMutation(JSON.parse(personArr))
      
方式10: 插槽 ==> 作用域插槽slot-scope
1). 实现父组件向子组件传递标签内容
2). 什么情况下使用作用域插槽?
    父组件需要向子组件传递标签结构内容
    但决定父组件传递怎样标签结构的数据在子组件中
3). 编码:
    子组件:
        <slot :row="item" :$index="index">  <!-- slot的属性会自动传递给父组件 -->
        </slot>
    父组件:
        <template slot-scope="{row, $index}">
            <span>{{$index+1}}</span> &nbsp;&nbsp;
            <span :style="{color: $index%2===1 ? 'blue' : 'green'}" >{{row.text}}</span>
        </template>
应用: element-ui中的Table组件

computed与method和watch的区别

  • computed

    1. 支持缓存,多次读取, 只会执行一次计算, 只有依赖数据发生改变,才会重新进行计算
  1. 不支持异步,当computed内有异步操作时无效,无法监听数据的变化
  2. computed 属性值会默认走缓存,计算属性是基于它们的响应式依赖进行缓存的,也就是基于data中声明过或者父组件传递的props中的数据通过计算得到的值
  3. 如果一个属性是由其他属性计算而来的,这个属性依赖其他属性,是一个多对一或者一对一,一般用computed
  4. 如果computed属性属性值是函数,那么默认会走get方法;函数的返回值就是属性的属性值;在computed中的,属性都有一个get和一个set方法,当计算属性数据变化时,调用set方法。
  • method

    • 没有缓存, 多次读取, 必须多次调用
  • watch

    1. watch支持异步;

    2. 监听的函数接收两个参数,第一个参数是最新的值;第二个参数是输入之前的值;

    3. 当一个属性发生变化时,需要执行对应的操作;一对多;

    4. 监听数据必须是data中声明过或者父组件传递过来的props中的数据,当数据变化时,触发其他操作,函数有两个参数,

    immediate:组件加载立即触发回调函数执行,

    deep: 深度监听,为了发现对象内部值的变化,复杂类型的数据时使用,例如数组中的对象内容的改变

Vue监听路由变化
方法一:通过 watch

// 监听,当路由发生变化的时候执行
watch:{
  $route(to,from){
    console.log(to.path);
  }
},
或者
// 监听,当路由发生变化的时候执行
watch: {
  $route: {
    handler: function(val, oldVal){
      console.log(val);
    },
    // 深度观察监听
    deep: true
  }
},
或者
// 监听,当路由发生变化的时候执行
watch: {
  '$route':'getPath'
},
methods: {
  getPath(){
    console.log(this.$route.path);
  }
}
 watch:{
      // this.$route.path
      '$route.path':function(newVal,oldVal){
        //console.log(newVal+"---"+oldVal);
        if(newVal === '/login'){
          console.log('欢迎进入登录页面');
        } else if(newVal === '/register'){
          console.log('欢迎进入注册页面');
        }
      }
    }


方法二;通过 vue-router 的钩子函数 beforeRouteEnter beforeRouteUpdate beforeRouteLeave

<script>
  export default {
    name: 'app',
    // 监听,当路由发生变化的时候执行
    beforeRouteEnter (to, from, next) {
      // 在渲染该组件的对应路由被 confirm 前调用
      // 不!能!获取组件实例 `this`
      // 因为当钩子执行前,组件实例还没被创建
    },
    beforeRouteUpdate (to, from, next) {
      // 在当前路由改变,但是该组件被复用时调用
      // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
      // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
      // 可以访问组件实例 `this`
    },
    beforeRouteLeave (to, from, next) {
      // 导航离开该组件的对应路由时调用
      // 可以访问组件实例 `this`
    }
</script>

props传参三种方式

 传参方式:

 1. 布尔值(只能搭配params参数使用)
 2. 对象(用于自定义参数)
 3. 函数(自定义参数 + 路由信息)

4)面试问题2: 如何指定params参数可传可不传?
path: ‘/search/:keyword?’

5)面试问题3: 指定params参数时可不可以用path和params配置的组合?(对象写法)
	不可以用path和params配置的组合, 只能用name和params配置的组合
	query配置可以与path或name进行组合使用

6)面试问题4: 如果指定name与params配置, 但params中数据是一个"", 无法跳转,路径会出问题
		解决1: 不指定params
	解决2: 指定params参数值为undefined
 this.$router.push({
        // path:'/search',
        name:'search',
        query:{
          keyword1:this.keyword.toUpperCase()
        },
        params:{
          //如果传递params参数是一个空串,那么路径会有问题,传过去如果是undefined就没事
          keyword:this.keyword || undefined
        }
      })
	前提是路由params参数要可传可不传
{
    path:'/search/:keyword?',
    component:Search,
    name:'search',
    props: route => ({keyword:route.params.keyword,keyword1:route.query.keyword1})
  },
7)面试问题5: 路由组件能不能传递props数据?
		可以: 可以将query或且params参数映射/转换成props传递给路由组件对象
	实现: props: (route)=>({keyword1:route.params.keyword, keyword2: route.query.keyword })

路由懒加载

引入路由时,动态引入,代码分割,异步加载,例:

//没有指定webpackChunkName的情况下,每个组件打包成一个js文件
const home1 = () => import('../home1/index')
const home2 = () => import('../home2/index')
//设置了webpackChunkName,会合并打包成一个js文件
{
    path: '/home1',
    name: 'home1',
    component: () =>import(/* webpackChunkName: "home" */,'../home2/index')
}
{
    path: '/home2',
    name: 'home2',
    component: () => import(/* webpackChunkName: "home"*/,'../home2/index')
}

import函数不是webpack提供的语法。是es6语法

面试问题5(非常重要): 编程式路由跳转到当前路由(参数不变), 会抛出NavigationDuplicated的警告错误

​ 面试问题: 在做项目时有没有遇到比较难的问题?(可做回答)

回答步骤:

  1. 我的问题: 我在上一个项目时没有问题, 后面再做一个新的项目时就有了问题

  2. 原因分析: vue-router3.1.0之后, 引入了push()的promise的语法, 如果没有通过参数指定回调函数就返回一个promise来指定成功/失败的回调, 且内部会判断如果要跳转的路径和参数都没有变化, 会抛出一个失败的promise

  3. 解决办法:解决1: 在跳转时指定成功或失败的回调函数, 通过catch处理错误

    ​ 解决2: 修正Vue原型上的push和replace方法 (优秀)

import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)


//终极解决多次触发push或者repalce,报错的问题
//NavigationDuplicated

const originPush = VueRouter.prototype.push
const originReplace = VueRouter.prototype.replace

VueRouter.prototype.push = function(location,onResolved,onRejected){
  if(onResolved === undefined && onRejected === undefined){
    // originPush.call目的是让VueRouter实例化对象去调用
    //如果不加,那就是window在调用
    return originPush.call(this,location).catch(() => {})
  }else{
    return originPush.call(this,location,onResolved,onRejected)
  }
}

VueRouter.prototype.replace = function(location,onResolved,onRejected){
  if(onResolved === undefined && onRejected === undefined){
    // originPush.call目的是让VueRouter实例化对象去调用
    //如果不加,那就是window在调用
    return originReplace.call(this,location).catch(() => {})
  }else{
    return originReplace.call(this,location,onResolved,onRejected)
  }
}


import routes from '@/router/routes'

export default new VueRouter({
  routes
})

对axios进行二次封装(面试必说)

  1. 配置通用的基础路径和超时:

    ​ axios.create({baseURL, timeout})

  2. 显示请求进度条

    ​ 显示: 准备发请求前显示, 在请求拦截器中执行NProgress.start()

    ​ 隐藏: 请求结束隐藏, 在响应拦截器成功/失败回调中NProgress.done()

  3. 携带token数据

    ​ 在请求拦截器中, 将token添加到请求头中

  4. 成功返回的数据不再是response, 而直接是响应体数据response.data

    ​ 响应拦截器成功的回调中: return response.data

  5. 统一处理请求错误, 具体请求也可以选择处理或不处理

    ​ 在响应拦截器失败的回调中: alert提示错误信息, return Promise.reject(error)

//对axios的二次封装
// 配置基础路径和超时限制
// 添加进度条信息  nprogress
// 返回的响应不再需要从data属性当中拿数据,而是响应就是我们要的数据
// 统一处理请求错误, 具体请求也可以选择处理或不处理
import axios from 'axios'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'


const service = axios.create({
  baseURL: '/api',  // 配置基础路径
  timeout: 20000,  //和超时限制
});


//请求拦截器
//请求拦截器内部一般不会处理错误的信息
service.interceptors.request.use(config => {
  //config是发送请求的配置对象,必须处理完返回这个配置对象
  //开启我们的进度条
  NProgress.start()
  return config
});
// 响应拦截器
service.interceptors.response.use(
  response => {
    //停止进度条
    NProgress.done()
    //返回响应的时候,直接返回响应的data
    return response.data
  },
  error => {

    NProgress.done()

    alert('请求出错' + error.message || '未知错误')
    //以后不允许用户继续处理: 中断promise链
    return new Promise(() => {}) //返回pending状态的promise 中断
    //以后它允许用户继续对错误进行处理
    // return Promise.reject(error)
  }
);


export default service

什么是ESLint?
官网上告诉我们,ESLint 是一个用来识别 ECMAScript/JavaScript 并且按照规则给出报告的代码检测工具,哦,所以我们可以知道,ESLint 就是一个工具,而且是一个用来检查代码的工具。

render函数是什么,怎么用,详情
render函数是什么

简单的说,在vue中我们使用模板HTML语法组建页面的,使用render函数我们可以用js语言来构建DOM

因为vue是虚拟DOM,所以在拿到template模板时也要转译成VNode的函数,而用render函数构建DOM,vue就免去了转译的过程。

当使用render函数描述虚拟DOM时,vue提供一个函数,这个函数是就构建虚拟DOM所需要的工具。官网上给他起了个名字叫createElement。

node.js是什么,详情
一种一个能够在服务器端运行JavaScript,并且可以开放源代码,以及跨平台运行JavaScript的一种运行环境。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值