前端vue高频面试题【更新中】

Vue

Vue双向数据绑定原理

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

阐述一下你所理解的MVVM响应式原理⭐⭐⭐⭐⭐

vue是采用数据劫持配合发布者-订阅者的模式的方式,通过Object.defineProperty()来劫持各个属性的getter和setter,在数据变动时,发布消息给依赖收集器(dep中的subs),去通知(notify)观察者,做出对应的回调函数,去更新视图

MVVM作为绑定的入口,整合Observer,Compile和Watcher三者,通过Observer来监听model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer,Compile之间的通信桥路,达到数据变化=>视图更新;视图交互变化=>数据model变更的双向绑定效果。

Vue 如何监听数组的?⭐⭐⭐⭐⭐

首先第一点是要看数组里面是不是还存在对象,如果存在对象的话再进行深层遍历是否还依然存在对象,再把对象进行 defineProperty监听。然后数组,数组的改变实质上只是几个方法,什么 popunshiftpush…Vue 重写了这几个方法,只要在调用这些方法的时候做出回调更新就可以了

为什么 Vue 要采用异步更新⭐⭐⭐⭐

因为首先 Vue 本身是组件级更新的,更改数据如果非常多,更新非常频繁,如果不采用异步更新的话每次都需要重新渲染。

每次有数据需要更新的时候,Vue 会把它放在一个队列中,等最后的时候会调用 nexttick 方法。nexttick就会清空这个队列。

用户也可以手动调用 nexttick(callback) 方法,会同样把callback 回调函数放入队列中,保证视图更新完之后被调用(因为会把 callback 放进队列的最后),并且是依次链式调用。

Vue中的nextTick⭐⭐⭐⭐⭐

  • 解释
    • nextTick:在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
  • 应用
    • 想要在Vue生命周期函数中的created()操作DOM可以使用Vue.nextTick()回调函数
    • 在数据改变后要执行的操作,而这个操作需要等数据改变后而改变DOM结构的时候才进行操作,需要用到nextTick

Vue中的nextTick是微任务还是宏任务⭐⭐⭐⭐

nextTick的内部实现如果支持 promise 那就使用 promise,没有就用MutationObserver(微任务),在没有就用 setImmediate(宏任务),还没有就用 setTimeOut所以nextTick 有可能是宏任务,也有可能是微任务

讲一讲Vue的发布订阅者模式⭐⭐⭐⭐⭐

  • data中每一个数据都绑定一个Dep,这个Dep中都存有所有用到该数据的观察者
  • 当数据改变时,发布消息给dep(依赖收集器),去通知每一个观察者。做出对应的回调函数
 const dep = new Dep()
// 劫持并监听所有属性
Object.defineProperty(obj, key, {
  enumerable: true,
  configurable: false,
  get() {
    // 订阅数据变化时,在Dep中添加观察者
    Dep.target && dep.addSub(Dep.target)
    return value
  },
  set: (newVal) => {
    if (newVal !== value) {
      this.observe(newVal)
      value = newVal
    }
    // 告诉Dep通知变化
    dep.notify()

  },
})

vue的生命周期⭐⭐⭐⭐⭐

  • beforeCreate
    • 创建之前,此时还没有data和Method
  • Created
    • 创建完成,此时data和Method可以使用了
    • 在Created之后beforeMount之前如果没有el选项的话那么此时生命周期结束,停止编译,如果有则继续
  • beforeMount
    • 在渲染之前
  • mounted
    • 页面已经渲染完成,并且vm实例中已经添加完$el了,已经替换掉那些DOM元素了(双括号中的变量),这个时候可以操作DOM了(但是是获取不了元素的高度等属性的,如果想要获取,需要使用nextTick()
  • beforeUpdate
    • data改变后,对应的组件重新渲染之前
  • updated
    • data改变后,对应的组件重新渲染完成
  • beforeDestory
    • 在实例销毁之前,此时实例仍然可以使用
  • destoryed
    • 实例销毁后

vue中父子组件的生命周期⭐⭐⭐⭐⭐

  • 父子组件的生命周期是一个嵌套的过程
  • 渲染的过程
    • beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted
  • 子组件更新过程
    • beforeUpdate->子beforeUpdate->子updated->父updated
  • 父组件更新过程
    • beforeUpdate->父updated
  • 销毁过程
    • beforeDestroy->子beforeDestroy->子destroyed->父destroyed

computed 、watch、method的区别⭐⭐⭐⭐⭐

  • computed

    • 计算属性,依赖其他属性,当其他属性改变的时候下一次获取computed值时也会改变,computed的值会有缓存
  • watch

    • 类似于数据改变后的回调
    • 如果想深度监听的话,后面加一个deep:true
    • 如果想监听完立马运行的话,后面加一个immediate:true
  • method

    • 在使用 method 的时候,是这样使用的{{fn{xx}}},渲染的时候如果没有发生变化,这个也是会被执行的。而 computed是有缓存的,如果没有变化就不用再去执行了

Vue优化方式⭐⭐⭐⭐⭐

  • v-if 和v-show
  • 使用Object.freeze()方式冻结data中的属性,从而阻止数据劫持
  • 组件销毁的时候会断开所有与实例联系,但是除了addEventListener,所以当一个组件销毁的时候需要手动去removeEventListener
  • 图片懒加载
  • 路由懒加载
  • 为减少重新渲染和创建dom节点的时间,采用虚拟dom

Vue-router的模式⭐⭐⭐⭐⭐

  • hash模式
    • 监听hashchange事件实现前端路由,利用url中的hash来模拟一个hash,以保证url改变时,页面不会重新加载。
  • history模式
    • 利用pushstate和replacestate来将url替换但不刷新,但是有一个致命点就是,一旦刷新的话,就会可能404,因为没有当前的真正路径,要想解决这一问题需要后端配合,将不存在的路径重定向到入口文件。

MVC与MVVM有什么区别⭐⭐⭐⭐⭐

mvc和mvvm的区别
基于Vue实现一个简易MVVM
不好意思!耽误你的十分钟,让MVVM原理还给你

  • MVC

    • Model(模型)是应用程序中用于处理应用程序数据逻辑的部分。通常模型对象负责在数据库中存取数据处理数据的crud
    • View(视图)是应用程序中处理数据显示的部分。通常视图是依据模型数据创建的。视图层,前端
    • Controller(控制器)是应用程序中处理用户交互的部分。一般包括业务处理模块和router路由模块
      • 通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据
  • mvvm

    • mvvm是前端图层的概念,mvvm是双向的,视图层可以通过 ViewModel 转换成模型层,模型层也可以通过 ViewModel 转换成视图层
      • Model(每个页面的单独的数据)
      • ViewModel (双向绑定,M和V之间的枢纽)
      • View(视图,相当于HTML结构)

diff算法

  • diff算法是指对新旧虚拟节点进行对比,并返回一个patch对象,用来存储两个节点不同的地方,最后利用patch记录的消息局部更新DOM

  • diff 算法

    • diff算法是虚拟节点的比较

    • 先进行key值的比较

    • 先进行同级比较,

    • 然后再比较是不是一方有儿子,一方没儿子

      • 如果是这样,直接在旧节点中插入或删除儿子即可
    • 在比较两方都有儿子的情况

      • 情况一:旧:ABCD,新:ABCDE;从头向尾比较,最后插入即可
      • 情况二:旧 :ABCD,新:EABCD;从尾向头比较,最后插入即可
      • 情况三:旧:ABCD,新:DABC;头和尾先进行一次比对,发现D 时,把 D 移至前面,再继续从头向尾比较,
      • 情况四:旧:ABCD,新 BCDA;从头向尾比较后发现不对,就会从尾向头比,把 A 移至最后,再继续比较
      • 情况五:旧 :ABCD,新CDME;从头向尾比较,把 CD 移至前面,最后 新建 ME,再把 CD 至为空
    • 递归比较子节点

虚拟DOM的优缺点⭐⭐⭐⭐⭐

  • 缺点
    • 首次渲染大量DOM时,由于多了一层虚拟DOM的计算,会比innerHTML插入慢
  • 优点
    • 减少了dom操作,减少了回流与重绘
    • 保证性能的下限,虽说性能不是最佳,但是它具备局部更新的能力,所以大部分时候还是比正常的DOM性能高很多的

Vue中Key的作用 ⭐⭐⭐⭐

key主要用在虚拟Dom算法中,每个虚拟节点VNode有一个唯一标识Key,通过对比新旧节点的key来判断节点是否改变,用key就可以大大提高渲染效率,这个key类似于缓存中的etag。

react、vue中的key有什么作用?(key的内部原理)

  1. 虚拟DOM中key的作用:
    key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】,
    随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较,比较规则如下:
  2. 对比规则:
    (1).旧虚拟DOM中找到了与新虚拟DOM相同的key:
    ①.若虚拟DOM中内容没变, 直接使用之前的真实DOM!
    ②.若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM。
    (2).旧虚拟DOM中未找到与新虚拟DOM相同的key:
    创建新的真实DOM,随后渲染到到页面。
  3. 用index作为key可能会引发的问题:
    1. 若对数据进行:逆序添加、逆序删除等破坏顺序操作:
      会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。
    2. 如果结构中还包含输入类的DOM:
      会产生错误DOM更新 ==> 界面有问题。
  4. 开发中如何选择key?:
    1. 最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。
    2. 如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的。

为什么 v-for 会要有key?⭐⭐⭐⭐⭐

因为在 vue 中会有一个 diff 算法,假如子节点 AB 调换了位置,它会比较 key 值,会直接调换,而不是一个销毁重新生成的过程

Vue组件之间的通信方式

  • 子组件设置props + 父组件设置v-bind:/:
    • 父传子
  • 子组件的$emit + 父组件设置v-on/@
    • 子传父
  • 任意组件通信,新建一个空的全局Vue对象,利用emit发送 ,on接收
    • 传说中的$bus
    • 任意组件
        Vue.prototype.Event=new Vue();
        Event.$emit(事件名,数据);
        Event.$on(事件名,data => {});
    
  • Vuex
    • 里面的属性有:
      • state
        • 存储数据的
        • 获取数据最好推荐使用getters
        • 硬要使用的话可以用MapState, 先引用,放在computed中...mapState(['方法名','方法名'])
      • getters
        • 获取数据的
        • this.$store.getters.xxx
        • 也可使用mapGetters 先引用,放在computed中,...mapGetters(['方法名','方法名'])
      • mutations
        • 同步操作数据的
        • this.$store.commit(“方法名”,数据)
        • 也可使用mapMutations ,使用方法和以上一样
      • actions
        • 异步操作数据的
        • this.$store.dispatch(“方法名”,数据)
        • 也可使用mapActions ,使用方法和以上一样
      • modules
        • 板块,里面可以放多个vuex
  • 父组件通过v-bind:/:传值,子组件通过this.$attrs获取
    • 父传子
    • 当子组件没有设置props的时候可以使用
    • this.$attrs获取到的是一个对象(所有父组件传过来的集合)
  • 祖先组件使用provide提供数据,子孙组件通过inject注入数据
  • parent/children
  • refs—$ref

Vue-router有哪几种钩子函数⭐⭐⭐⭐⭐

  • beforeEach
    • 参数有
      • to(Route路由对象)
      • from(Route路由对象)
      • next(function函数) 一定要调用才能进行下一步
  • afterEach
  • beforeRouterLeave

  • 全局守卫:
    • beforeEach(全局前置守卫)
      • 参数有
        • to(Route路由对象)
        • from(Route路由对象)
        • next(function函数) 一定要调用才能进行下一步
    • afterEach(全局后置守卫)
  • 路由独享守卫:
    • beforeEnter
  • 组件内的守卫:
    • beforeRouteUpdate、
    • beforeRouteEnter、
    • beforeRouteLeave

路由导航守卫配置

见详解
全局守卫:beforeEach(to,from,next)和 afterEach(to,from)
路由独享守卫:beforeEnter
组件内的守卫: 路由进入 / 更新 / 离开之前 beforeRouterEnter/update/leave

说一下什么是路由守卫?

是路由跳转前、后的一些钩子函数
分类

  • 全局守卫:【写在main.js中 或 router文件夹下的index.js中】
    • beforeEach: 进入路由之前的验证(常用,如:判断用户是否登录)
    • afterEach: 路由进入之后的验证(常用,如:修改页面标题)
  • 局部守卫:【写在路由组件内单独的守卫】
    • beforeRouteEnter: 进入路由之前的验证
    • beforeRouteLeave: 离开路由之前的验证(常用,离开当前页面提示是否保存内容)
    • beforeRouteUpdate: 组件路由更新前的验证
  • 独享守卫:【相当于写在路由配置里的全局守卫,只有前置守卫】
    • beforeEnter:进入路由之前的验证

三个参数

  • to: 要进入的路由对象
  • from: 要离开的路由对象
  • next: 放行函数

为什么组件中 data 是个函数⭐⭐⭐⭐⭐

在 Vue 底层中,在每次创建组件的时候,都会 new 一个VueComponent实例对象,他们的 data会挂载到VueComponent 的原型上共享,如果是一个对象的话,所有人都可以修改,但是如果是一个函数返回值的话就可以创建一个私有作用域来避免这个问题

vue 事件绑定原理

事件绑定分别分为组件事件绑定( 例如@click.native) 和非组件事件绑定,然后再对两种情况分别去处理,一种是 addEventListener 另一个是$on $emit

@click.sync native stop prevent self分别是做什么的⭐⭐⭐

  • @click.sync 语法
    • <comp :foo.sync="bar"></comp> 相当于 <comp :foo="bar" @update:foo="val => bar = val"></comp>
  • @click.native 父组件的原生事件需要加上 native,否则不生效
  • @click.stop 是阻止冒泡
  • @click.prevent 是阻止默认行为
  • @click.self 点击自己的时候才能触发

v-model 的原理⭐⭐⭐⭐⭐

:value @input 的语法糖

v-html 回会导致什么问题⭐⭐⭐⭐⭐

可能会造成 xss 攻击
v-html 会替换掉子标签

谈谈你对vue-router的 keep-alive 的理解⭐⭐⭐⭐⭐

keep-alive 有一个最大缓存限制,使用的是 LRU(最久未使用法)(使用了就放到最上边,先删最下边)

vue3 的 proxy有什么优缺点⭐⭐⭐⭐

  • 优点:
    • 不会像 Object.defineProperty那样遍历 Vue 中的 data、computed、props的全部属性。只是经过了一个类似于拦截的操作。
    • 而且也可以监听数组的变化,不会像曾经一样重写数组方法了;
  • 缺点:
    • 对浏览器版本要求高,因为 polyfill 没有办法弥补 proxy,如果想弥补,最多还只是使用Object.defineProperty

vue的$set是做什么的⭐⭐⭐

vue 中 data的数据,如果是对象的话,会把它的属性转为Object.defineProperty的getter和setter,使之变为响应式的。但是新增的属性不是响应式的。this.$set(this.obj,'e',0)

【Vue官网解释:】向响应式对象中添加一个 property,并确保这个新 property 同样是响应式的,且触发视图更新。它必须用于向响应式对象上添加新 property,因为 Vue 无法探测普通的新增 property (比如 this.myObject.newProperty = ‘hi’)

vue 常见的性能优化⭐⭐⭐⭐⭐

  • spa 使用 keep-alive
  • key 的使用
  • v-if 和v-show
  • v-if 不要和 v-for 一起使用
  • v-for 的优先级要早于 v-if,如果一起使用的话,会先遍历在判断 v-if,会造成性能问题;
    • 使用Object.freeze()方式冻结data中的属性,从而阻止数据劫持
  • 组件销毁的时候会断开所有与实例联系,但是除了addEventListener,所以当一个组件销毁的时候需要手动去removeEventListener
  • 图片懒加载
  • 路由懒加载
  • 防抖节流
  • 长列表固定个数
  • 为减少重新渲染和创建dom节点的时间,采用虚拟dom

v-show与v-if的区别

v-if如果为 true 的话就会走正常流程创建 ast 语法树,如果为 false 的话就会创建一个空节点

  • 相同点: v-show 和v-if都是true的时候显示,false的时候隐藏
  • 不同点1:原理不同
    • v-show:一定会渲染,只是修改display属性
    • v-if:根据条件渲染
  • 不同点2:应用场景不同
    • 频繁切换用v-show,不频繁切换用v-if

v-if 是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建,操作的实际上是dom元素的创建或销毁。
v-show 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换 它操作的是display:none/block属性。

一般来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。

什么是render函数⭐⭐⭐

render是一个函数,该参数有一个createElement形参,这形参也作为一个方法(可以动态创建标签),可传入三个参数:标签名、属性、内容

documentFragment 和一次性渲染有什么不同⭐⭐⭐⭐

  • documentFrament 首先是虚拟的,它的节点增加与删除肯定不会引起 dom 的变化的,所以如果增加节点或者删除节点使用 createDocumentFrament 的话会减少回流的操作。
  • Vue 也是使用了 CreateDocumentFragment 的。如果初次渲染,使用 documentFragment会多一个步骤,也就是创建这个documentFragment的步骤,所以一般首次加载是很慢的。

为什么避免v-if和v-for一起用

当 v-if 与 v-for 一起使用时,v-for 具有比 v-if 更高的优先级。这意味着 v-if 将分别重复运行于 每个 v-for 循环中,即先运行 v-for 的循环,然后在每一个 v-for 的循环中,再进行 v-if 的条件对比,会造成性能问题,影响速度。

Vue组件引入步骤

第一步,定义组件(创建组件)
第二步,引入组件、注册组件
第三步,使用组件(写组件标签)

<template>
	// 第三步: 模板中使用 组件
	<Hello></Hello>
</template>
<script>
// 第一步: 导入组件
import Hello from "./components/Hello.vue"
exports default {
	// 第二步: 注册组件
	components:{
		Hello
	}
}
</script>

props和data优先级谁高

优先级由高到低:
props --> methods --> data --> computed --> watch

动态路由配置(路由传参)方式

路由传参方式

vue-router的query和params传参区别

声明式导航传参
在 router-link 上的 to 属性传值,

  • 1、/path?参数名=值
    • 接收传递过来的值: $route.query.参数名
  • 2、/path/值/值 –> 需要路由对象提前配置 path: “/path/参数名”
    • 接收传递过来的值: $route.params.参数名

编程式导航传参
this.$router.push() 可以不传参数,根据传的值自动匹配是path还是name
因为使用path会自动忽略params ,所以会出现两种组合
注意:name+query/params,但是path+query
(1) name+params 方式传参
A页面传参

this.$router.push({
    name: 'xxx', // 跳转的路由
    params: {
      id: id   // 发送的参数
    }
})

B页面接收传参:

this.$route.params.id

(2) path+query 方式传参
A页面传参:

this.$router.push({
    path: '/xxx', // 跳转的路由
    query: {
      id: id    // 发送的参数
    }
})

B页面接参:

this.$route.query.id

params 和query 方式传参的区别

  • 1、写法上不同
  • 2、地址栏不同
  • 3、刷新方式不同

params和query的区别?

用法:query要用path来引入,params要用name来引入,接收参数都是类似的,分别是 this.$route.query.namethis.$route.params.name
url地址显示:query更加类似于ajax中get传参,params则类似于post,说的再简单一点,前者在浏览器地址栏中显示参数,后者则不显示
注意:query刷新不会丢失query里面的数据,params刷新会丢失 params里面的数据。

对组件的了解

对Vuex的了解

五种状态:

  • state: 存储公共数据 this.$store.state
  • mutations:同步操作,改变store的数据 this.$store.commit()
  • actions: 异步操作,让mutations中的方法能在异步操作中起作用 this.$store.dispatch()
  • getters: 计算属性 this.$store.getters
  • modules: 子模块
    使用场景:
    用户信息、菜单信息、购物车信息
    解决vuex页面刷新数据丢失问题的方式:
  • 办法一:将vuex中的数据直接保存到浏览器缓存中(sessionStorage、localStorage、cookie)
  • 办法二:在页面刷新的时候再次请求远程数据,使之动态更新vuex数据
  • 办法三:在某一组件向后台请求远程数据保存在vuex,并且在页面刷新前将vuex的数据保存至sessionStorage。在另一组件优先使用vuex内的数据,只有刷新后还没取到后台数据,才会从sessionStorage里取。
    • 优点:每次刷新页面更新sessionStorage,确保数据的安全性。刷新后还没取到后台数据就从sessionStorage中取,防止网络延迟、数据量大时vuex数据丢失问题。

Vue和Angular的区别

相同点: 都支持指令: 内置指令和自定义指令; 都支持过滤器; 内置过滤 器和自定义过滤器; 都支持双向数据绑定; 都不支持低端浏览器
不同点: AngularJS 的学习成本高, 比如增加了 Dependency Injection 特性, 而 Vue.js 本身提供的 API 都比较简单、直观;在性能上, AngularJS 依赖对数据做脏检查,所以 Watcher 越多越慢;Vue.js 使用基本依赖追踪 的观察并且使用异步队列更新, 所有的数据都是独立触发的

mounted和created区别

created与mounted真正的区别:
Created:在模板渲染成html前调用,即通常初始化某些属性值,然后再渲染成视图。
Mounted:在模板渲染成html后调用,通常是初始化页面完成后,再对html的dom节点进行一些需要的操作。
Created与Mounted的区别面试时的说法:
Created:在dom渲染之前调用,通常把初始化数据放在里面调用比如ajax数据等等。
Mounted:在dom渲染之后调用,比如咱们要获取document.getElementById,$(“#id”),ref等等需要dom操作放在这里,比如初始化的轮番图效果swiper需要获取dom就放在这里使用。
那么在用户体验解决了什么问题呢?
比如在传统模式下开发,网速慢的时候会先展示默认的静态数据,等ajax请求完成后才显示动态数据,这样对于用户体验不是很好,现在vue的钩子函数created就解决了这个问题,咱们把ajax请求的数据放到created里面,这样页面在加载dom之前就先把数据获取出来然后在渲染到页面上就解决了之前显示默认静态数据的问题,提升用户体验。

Vuex的属性

Vuex 有五个核心概念:state、getters、mutations、actions、modules
state => 基本数据
getters => 从基本数据派生的数据
mutations => 提交更改数据的方法,同步!
actions => 像一个装饰器,包裹 mutations,使之可以异步
modules => 模块化 Vuex

对MVVM的理解

mvvm是前端图层的概念,mvvm是双向的,视图层可以通过 ViewModel 转换成模型层,模型层也可以通过 ViewModel 转换成视图层

  • Model(每个页面的单独的数据)
  • ViewModel (双向绑定,M和V之间的枢纽)
  • View(视图,相当于HTML结构)

Vue3

vue3 相比于vue2有什么升级

  • 采用 ts 编写
  • composition API
  • 响应式原理使用的 proxy

1. vue2和vue3的区别

  • 监测机制的改变

    • vue3 中使用了ES6 的 ProxyAPI 对数据代理,监测的是整个对象,而不再是某个属性。
    • 消除了 Vue 2 当中基于 Object.defineProperty 的实现所存在的很多限制
    • vue3可以监测到对象属性的添加和删除,可以监听数组的变化;
    • vue3支持 Map、Set、WeakMap 和 WeakSet。
  • Vue3支持碎片(Fragments)

    • Vue2在组件中只有一个根节点。
    • Vue3在组件可以拥有多个根节点。
  • API模式不同

    • Vue2与Vue3 最大的区别:Vue2使用选项式API(Options API)对比Vue3组合式API(Composition API)
  • 建立数据的方式不同

    • Vue2:这里把数据放入data属性中
    • Vue3:需要使用一个新的setup()方法,此方法在组件初始化构造的时候触发。
    • 使用以下三步来建立响应式数据:
      • 从vue引入ref或reactive
      • 简单数据类型使用ref()方法处理,复杂类型数据用reactive()处理
      • 使用setup()方法来返回我们的响应性数据,从而我们的template可以获取这些响应性数据
  • 生命周期钩子不同 — Lifecyle Hooks

    • setup() :开始创建组件之前,在beforeCreate和created之前执行。创建的是data和method
    • onBeforeMount() : 组件挂载到节点上之前执行的函数。
    • onMounted() : 组件挂载完成后执行的函数。
    • onBeforeUpdate(): 组件更新之前执行的函数。
    • onUpdated(): 组件更新完成之后执行的函数。
    • onBeforeUnmount(): 组件卸载之前执行的函数。
    • onUnmounted(): 组件卸载完成后执行的函数
    • onActivated(): 被包含在中的组件,会多出两个生命周期钩子函数。被激活时执行 。
    • onDeactivated(): 比如从 A组件,切换到 B 组件,A 组件消失时执行。

【若组件被<keep-alive>包含,则多出上面↑两个钩子函数】

  • 父子传参不同,子组件通过defineProps()进行接收,并且接收这个函数的返回值进行数据操作。

总结: vue3 性能更高, 体积更小, 更利于复用, 代码维护更方便

2. defineProperty和proxy的区别

Vue 在实例初始化时遍历 data 中的所有属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter。并 劫持各个属性 getter 和 setter,在数据变化时发布消息给订阅者,触发相应的监听回调,而这之间存在几个问题

  • 初始化时需要遍历对象所有 key,如果对象层次较深,性能不好
  • 通知更新过程需要维护大量 dep 实例和 watcher 实例,额外占用内存较多
  • Object.defineProperty 无法监听到数组元素的变化,只能通过劫持重写数方法
  • 动态新增,删除对象属性无法拦截,只能用特定 set/delete API 代替
  • 不支持 Map、Set 等数据结构

Vue3 使用 Proxy 来监控数据的变化。Proxy 是 ES6 中提供的功能,其作用为:用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)。相对于Object.defineProperty(),其有以下特点:

  • Proxy 直接代理整个对象而非对象属性,这样只需做一层代理就可以监听同级结构下的所有属性变化,包括新增属性和删除属性。
  • 它的处理方式是在 getter 中去递归响应式,这样的好处是真正访问到的内部属性才会变成响应式,简单的可以说是按需实现响应式,减少性能消耗。
  • Proxy 可以监听数组的变化。

3. Vue3 Diff算法和 Vue2 的区别

我们知道在数据变更触发页面重新渲染,会生成虚拟 DOM 并进行 patch 过程,这一过程在 Vue3 中的优化有如下
编译阶段的优化:

  • 事件缓存:将事件缓存(如: @click),可以理解为变成静态的了
  • 静态提升:第一次创建静态节点时保存,后续直接复用
  • 添加静态标记:给节点添加静态标记,以优化 Diff 过程

由于编译阶段的优化,除了能更快的生成虚拟 DOM 以外,还使得 Diff 时可以跳过"永远不会变化的节点",

Diff 优化如下

  • Vue2 是全量 Diff,Vue3 是静态标记 + 非全量 Diff
  • 使用最长递增子序列优化了对比流程

根据尤大公布的数据就是 Vue3 update 性能提升了 1.3~2 倍

4. composition API 与 options API的区别

  • vue2 采用的就是 optionsAPI
    (1) 优点:易于学习和使用, 每个代码有着明确的位置 (例如: 数据放 data 中, 方法放 methods中)
    (2) 缺点: 相似的逻辑, 不容易复用, 在大项目中尤为明显
    (3) 虽然 optionsAPI 可以通过mixins 提取相同的逻辑, 但是也并不是特别好维护

  • vue3 新增的就是 compositionAPI
    (1) compositionAPI 是基于 逻辑功能 组织代码的, 一个功能 api 相关放到一起
    (2) 即使项目大了, 功能多了, 也能快速定位功能相关的 api
    (3) 大大的提升了 代码可读性可维护性

  • vue3 推荐使用 composition API, 也保留了options API
    即就算不用composition API, 用 vue2 的写法也完全兼容!!

5. Composition API与React Hook很像,区别是什么

从React Hook的实现角度看,React Hook是根据useState调用的顺序来确定下一次重渲染时的state是来源于哪个useState,所以出现了以下限制

  • 不能在循环、条件、嵌套函数中调用Hook
  • 必须确保总是在你的React函数的顶层调用Hook
  • useEffect、useMemo等函数必须手动确定依赖关系

而Composition API是基于Vue的响应式系统实现的,与React Hook的相比

  • 声明在setup函数内,一次组件实例化只调用一次setup,而React Hook每次重渲染都需要调用Hook,使得React的GC比Vue更有压力,性能也相对于Vue来说也较慢
  • Compositon API的调用不需要顾虑调用顺序,也可以在循环、条件、嵌套函数中使用
  • 响应式系统自动实现了依赖收集,进而组件的部分的性能优化由Vue内部自己完成,而React Hook需要手动传入依赖,而且必须必须保证依赖的顺序,让useEffect、useMemo等函数正确的捕获依赖变量,否则会由于依赖不正确使得组件性能下降。

虽然Compositon API看起来比React Hook好用,但是其设计思想也是借鉴React Hook的。

6. setup 函数

setup() 函数是 vue3 中,专门为组件提供的新属性。它为我们使用 vue3的 Composition API 新特性提供了统一的入口, setup 函数会在 beforeCreatecreated 之前执行, vue3也是取消了这两个钩子,统一用setup代替, 该函数相当于一个生命周期函数,vue中过去的datamethodswatch等全部都用对应的新增api写在setup()函数中

setup() 接收两个参数 propscontext。它里面不能使用 this,而是通过 context 对象来代替当前执行上下文绑定的对象,context 对象有四个属性:attrsslotsemitexpose

里面通过 refreactive 代替以前的 data 语法,return 出去的内容,可以在模板直接使用,包括变量和方法

<template>
  <div class="container">
    <h1 @click="say()">{{msg}}</h1>
  </div>
</template><script>
export default {
  setup (props,context) {
    console.log('setup执行了')
    console.log(this)  // undefined
    // 定义数据和函数
    const msg = 'hi vue3'
    const say = () => {
      console.log(msg)
    }
    // Attribute (非响应式对象,等同于 $attrs)
    context.attrs
    // 插槽 (非响应式对象,等同于 $slots)
    context.slots
    // 触发事件 (方法,等同于 $emit)
    context.emit
    // 暴露公共 property (函数)
    context.expose
​
    return { msg , say}
  },
  beforeCreate() {
    console.log('beforeCreate执行了')
    console.log(this)  
  }
}
</script>

7. setup语法糖 (script setup语法)

script setup是在单文件组件 (SFC) 中使用组合式 API 的编译时语法糖。相比于普通的 script 语法更加简洁
要使用这个语法,需要将 setup attribute 添加到 <script> 代码块上:
格式:

<script setup>
console.log('hello script setup')
</script>

顶层的绑定会自动暴露给模板,所以定义的变量,函数和import导入的内容都可以直接在模板中直接使用

<template>
  <div>
    <h3>根组件</h3>
    <div>点击次数:{{ count }}</div>
    <button @click="add">点击修改</button>
  </div>
</template><script setup>
import { ref } from 'vue'const count = ref(0)
const add = () => {
  count.value++
}
</script>

使用 setup 语法糖时,不用写 export default {},子组件只需要 import 就直接使用,不需要像以前一样在 components 里注册,属性和方法也不用 return。
并且里面不需要用 async 就可以直接使用 await,因为这样默认会把组件的 setup 变为 async setup
用语法糖时,props、attrs、slots、emit、expose 的获取方式也不一样了
3.0~3.2版本变成了通过 import 引入的 API:definePropsdefineEmituseContext(在3.2版本已废弃),useContext 的属性 { emit, attrs, slots, expose }
3.2+版本不需要引入,而直接调用:definePropsdefineEmitsdefineExposeuseSlotsuseAttrs

8. reactive、 shallowReactive 函数

reactive
reactive() 函数接收一个普通对象,返回一个响应式的数据对象, 相当于 Vue 2.x 中的 Vue.observable() API,响应式转换是“深层”的——它影响所有嵌套属性。基于proxy来实现,想要使用创建的响应式数据也很简单,创建出来之后,在setupreturn出去,直接在template中调用即可
shallowReactive
创建一个响应式代理,它跟踪其自身属性的响应性shallowReactive生成非递归响应数据,只监听第一层数据的变化,但不执行嵌套对象的深层响应式转换 (暴露原始值)。

9. ref、 shallowRef 、isRef、toRefs 函数

ref
ref() 函数用来根据给定的值创建一个响应式的数据对象,ref() 函数调用的返回值是一个对象,这个对象上只包含一个 value 属性, 只在setup函数内部访问ref函数需要加.value,其用途创建独立的原始值

reactive 将解包所有深层的 refs,同时维持 ref 的响应性。当将 ref分配给 reactive property 时,ref 将被自动解包
在这里插入图片描述
shallowRef
ref() 的浅层作用形式。shallowRef() 常常用于对大型数据结构的性能优化或是与外部的状态管理系统集成
isRef
isRef() 用来判断某个值是否为 ref() 创建出来的对象
toRefs
使用场景: 如果对一个响应数据, 进行解构 或者 展开, 会丢失他的响应式特性!
原因: vue3 底层是对 对象 进行监听劫持
作用: 对一个响应式对象的所有内部属性, 都做响应式处理

  • reactive/ref的响应式功能是赋值给对象的, 如果给对象解构或者展开, 会让数据丢失响应式的能力
  • 使用 toRefs 可以保证该对象展开的每个属性都是响应式的

10. readonly、isReadonly、shallowReadonly函数

readonly
传入refreactive对象,并返回一个原始对象的只读代理,对象内部任何嵌套的属性也都是只读的、 并且是递归只读。
isReadonly
检查对象是否是由 readonly 创建的只读对象
shallowReadonly
shallowReadonly 作用只处理对象最外层属性的响应式(浅响应式)的只读,但不执行嵌套对象的深度只读转换 (暴露原始值)

readonlyconst有什么区别?

  • const是赋值保护,使用const定义的变量,该变量不能重新赋值。但如果const赋值的是对象,那么对象里面的东西是可以改的。原因是const定义的变量不能改说的是,对象对应的那个地址不能改变
  • readonly是属性保护,不能给属性重新赋值

11.computed、watch函数

computed
该函数用来创造计算属性,和过去一样,它返回的值是一个ref对象。 里面可以传方法,或者一个对象,对象中包含set()get()方法
watch
watch 函数用来侦听特定的数据源,并在回调函数中执行副作用。默认情况是懒执行的,也就是说仅在侦听的源数据变更时才执行回调。

// 监听单个ref
const money = ref(100)
watch(money, (value, oldValue) => {
  console.log(value)
})// 监听多个ref
const money = ref(100)
const count = ref(0)
watch([money, count], (value) => {
  console.log(value)
})// 监听ref复杂数据
const user = ref({
  name: 'zs',
  age: 18,
})
watch(
  user,
  (value) => {
    console.log('user变化了', value)
  },
  {
    // 深度监听,,,当ref的值是一个复杂数据类型,需要深度监听
    deep: true,
    immediate: true
  }
)// 监听对象的某个属性的变化
const user = ref({
  name: 'zs',
  age: 18,
})
watch(
  () => user.value.name,
  (value) => {
    console.log(value)
  }
)

11. watch 和 watchEffect 的区别

watch 作用是对传入的某个或多个值的变化进行监听;触发时会返回新值和老值;也就是说第一次不会执行,只有变化时才会重新执行

watchEffect 是传入一个函数,会立即执行,所以默认第一次也会执行一次;不需要传入监听内容,会自动收集函数内的数据源作为依赖,在依赖变化的时候又会重新执行该函数,如果没有依赖就不会执行;而且不会返回变化前后的新值和老值

共同点是 watchwatchEffect 会共享以下四种行为:

  • 停止监听:组件卸载时都会自动停止监听
  • 清除副作用:onInvalidate 会作为回调的第三个参数传入
  • 副作用刷新时机:响应式系统会缓存副作用函数,并异步刷新,避免同一个 tick 中多个状态改变导致的重复调用
  • 监听器调试:开发模式下可以用 onTrack 和 onTrigger 进行调试

12. Vue3 的生命周期

基本上就是在 Vue2 生命周期钩子函数名基础上加了 on;beforeDestory 和 destoryed 更名为 onBeforeUnmount 和 onUnmounted;然后用setup代替了两个钩子函数 beforeCreate 和 created;新增了两个开发环境用于调试的钩子
在这里插入图片描述

13. setup语法下怎么设置name属性

  • 安装插件
yarn add vite-plugin-vue-setup-extend -D
  • 配置 vite.config.ts
import vueSetupExtend from 'vite-plugin-vue-setup-extend'export default defineConfig({
  plugins: [vue(), vueSetupExtend()],
})

  • 在标签中使用
<script setup name="MyCom">
    // 必须在script标签里面写一点类容,这个插件才会生效,哪怕是注释
</script>

14. Vue3怎么让全局组件有提示

vue3中如果注册的是局部组件,那么props是有类型提示的,但是如果注册的是全局组件,props就没有类型提示了
在这里插入图片描述
解决办法

// 在src目录下新建一个文件 global.d.ts
import XtxSkeleton from '@/components/XtxSkeleton/XtxSkeleton.vue'
// 参考:
declare module 'vue' {
  export interface GlobalComponents {
    XtxSkeleton: typeof XtxSkeleton
  }
}
export {}

15. Vue3怎么注册全局自定义指令

app.directive('lazy'{  // app.directive('指令名‘,配置对象)
    mounted(el){
        .......
    }
})

16. Vite 和Webpack的区别

  • 都是现代化打包工具
  • 启动方式不一样。vite在启动的时候不需要打包,所以不用分析模块与模块之间的依赖关系,不用进行编译。这种方式就类似于我们在使用某个UI框架的时候,可以对其进行按需加载。同样的,vite也是这种机制,当浏览器请求某个模块时,再根据需要对模块内容进行编译。按需动态编译可以缩减编译时间,当项目越复杂,模块越多的情况下,vite明显优于webpack.
  • 热更新方面,效率更高。当改动了某个模块的时候,也只用让浏览器重新请求该模块,不需要像webpack那样将模块以及模块依赖的模块全部编译一次。

缺点

  • vite相关生态没有webpack完善,vite可以作为开发的辅助。

17. pinia的好处

  • pinia和vuex4一样,也是vue 官方 状态管理工具(作者是 Vue 核心团队成员)

  • pinia相比vuex4,对于vue3的 兼容性 更好

  • pinia相比vuex4,具备完善的 类型推荐 => 对 TS 支持很友好

  • pinia同样支持vue开发者工具

  • Pinia 的 API 设计非常接近 Vuex 5 的提案
    pinia核心概念

    • state: 状态
    • actions: 修改状态(包括同步和异步,pinia中没有mutations)
    • getters: 计算属性

vuex只能有一个根级别的状态, pinia 直接就可以定义多个根级别状态

18. Vue3的v-model语法

  1. 父组件给子组件传入一个modelValue的属性
  2. 子组件通知父组件的update:modelValue事件,将修改后的值传给父组件
  3. 父组件监听 update:modelValue,修改对应的值
// 父组件
// 原生写法
<son :model-value="money" @update:modelValue="val=>money = val" />
// v-mode语法糖写法
<son v-model="money" v-mode:house="house" />
    
 // 子组件
<button @click="$emit('update:modelValue',modelValue+100)">点我加钱 </button>

好处是什么

为了整合 .sync和v-model

在Vue2中,v-mode只能绑定一个属性,如果需要绑定多个属性则需要借助.sync修饰符

.sync修饰符在Vue3中已被移除,直接被v-model取代。

新增vue面试题

Git

Git 中 rebase 和 merge 的区别⭐⭐⭐⭐⭐

都是合并分支,
rebase 不会产生额外的 commit,
而 merge 会把这两个分支的遗漏 commit 记录重新创建一个commit保存起来。比较臃肿,所以尽量不要用 merge。

git fetch 和 git pull 的区别⭐⭐⭐⭐⭐

git fetch 是把远程代码拉下来但是不会合并
git pull 会自动合并

git的常用命令⭐⭐⭐⭐⭐

git常用命令
git add
git commit
git push
git clone
git pull
git fetch
git merge
git rebase
git log
git status
git branch
git checkout
在这里插入图片描述

git常用命令与常见面试题总结

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值