超实用的vue面试题详解


一、vue双向数据绑定原理

vue.js 则是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。

在这里插入图片描述

vue实现双向数据绑定的原理就是利用了 Object.defineProperty() 这个方法重新定义了对象获取属性值(get)和设置属性值(set)的操作来实现的。
它接收三个参数,要操作的对象,要定义或修改的对象属性名,属性描述符。重点就是最后的属性描述符。属性描述符是一个对象,主要有两种形式:数据描述符和存取描述符。这两种对象只能选择一种使用,不能混合两种描述符的属性同时使用。上面说的get和set就是属于存取描述符对象的属性。
在面试中如何应对?面试官:说一下VUE双向绑定的原理?答:VUE实现双向数据绑定的原理就是利用了 Object.defineProperty() 这个方法重新定义了对象获取属性值(get)和设置属性值(set)的操作来实现的

defineProperty的用法var obj = { };var name;第一个参数:定义属性的对象。第二个参数:要定义或修改的属性的名称。第三个参数:将被定义或修改的属性描述符。

Object.defineProperty(obj, "data", {
	//获取值
	get: function (){  
		return name; 
	},
	//设置值
	set: function (val) {
		name = val;console.log(val)
	}
})
//赋值调用
set:obj.data = 'aaa';
//取值调用
get:console.log(obj.data);

二、vue组件中data为什么是个函数

在vue实例中,data可以是函数也可以是一个对象,但是在vue组件中data只能是一个函数

在组件中如果data是一个对象,当存储的数据是一个引用类型时,在使用这个数据赋值并改变时,因为只是共享了一份内部地址,所以之前的值也会改变,简单来说就是数据污染,当data是一个函数的时候就不存在这种情况了。
采用函数的形式,initData时会将其当成工厂函数返回全新的data对象

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

v-if和v-show的效果都是显示跟隐藏,但是实际上还是有着一些不同

1.v-if 如果判断为false 则会直接删除相应的Dom元素,而v-show则是会给其加上display:none;进行隐藏。
2.v-if 如果判断为true则是进行创建这个DOM元素,v-show会把元素显示出来。

总结能得出结论 v-if 具有更高的切换成本,而v-show有着更高的渲染成本

四、v-if和v-for的优先级

如下代码,当v-if和v-for同时使用时浏览器会报错

<div v-for='(item) in List' :key='item.id' v-if='flag'>

在实际使用时是没问题的,关于优先级,vue的官方文档中明确指出v-for比v-if的优先级高,意思就是当同时使用时,浏览器会先渲染v-for。
当v-for和v-if同时使用时会带来一些不必要的性能消耗,当数据少时还没问题,如果数据量大,浏览器的性能消耗就会很明显

如果遇到了建议这样写

<div v-if = 'item.flag'>
    <div v-for='(item) in List' :key='item.id' >
</div>

五、v-for中的key值的作用

1、目前可以理解为遍历数组或元素中的唯一标识,增加或删减元素时,通过这个唯一标识key判断是否是之前的元素,并且该元素key的值是多少。这个唯一标识是保持不变的。
2、key是vue为v-for提供的一个属性,key属性可以用来提升v-for渲染的效率!vue不会去改变原有的元素和数据,而是创建新的元素然后把新的数据渲染进去
3、在使用v-for的时候,vue里面需要我们给元素添加一个key属性,这个key属性必须是唯一的标识

给key赋值的内容不能是可变的

  1. 在写v-for的时候,都需要给元素加上一个key属性
  2. key的主要作用就是用来提高渲染性能的!
  3. key属性可以避免数据混乱的情况出现 (如果元素中包含了有临时数据的元素,如果不用key就会产生数据混乱)

六、修改数据页面不更新的原因和解决方案

1、直接添加属性的问题

<template>
  <div>
	<p v-for="(item,key) in list" :key="key">
	    {{ item }}
	</p>
	<button @click="addProperty">动态添加新属性</button>
  </div>
</template>
<script>
export default {
 	data(){
	 	return{
	        list:{
	            oldProperty:"旧值"
	        }
        }
    },
    methods:{
        addProperty(){
            this.list.newProperty = "新值"  // 为items添加新属性
            console.log(this.lists)  // 输出带有newProperty的items
        }
    }
};
</script>

点击按钮后会发现数据打印出来了,但是页面还是没有渲染出数据

2、原理分析
vue2是利用过Object.defineProperty实现数据响应式

const item = {}
Object.defineProperty(obj, 'oldProperty', {
        get() {
            console.log(`get oldProperty:${val}`);
            return val
        },
        set(newVal) {
            if (newVal !== val) {
                console.log(`set oldProperty:${newVal}`);
                val = newVal
            }
        }
    })
}

原因:组件初始化时,对data中的list进行递归遍历,对list的每一个属性进行劫持,添加set,get方法。我们后来新加的newProperty属性,并没有通Object.defineProperty设置成响应式数据,修改后不会视图更新。

3、解决方案
Vue 不允许在已经创建的实例上动态添加新的响应式属性,若想实现数据与视图同步更新,可采取下面三种解决方案:
Vue.set()
Object.assign()
$forcecUpdated()

Vue.set()

Vue.set( target, propertyName/index, value)
参数:
target:要修改的对象或数组
propertyName/index:属性或下标
value:修改后的value值
这里再次调用defineReactive方法,实现新增属性的响应式,关于defineReactive方法,内部还是通过Object.defineProperty实现属性拦截

function defineReactive(obj, key, val) {
    Object.defineProperty(obj, key, {
        get() {
            console.log(`get ${key}:${val}`);
            return val
        },
        set(newVal) {
            if (newVal !== val) {
                console.log(`set ${key}:${newVal}`);
                val = newVal
            }
        }
    })
}

Object.assign()

直接使用Object.assign()添加到对象的新属性不会触发更新
应创建一个新的对象,合并原对象和混入对象的属性

this.item = Object.assign({},this.list,{newProperty:'新值'})

$forceUpdate

如果你发现你自己需要在 Vue中做一次强制更新,99.9% 的情况,是你在某个地方做错了事
$forceUpdate迫使Vue 实例重新渲染
PS:仅仅影响实例本身和插入插槽内容的子组件,而不是所有子组件。

this.item.newProperty = "新值" 
this.$forceUpdate();

七、$nextTick

1、$nextTick是什么

this.$nextTick()将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM 更新。它跟全局方法 Vue.nextTick 一样,不同的是回调的 this 自动绑定到调用它的实例上。

假设我们更改了某个dom元素内部的文本,而这时候我们想直接打印出这个被改变后的文本是需要dom更新之后才会实现的,也就好比我们将打印输出的代码放在setTimeout(fn, 0)中;
模板

<div class="app">
  <div ref="msgDiv">{{msg}}</div>
  <div v-if="msg1">Message got outside $nextTick: {{msg1}}</div>
  <div v-if="msg2">Message got inside $nextTick: {{msg2}}</div>
  <div v-if="msg3">Message got outside $nextTick: {{msg3}}</div>
  <button @click="changeMsg">Change the Message</button>
</div>

vue实例

new Vue({
  el: '.app',
  data: {
    msg: 'Hello Vue.',
    msg1: '',
    msg2: '',
    msg3: ''
  },
  methods: {
    changeMsg() {
      this.msg = "Hello world."
      this.msg1 = this.$refs.msgDiv.innerHTML
      this.$nextTick(() => {
        this.msg2 = this.$refs.msgDiv.innerHTML
      })
      this.msg3 = this.$refs.msgDiv.innerHTML
    }
  }
})

打印结果为msg1和msg3显示的内容还是变换之前的,而msg2显示的内容是变换之后的。其根本原因是因为Vue中DOM更新是异步的
在Vue生命周期的created()钩子函数进行的DOM操作一定要放在Vue.nextTick()的回调函数中

在created()钩子函数执行的时候DOM 其实并未进行任何渲染,而此时进行DOM操作无异于徒劳,所以此处一定要将DOM操作的js代码放进Vue.nextTick()的回调函数中。与之对应的就是mounted()钩子函数,因为该钩子函数执行时所有的DOM挂载和渲染都已完成,此时在该钩子函数中进行任何DOM操作都不会有问题 。

在数据变化后要执行的某个操作,而这个操作需要使用随数据改变而改变的DOM结构的时候,这个操作都应该放进Vue.nextTick()的回调函数中。

八、vue-router钩子函数和执行顺序

vue-router的导航守卫实际和组件的生命周期都是同一类型的钩子函数,在一个特定时间内会触发。
导航守卫有三个类型,分别是全局的钩子(针对整个路由器实例)单个路由对象的钩子组件内的钩子

//全局前置守卫
router.beforeEach((to, from, next) => {});
// 全局解析守卫
router.beforeResolve((to, from, next) => {});
// 全局后置钩子
router.afterEach((to, from) => {});

以上三个都是全局钩子,无论是哪个路由对象被激活,这些钩子都会被触发,只是触发的时机不同。
针对单个路由对象的钩子:

{
    path: "/",
    name: "Home",
    component: Home,
    // 路由独享守卫
    beforeEnter: (to, from, next) => {
    },
  },

组件内部钩子

 beforeRouteEnter(to, from, next) {
    // 在渲染该组件的对应路由被 confirm 前调用
    // 不!能!获取组件实例 `this`
    // 因为当守卫执行前,组件实例还没被创建
  },
  beforeRouteUpdate(to, from, next) {
    // 在当前路由改变,但是该组件被复用时调用
    // 可以访问组件实例 `this`
  },
  beforeRouteLeave(to, from, next) {
    // 导航离开该组件的对应路由时调用
    // 可以访问组件实例 `this`
  }

一个实际项目中,如果涉及有导航切换,则钩子函数和生命周期的执行顺序应该是有两种情况:

1、打开页面的任意一个页面,没有发生导航切换。
全局前置守卫beforeEach (路由器实例内的前置守卫)->路由独享守卫beforeEnter(激活的路由)->组件内守卫beforeRouteEnter(渲染的组件)->全局解析守卫beforeResolve(路由器实例内的解析守卫)->全局后置钩子afterEach(路由器实例内的后置钩子)->beforeCreate->Created->beforeMount->Mounted
总结一下大概是这样:this. r o u t e r ( b e f o r e E a c h ) − > t h i s . router(beforeEach)->this. router(beforeEach)>this.route(beforeEnter)->this(beforeRouteEnter)->this. r o u t e r ( b e f o r e R e s o l v e ) − > t h i s . router(beforeResolve)->this. router(beforeResolve)>this.router(afterEach)->this(某个组件的生命周期 beforeCreate->Created->beforeMount->Mounted)

2、 如果是有导航切换的(从一个组件切换到另外一个组件)
组件内守卫beforeRouteLeave(即将离开的组件)->全局前置守卫beforeEach (路由器实例内的前置守卫)->组件内守卫beforeRouteEnter(渲染的组件)->全局解析守卫beforeResolve(路由器实例内的解析守卫)->全局后置钩子afterEach(路由器实例内的后置钩子)->beforeCreate->created->beforeMount->beforeDestroy->destroyed->mounted

九、vuex的核心概念和运行机制

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式
vuex的核心概念

  • state:提供一个响应式数据;
  • Mutation;更改state方法;
  • Getter: 类似于vue组件中的计算属性,对state数据进行计算(会被缓存)
  • Action:触发mutation 方法,可以实现异步操作;
  • Module:模块化管理store(仓库),每个模块拥有自己的 state、mutation、action、getter

十、axios的封装

一般我会在项目的src目录中,新建一个http文件夹,然后在里面新建一个request.js和一个api.js文件。request.js文件用来封装我们的axios,api.js用来统一管理我们的接口。
引入axios,并设置请求拦截器和相应拦截器

import axios from 'axios'
const request = axios.create({
    // baseURL:'http://39.100.7.70:81/',
    timeout:5000
});
request.interceptors.request.use(config=>{
    return config
});
request.interceptors.response.use(config=>{
    return config
});
export default request

在api.js中引入request.js并抛出方法

import axios from '../utils/request'
export default{
    addOrder(data={}){
        return axios({
            url:"/api/user/order/addOrder",
            method:"POST",
            data:data
        })
    },
    getOrder(data={}){
        return axios({
            url:"/api/user/order/getOrder",
            method:"POST",
            data:data
        })
    }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值