2023年Vue2前端面试题(全面)

2023年Vue2前端面试题(全面)

1.v-if与v-show区别?

v-show通过css display控制显示和隐藏,v-if组件真正的渲染和销毁,而不是显示和隐藏,频繁切

换状态使用v-show ,否则使用v-if

2.为何v-for要使用key?

快速查找到节点,减少渲染次数,提升渲染性能

3.简单描述vue组件生命周期?

单组件生命周期:

        挂载:beforeCreate==>created==>beforeMount==>mounted

        更新:beforeUpdate==>updated

        销毁:beforeDestory==>destoryed

父子组件生命周期:

        挂载:parent beforeCreate==>parent created==>parent beforeMount==>child

beforeCreate==>chlid created==>chlid beforeMount==>child mounted==>parent mounted

        更新:parent beforenUpdata==>chlid beforeUpdate==>child updated==>parent updated

        销毁:parent beforeDestory==>child beforeDestory==>child destoryed==>parent detoryed

从以上可以看出:

挂载时:子组件是在父组件beforeMount后开始挂载,并且子组件先mounted,父组件随后

更新时:子组件是在父组件beforeUpdate后开始更新,并且子组件先于父组件更新

销毁时:子组件是在父组件beforeDestory后开始销毁,并且子组件先于父组件销毁

4.vue组件如何进行组件通信?

1)父组件向子组件传递数据--使用props

功能:让组件接收外部传过来的数据 ;

        传递数据: 直接在组件标签内写属性

        接收数据:

第一种方式:(只接收)

props: ['name']

第二种接收方式:(限定类型)

props:{
  name:String
}

第三种接收方式:(限定类型+限制必要性+指定默认值)

props:{
 name: {
      type:String, //类型
      required:true, //必要性
      default:'老王' // 默认值
     }
   }

备注:props是只读的,Vue底层会监视你对props的修改,如果进行了修改,就会发出警告,若业

务需 求确实需 要修改那么复制props的内容到data中一份,然后去修改data中的数据

父组件:

<template>
  <div>
    <Student name= '张三' sex='男' :age='18'></Student>
  </div>
</template>
 
<script>
import Student from './***ponents/Student.vue'
export default {
  name: 'app',
  ***ponents:{Student}
}
 
</script>

子组件:

<template>
  <div>
    <h1>{{msg}}</h1>
    <h2>学生姓名:{{name}}</h2>
    <h2>学生性别:{{sex}}</h2>
    <h2>学生年龄:{{myAge+1}}</h2>
    <button @click= 'updateAge'>尝试修改收到的年龄</button>
  </div>
</template>
 
<script>
export default {
  name: 'Student',
  data() {
    return {
      msg:'我是一个尚硅谷的学生',
      myAge:this.age
   }
 },
  methods: {
    updateAge() {
      this.myAge++
   }
 },
  // 简单声明接收
  props: ['name', 'age', 'sex'] 
 
  // 接收的同时对数据类型进行限制
  // props: {
  //   name:String,
  //   age:Number,
  //   sex:String
  // }
 
 
  //接收的同时:对数据类型进行限制+默认值的指定+必要值的限定
  // props:{
  //   name: {
  //     type:String, // name的类型是字符串
  //     required:true // name是必要的
  //   },
  //   age: {
  //     type:Number,
  //     default:99 // 默认值
  //   },
  //   sex: {
  //     type:String,
  //     required:true
  //  }
  // }
}
 
</script>

2)子组件向父组件传递数据--可以通过自定义事件

父组件:

<template>
	<div class="app">
		<h1>{{msg}},学生姓名是:{{studentName}}</h1>
 
		<!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据-->
		<Student @atguigu="getStudentName"/> 
 
	</div>
</template>
 
<script>
	import Student from './***ponents/Student'
 
	export default {
		name:'App',
		***ponents:{Student},
		data() {
			return {
				msg:'你好啊!',
				studentName:''
			}
		},
		methods: {
			getSchoolName(name){
				console.log('App收到了学校名:',name)
				this.studentName = name
			}
		},
	}
</script>
 

子组件:

<template>
	<div class="school">
		<h2>学校名称:{{name}}</h2>
		<h2>学校地址:{{address}}</h2>
		<button @click="sendSchoolName">把学校名给App</button>
	</div>
</template>
 
<script>
	export default {
		name:'School',
		data() {
			return {
				name:'小剑',
				address:'北京',
			}
		},
		methods: {
			 sendSchoolName(){
			 	this.$emit('atguigu', this.name)
			 }
		},
	}
</script>

3)ref 链:父组件要给子组件传值,在子组件上定义一个 ref 属性,这样通过父组件的 $refs 属性

就可以获取子组件的值了,也可以进行父子,兄弟之间的传值($parent / $children与 ref类似)

4)兄弟组件通信--使用全局事件总线($bus)

安装全局事件总线:

new Vue({
    .....
beforeCreate() {
Vue.prototype.$bus = this//安装全局事件总线,$bus就是当前应用的vm
 },
    ....
})

示例代码如下:

// 在main.js中安装全局事件总线
//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//关闭Vue的生产提示
Vue.config.productionTip = false
 
//创建vm
new Vue({
	el:'#app',
	render: h => h(App),
	beforeCreate() {
		Vue.prototype.$bus = this //安装全局事件总线
	},
})

接收数据的组件:

接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身

methods:{
 demo(data) {.....}
 },
 mounted() {
 this.$bus.$on('xxx', this.demo)// 或者是写箭头函数的回调
 
}

示例代码如下:

<template>
	<div class="school">
		<h2>学校名称:{{name}}</h2>
		<h2>学校地址:{{address}}</h2>
		<h2>收到的学生的名字是:{{studentName}}</h2>
	</div>
</template>
 
<script>
	export default {
		name:'School',
		data() {
			return {
				name:'小剑',
				address:'北京',
				studentName:''
			}
		},
		methods:{
			// getName(name) {
			// 	this.studentName = name
			// },
		},
		mounted() {
			// 写法一,把回调函数写在methods函数当中
            // this.$bus.$on('hello', this,getName)
            // 写法二,直接写回调函数
			this.$bus.$on('hello', (name)=> {
				this.studentName = name
			})
		},
		beforeDestroy() {
			this.$bus.$off('hello')
		},
	}
</script>

提供数据 :this.$bus.$emit('xxx', 数据)

示例代码如下:

<template>
	<div class="student">
		<h2>学生姓名:{{name}}</h2>
		<h2>学生性别:{{sex}}</h2>
		<button @click="sendStudentName">把学生名给School组件</button>
	</div>
</template>
 
<script>
	export default {
		name:'Student',
		data() {
			return {
				name:'张三',
				sex:'男',
			}
		},
		methods: {
			sendStudentName(){
				this.$bus.$emit('hello',this.name)
			}
		},
	}
</script>
 

5)使用消息订阅与发布--使用于任意组件通信

使用步骤

        1).安装pubsub:npm i pubsub-js

        2).引入:import pubsub from 'pubsub-js'

        3).接收数据:A组件想要接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身

        4).提供数据:pubsub.publish('xxx',数据)

接收数据的组件示例代码如下:

<template>
	<div class="school">
		<h2>学校名称:{{name}}</h2>
		<h2>学校地址:{{address}}</h2>
		<h2>收到学生的名字:{{studentName}}</h2>
	</div>
</template>
 
<script>
	import pubsub from 'pubsub-js'
	export default {
		name:'School',
		data() {
			return {
				name:'小剑',
				address:'北京',
				studentName:''
			}
		},
		mounted() {
			this.pubId = pubsub.subscribe('hello',(msgName,data)=>{
				console.log(this, msgName, data)
				this.studentName = data
			})
		},
		beforeDestroy() {
			pubsub.unsubscribe(this.pubId)
		},
	}
</script>

提供数据的代码如下:

<template>
	<div class="student">
		<h2>学生姓名:{{name}}</h2>
		<h2>学生性别:{{sex}}</h2>
		<button @click="sendStudentName">把学生名给School组件</button>
	</div>
</template>
 
<script>
	import pubsub from 'pubsub-js'
	export default {
		name:'Student',
		data() {
			return {
				name:'张三',
				sex:'男',
			}
		},
		methods: {
			sendStudentName(){
				pubsub.publish('hello',this.name)
			}
		},
	}
</script>
 

6)provide inject组件通信

7)vuex

5.双向绑定v-model的实现原理

双向数据绑定最核心的方法便是通过Object.defineProperty()来实现对属性的劫持,达到监听数据

变动的目的。

首先是从data里面的数据通过绑定到input控件和p标签上,然后在input上通过input监听控件,触发

change事件,调用方法都可以默认获取e事件,e.target.value是获取调用该方法的dom对象上的

value值,把value值赋给data里面的初始数据,从而实现双向数据绑定的原理

6.对MVVM的理解?

M==>mode(数据模型),V==>vie(代表UI组件,负责将数据转换为UI展现出来),

VM==>ViewModel,在MVVM架构下,View和Model没有直接的关系,而是通过ViewModel进行交

互的,Model和ViewModel之间的交互是相互的,所以view数据的变化会同步到Model上,而Model

的变化也会同步到View上

7.***puted(计算属性)与watch(侦听器)的区别?

1)计算属性可以被缓存,侦听属性不能被缓存

2)侦听属性可以包含异步任务,而计算属性不行

3)计算属性通常是由一个或多个响应式数据计算出一个值返回;而侦听属性通常是监听一个数据

的变化,由这一个数据的变化可能影响到另外的一个或多个数据的变化

4)是否调用return:计算属性中的函数必须要用return返回,而侦听器不用

5)***puted默认第一次加载的时候就开始监听;watch默认第一次加载不做监听,如果需要第一

次加载做监听,添加immediate属性,设置为true(immediate:true)

8.data为什么必须是一个函数?

如果data是一个对象,则任意一个实例对data中的数据进行修改时其他的示例都会收到影响,而将

data定义为一个函数,在该函数体内部返回一个函数,这样,在创建组件实例时,会调用data()方

法,获取返回的新的数据对象,则各个组件实例就会有自己独立的数据对象,组件间互不影响

9.何时使用keep-alive?

缓存组件不需要重复渲染,多个静态tab页切换,优化性能

10.何时使用beforeDestory?

1)解绑自定义事件

2)清零定时器

3)解绑自定义dom事件,如window.scroll等

11.vue为何是异步渲染?$nextTick有何用?

因为如果不采用异步更新,那么每次更新数据都会对当前组件进行重新渲染,所以考虑性能问题,

Vue会在本轮数据更新之后,再去异步更新视图

$nextTick 是在下次 DOM 更新循环结束之后执行延迟回调,在修改数据之后使用 $nextTick,则可

以在回调中获取更新后的 DOM

12.vue常见的性能优化有哪些?

1)合理使用v-if和v-show

2)合理使用***puted

3)使用v-for的时候动态添加key

4)自定义事件,dom事件在beforeDestory中及时销毁

5)合理使用异步组件

6)合理使用keep-alive

7)data层级不要太深

8)使用vue-loader在开发环境做编译模板

9)前端通用性能优化(图片懒加载/减少HTTP请求/合理设置HTTP缓存/资源合并与压缩/合并CSS

图片/将CSS放在header中/避免重复的资源请求/切分到多个域名)

10)使用ssr

代码层面:

        v-if 和 v-show 区分使用场景

        ***puted 和 watch  区分使用场景

        v-for 遍历必须为 item 添加 key,且避免同时使用 v-if

        长列表性能优化

        事件的销毁 addEventlisenter 事件监听

        图片资源懒加载

        路由懒加载

        第三方插件的按需引入

        优化无限列表性能

        服务端渲染 SSR or 预渲染

Webpack 层面的优化

        Webpack 对图片进行压缩

        减少 ES6 转为 ES5 的冗余代码

        提取公共代码

        模板预编译

        提取组件的 CSS

        优化 SourceMap

        构建结果输出分析

        Vue 项目的编译优化

基础的 Web 技术的优化

        开启 gzip 压缩

        浏览器缓存

        CDN 的使用

        使用 Chrome Performance 查找性能瓶颈

13.vuex是什么?怎么使用?使用场景有哪些?

vuex 是一个专门为 vue 构建的状态管理工具,主要是为了解决 多组间之间状态共享问题。强调的

是集中式管理,(组件与组件之间的关系变成了组件与仓库之间的关系)。

vuex 的核心包括:state(存放状态)、mutations(同步的更改状态)、actions(发送异步请求,

拿到数据)、getters(根据之前的状态派发新的状态)、modules(模块划分)。

state 发布一条新的数据,在 getters 里面根据状态派发新的状态,actions 发送异步请求获取数

据,然后在 mutations 里面同步的更改数据。

应用场合:购物车的数据共享、登入注册

14.vue指令?

v-html   // html

v-text   // 元素里要显示的内容

v-bind:data    // 绑定动态数据   :data

v-on:click      // 绑定事件       @click

        .stop 阻止事件继续传播

        .prevent  阻止默认时间

        .capture 使用事件捕获模式,即元素自身触发的事件先在此处处理,然后才交由内部元素进行

处理

        .self 只当在 event.target 是当前元素自身时触发处理函数

        .once 事件将只会触发一次

        .passive 告诉浏览器你不想阻止事件的默认行为

v-for  // 循环,vue2中v-for的优先级高于v-if,不推荐一起使用

v-if   //  条件渲染指令  如果是真则渲染节点,否则不渲染节点

v-if、v-else 和 v-else-if :类似js的if...else判断语句

v-show:通过display:none;控制元素显示隐藏

v-model    //  双向绑定,用于表单

        .lazy 默认情况下,v-model同步输入框的值和数据。可以通过这个修饰符,转变为在change

事件再同步。

        .number 自动将用户的输入值转化为数值类型

        .trim 自动过滤用户输入的首尾空格

v-pre :主要用来跳过这个元素和它的子元素编译过程。

v-cloak :保持在元素上直到关联实例结束时进行编译。

v-once :关联的实例,只会渲染一次。之后的重新渲染,实例极其所有的子节点将被视为静态内

容跳过,这可以用于优化更新性能

15.vue.js两个核心是什么?

数据驱动和组件化

16.说说vue动态组件?

多个组件通过同一个挂载点进行组件的切换,is的值是哪个组件的名称,那么页面就会显示哪个组

件。

17.$router和$route的区别是什么?

$router为VueRouter的实例,是一个全局路由对象,包含了路由跳转的方法、钩子函数等。

$route 是路由信息对象或者是跳转的路由对象,每一个路由都会有一个route对象,是一个局部对

象,包含path,params,hash,query,fullPath,matched,name等路由信息参数。

18.路由钩子(路由守卫)有哪些?

全局路由守卫

        router.beforeEach(to, from, next),全局前置守卫

        router.beforeResolve(to, from, next),全局的解析守卫

        router.afterEach(to, from ,next) 全局的后置守卫

组件内守卫    路由独享的守卫

        beforeRouteEnter

        beforeRouteUpdate

        beforeRouteLeave

路由独享守卫

        beforeEnter   组件内的守卫

从a页面进入b页面时触发的生命周期

1)beforeRouteLeave:路由组件的组件离开路由钩子,可取消路由离开;

2)beforeEach:路由全局前置守卫,可用于登录验证,全局路由loading等;

3)beforeEnter:独享路由守卫;

4)beforeRouteEnter:路由的组件进入路由前钩子;

5)beforeResolve:路由全局解析守卫;

6)afterEach:路由全局后置钩子;

7)beforeCreate:组件生命周期,不能访问this;

8)created:组件生命周期,可以访问this,不能访问dom;

9)beforeMount:组件生命周期;

10)deactivated:离开缓存组件a,或者触发a的beforeDestory和destoryed组件销毁钩子

11)mounted:访问/操作dom;

12)activated:进入缓存组件,进入a的嵌套子组件(如果有的话);

13)执行beforeRouteEnter回调函数next

19.对keep-alive的理解?

keep-alive缓存vue实例,提高性能是 Vue 内置的一个组件,可以使被包含的组件保留状态,避免

重新渲染 。

提供 include 和 exclude 属性,两者都支持字符串或正则表达式, include 表示只有名称匹配的组

件会被缓存,exclude 表示任何名称匹配的组件都不会被缓存 ,其中 exclude 的优先级比 include

高;对应两个钩子函数 activated 和 deactivated ,当组件被激活时,触发钩子函数 activated,当

组件被移除时,触发钩子函数 deactivated。

20.***puted、watch(自动监听、深度监听)、methods区别?

我们可以将同一函数定义为一个 method 或者一个计算属性。对于最终的结果,两种方式是相同

的。

不同点:

***puted:计算属性是基于它们的依赖进行缓存的,只有在它的相关依赖发生改变时才会重新求

值。

methods:只要发生重新渲染, method 调用总会执行该函数。

watch监听对象需要深度监听,默认是浅监听

当页面中有某些数据依赖其他数据进行变动的时候,可以使用计算属性***puted。

watch用于观察和监听页面上的vue实例,如果要在数据变化的同时进行异步操作或者是比较大的

开销,那么watch为最佳选择。

21.vue中对象更改检测的注意事项?

由于 JavaScript 的限制,Vue 不能检测对象属性的添加或删除:

对于已经创建的实例,Vue 不能动态添加根级别的响应式属性。但是,可以使用 `Vue.set(object,

key, value)`方法向嵌套对象`添加响应式属性`。为已有对象赋予多个新属性,比如使用

`Object.assign()`或 `_.extend()`。在这种情况下,你应该用两个对象的属性创建一个新的对象

22.ref的作用?

获取dom元素 this.$refs.box

获取子组件中的data this.$refs.box.msg

调用子组件中的方法 this.$refs.box.open()

23.路由传参的方式?

query传参和params传参

1)声明式导航

不带参跳转 对应的地址为/foo

url字符串拼接传参 对应的地址为/foo?id=123

query方式对象形式传参 对应的地址为/foo?id=123

params方式对象形式传参 对应地址为 /path/123 , 注意params和query一起使用params会失效,

params与name一起使用

2)编程式导航(路由实例对象router=new VueRouter())

字符串router.push('home')

对象router.push({ path: 'home' })

命名的路由 对应路径为/path/123

router.push({ name: 'user', params: { userId: '123' }})

带查询参数,变成 /register?plan=123

router.push({ path: 'register', query: { plan: '123' }})

接收参数

this.$route.params.id

this.$route.query.xxx

24.vuex页面刷新数据丢失的问题怎么解决?

问题:F5页面刷新,页面销毁之前的资源,重新请求,因此写在生命周期里的vuex数据是重新初

始化,无法获取的,这也就是为什么会打印出空的原因。

解决思路1:

使用localStorage sessionStorage或者是cookie

实际使用vuex值变化时,H5刷新页面,vuex数据重置为初始状态,所以还是要用到localStorage

解决思路2:

使用插件vuex-persistedState

vuex-persistedState默认持久化所有的state,可以指定需要持久化的state

25.vue生命周期在哪些场景中使用?

beforeCreate

创建前,访问不到data当中的属性以及methods当中的属性和方法,可以在当前生命周期创建一个

loading,在页面加载完成之后将loading移除

created

创建后,当前生命周期执行的时候会遍历data中所有的属性,给每一个属性都添加一个getter、

setter方法,将data中的属性变成一个响应式属性

beforeMount

模板与数据进行结合,但是还没有挂载到页面上。因此我们可以在当前生命周期中进行数据最后的

修改

mounted

当前生命周期数据和模板进行相结合,并且已经挂载到页面上了,因此我们可以在当前生命周期中

获取到真实的DOM元素,在这发起后端请求,拿回数据,配合路由钩子做一些事情

beforeUpdate

当数据发生改变的时候当前生命周期就会执行,因此我们可以通过当前生命周期来检测数据的变化

当前生命周期执行的时候会将更新的数据与模板进行相结合,但是并没有挂载到页面上,因此我们

可以在当前生命周期中做更新数据的最后修改

updated

数据与模板进行相结合,并且将更新后的数据挂载到了页面上。因此我们可以在当前生命周期中获

取到最新的DOM结构

beforeDestory

当前生命周期中我们需要做事件的解绑  监听的移除  定时器的清除等操作

destoryed

当前生命周期执行完毕后会将vue与页面之间的关联进行断开

当<keep-alive>包裹动态组件的时候会触发两个新的生命周期

 activated     当组件为活跃状态的时候触发(活跃状态:进入页面的时候)

deactivated     缓存状态的时候触发

26.$set是干什么用的?

当数据变化但没有更新视图时使用,例如对象新增加的属性,数组新增加的成员

this.$set(obj,"key","value")

27.$nextTick的作用是什么?

在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的

DOM。

例如:在created生命周期中想要操作dom就可以使用

this.$nextTick(()=>{ ... })

可以在mounted之前的生命周期中操作dom

28.mixin是干什么的?

mixin提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意

组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。

var mixin = {
  data: function () {
    return {
      message: 'hello',
      foo: 'abc'
    }
  }
}
​
new Vue({
  mixins: [mixin],
  data: function () {
    return {
      message: 'goodbye',
      bar: 'def'
    }
  },
  created: function () {
    console.log(this.$data)
    // => { message: "goodbye", foo: "abc", bar: "def" }
  }
})

29.具名插槽,作用域插槽的使用?

// 比如新建一个<base-layout> 组件
<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>
// 使用插槽
<base-layout>
  <template v-slot:header> // 或者 <template #header>
    <h1>Here might be a page title</h1>
  </template>
​
  <template v-slot:default>
    <p>A paragraph for the main content.</p>
    <p>And another one.</p>
  </template>
​
  <template v-slot:footer>
    <p>Here's some contact info</p>
  </template>
</base-layout>
​

// 作用域插槽,比如新建一个<current-user>组件
<span>
  <slot v-bind:user="user">
    {{ user.lastName }}
  </slot>
</span>
// 使用插槽
<current-user>
  <template v-slot:default="slotProps">
    {{ slotProps.user.firstName }}
  </template>
</current-user>
// 或者缩写
<current-user v-slot="slotProps">
  {{ slotProps.user.firstName }}
</current-user>

30.虚拟dom中key的作用?

key是虚拟DOM对象的标识,当状态中的数据发生变化时,Vue会根据【新数据】生成【新的虚拟

DOM】,随后Vue进行【新虚拟DOM】的差异比较,比较规则如下:

旧虚拟DOM中找到了与新虚拟DOM相同的Key:若虚拟DOM中的内容没有发生变化,直接使用之

前的真实DOM,若虚拟DOM中内容发生了变化,则生成新的真实DOM,随后替换掉页面中之前的

真实DOM

旧虚拟DOM中未找到与新虚拟DOM相同的key:则创建新的虚拟DOM,随后渲染到页面

31.用Index作为key,可能会引发的问题?

1)若对数据内容进行逆序的添加删除等破坏顺序的操作:会产生没有必要的真实DOM的更新,界

面效果没有问题,但是效率低

2)如果界面中含有输入类的DOM,会产生错误的DOM更新,界面有问题

32.vue生命周期的作用是什么?

Vue生命周期中有多个事件钩子,让我们在控制整个Vue实例过程时更容易形成好的逻辑。

33.第一次页面加载会触发那几个钩子?

第一次页面加载时会触发 beforeCreate, created, beforeMount, mounted 这几个钩子

34.Vue中hash模式和history模式的区别?

hash模式(默认)

工作原理:监听网页的hash值得变化===》onhashchange事件,获取location.hash

使用URL的hash来模拟一个完整的URL,于是URL改变时,页面不会重新加载,会给用户好像跳

转了网页一样的感觉,但是实际上没有跳转,主要用在单页面应用(SPA)

history 模式

工作原理:主要是利用history.pushState()API来改变URL,而不刷新页面

其实一共有五种模式可以实现改变URL,而不刷新页面,需要后台配置支持,如果输入一个并不存

在的url,需要后端配置做“兜底配置”,而不是粗暴的返回404,而是返回首页

35.为什么使用vue?

有点:轻量级的框架,双向数据绑定,组件化开发,单页面路由,学习成本低,虚拟DOM,渐进

式框架,数据和结构的分离,运行速度快,插件化

缺点:不支持IE8以下,社区没有angular和react丰富,缺乏高阶教程和文档,单页面应用不利于

seo优化,首次加载耗时较多

转载请说明出处内容投诉
CSS教程_站长资源网 » 2023年Vue2前端面试题(全面)

发表评论

欢迎 访客 发表评论

一个令你着迷的主题!

查看演示 官网购买