Vue.js面试题(二)https://developer.aliyun.com/article/1399412?spm=a2c6h.13148508.setting.14.59904f0eEJICdO
41.★★★ Vue深层次嵌套传值方法
利用和attrs和attrs 和 listeners
42.★★★ Vue组件如何引入使用
- 定义组件并抛出
- import引入,并在component里面定义
- 使用组件(注意首字母大写)
43.★★★★ Vue路由实现的底层原理
在Vue中利用数据劫持defineProperty在原型prototype上初始化了一些getter,
分别是router代表当前Router的实例 、 router代表当前Router的实例、router代表当前Router的实例、route 代表当前Router的信息。在install中也全局注册了router-view,router-link,其中的Vue.util.defineReactive, 这是Vue里面观察者劫持数据的方法,
劫持_route,当_route触发setter方法的时候,则会通知到依赖的组件。
接下来在init中,会挂载判断是路由的模式,是history或者是hash,点击行为按钮,
调用hashchange或者popstate的同时更_route,_route的更新会触发route-view的重新渲染。
44.★★★★ 如何封装一个通用组件
通用组件的封装就是对可复用组件的解耦和样式复用,为了解耦一般数据都是通过父组件传递过来,
在子组件中进行数据处理,对于一些较为复杂的数据可能还需要做数据验证,
为了避免高耦合,逻辑最好放在父组件中,通过自定义事件将数据回传,子组件只是一个承载体,
这样既降低耦合,保证子组件中数据和逻辑不会混乱。
如果同一组件需要适应不同需求时,我们需要配合slot来使用,
可以通过具名插槽灵活地解决了不同场景同一组件不同配置的问题。
45.★★ Vue 生命周期通常使用哪些
常用的生命周期有,beforeCreate,created,beforeMount,mounted,beforeUpdate,updated,beforeDestroy,destroyed
46.★★ Vue 深层次的组件怎么和父组件通讯
//使用$attrs和$listeners Vue.component('C', { template: ` <div> <p>我是C组件</p> <input type='text' v-model='$attrs.msgc' @input='$emit("getC", $attrs.msgc)' /> </div> ` }) Vue.component('B', { /** 给C组件绑定$attrs属性和$listeners事件,C组件可以直接获取到A组件中传递下来的props(除了B组件中props声明的) */ template: ` <div> <p>我是B组件</p> <input type='text' v-model='mymsg1' @input="$emit('getChild', mymsg1)" /> <C v-bind='$attrs' v-on='$listeners'/> </div> `, props: ['msg1'], data () { return { mymsg1: this.msg1 } } }) Vue.component('A', { template: ` <div id='app'> <p>我是A组件</p> <B :msg1='msg1' :msgc='msgc' @getChild='getChild' @getC='getC' /> </div> `, data () { return { msg1: 'A', msgc: 'hello c!' } }, methods: { getChild (val) { console.log( val ) }, getC (val) { console.log( val ) } } }) const app = new Vue({ el: '#app', template: ` <A /> ` })
47.★★★★★ Vue 响应式原理
1.观察者observer:首先通过观察者对data中的属性使用object.defineproperty劫持数据的getter和setter,通知订阅者,触发他的update方法,对视图进行更新
2.Compile:用来解析模板指令,并替换模板数据,初始化视图,初始化相应的订阅器
3.订阅者Watcher:订阅者接到通知后,调用update方法更新对应的视图
4.订阅器Dep:订阅者可能有多个,因此需要订阅器Dep来专门接收这些订阅者,并统一管理
但在vue3中抛弃了object.defineproperty方法,因为
- Object.defineproperty无法监测对象属性的添加和删除、数组索引和长度的变更,因此vue重写了数组的push/pop/shift/unshift/splice/sort/reverse方法
- Object.defineProperty只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历,这样很消耗性能
vue3中实现数据双向绑定的原理是数据代理,使用proxy实现。Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。
48.★★★★ Vue proxy的原理
主要通过Proxy对对象进行绑定监听处理,通过new Map对对象的属性操作进行处理,将要执行的函数匹配到存到对应的prop上面,通过每次的访问触发get方法,进行存方法的操作,通过修改触发set的方法,此时执行回调监听的函数,这样达到修改数据和视图的
49. ★★★ Vue $forceUpdate的原理
作用:
迫使 Vue 实例重新渲染。注意它仅仅影响实例本身和插入插槽内容的子组件,而不是所有子组件。
内部原理
Vue.prototype.$forceUpdate = function () { const vm: Component = this if (vm._watcher) { vm._watcher.update() } }
50.★★★ v-for key
- key是为Vue中的vnode标记的唯一id,通过这个key,我们的diff操作可以更准确、更快速
- diff算法的过程中,先会进行新旧节点的首尾交叉对比,当无法匹配的时候会用新节点的key与旧节点进行比对,然后超出差异.
diff程可以概括为:oldCh和newCh各有两个头尾的变量StartIdx和EndIdx,它们的2个变量相互比较,一共有4种比较方式。如果4种比较都没匹配,如果设置了key,就会用key进行比较,在比较的过程中,变量会往中间靠,一旦StartIdx>EndIdx表明oldCh和newCh至少有一个已经遍历完了,就会结束比较,这四种比较方式就是首、尾、旧尾新头、旧头新尾.
准确: 如果不加key,那么vue会选择复用节点(Vue的就地更新策略),导致之前节点的状态被保留下来,会产生一系列的bug. 快速: key的唯一性可以被Map数据结构充分利用,相比于遍历查找的时间复杂度O(n),Map的时间复杂度仅仅为O(1)
51.★★★★ defineProperty在数据劫持后是如何通知数据的更新和视图的更新的
vue的双向绑定是由数据劫持结合发布者-订阅者模式实现的,那么什么是数据劫持?vue是如何进行数据劫持的?说白了就是通过Object.defineProperty()来劫持对象属性的setter和getter操作,在数据变动时做你想要做的事情
我们已经知道实现数据的双向绑定,首先要对数据进行劫持监听,所以我们需要设置一个监听器Observer,用来监听所有属性。如果属性发生变化了,就需要告诉订阅者Watcher看是否需要更新。因为订阅者是有很多个,所以我们需要有一个消息订阅器Dep来专门收集这些订阅者,然后在监听器Observer和订阅者Watcher之间进行统一管理的。接着,我们还需要有一个指令解析器Compile,对每个节点元素进行扫描和解析,将相关指令(如v-model,v-on)对应初始化成一个订阅者Watcher,并替换模板数据或者绑定相应的函数,此时当订阅者Watcher接收到相应属性的变化,就会执行对应的更新函数,从而更新视图
因此接下去我们执行以下3个步骤,实现数据的双向绑定:
1.实现一个监听器Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者。
2.实现一个订阅者Watcher,每一个Watcher都绑定一个更新函数,watcher可以收到属性的变化通知并执行相应的函数,从而更新视图。
3.实现一个解析器Compile,可以扫描和解析每个节点的相关指令(v-model,v-on等指令),如果节点存在v-model,v-on等指令,则解析器Compile初始化这类节点的模板数据,使之可以显示在视图上,然后初始化相应的订阅者(Watcher)。
52.★★★★ axios谁封装的,怎么封装的
// 使用axios用于对数据的请求 import axios from 'axios' // 创建axios实例 const instance = axios.create({ baseURL: baseURL + version, timeout: 5000 }) // 创建请求的拦截器 instance.interceptors.request.use(config => { config.headers['Authorization'] = localStorage.getItem('token') return config }, error => { return Promise.reject(error) }) // 创建响应的拦截器 instance.interceptors.response.use(response => { let res = null // 对相应的数据进行过滤 if (response.status === 200) { if (response.data && response.data.err === 0) { res = response.data.data } else if (response.data.err === -1) { return alert('token无效') } } else { return alert('请求失败') } return res }, error => { return Promise.reject(error) }) export default instance
53.★★★ 为什么要设置key值,可以用index吗?为什么不能?
vue中列表循环需加:key=“唯一标识” 唯一标识可以是item里面id index等,因为vue组件高度复用增加Key可以标识组件的唯一性,为了更好地区别各个组件 key的作用主要是为了高效的更新虚拟DOM
54.★★★ 怎么修改Vuex中的状态?Vuex中有哪些方法?
- 通过this.$store.state.属性 的方法来访问状态
- 通过this.$store.commit(‘mutation中的方法’) 来修改状态
55.★★★ vue-router路由传参的方式
query // 方法一 <template> <router-link :to="{ path: 'blogDetail', query: { id: item.id, views: item.views } }" tag="h2" > </router-link> </template> // 方法二 this.$router.push({ path: 'blogDetail', query: { id: item.id, views: item.views } })
params <template> <router-link :to="{ name: 'blogDetail', params: { id: item.id, views: item.views } }" tag="h2" > </router-link> </template> this.$router.push({ name: 'blogDetail', params: { id: item.id, views: item.views } })
56.★★★ hash history区别
57.★★★ 用过beforeEach吗?
每次通过vue-router进行页面跳转,都会触发beforeEach这个钩子函数,这个回调函数共有三个参数,to,from,next这三个参数,to表示我要跳转的目标路由对应的参数,from表示来自那个路由,就是操作路由跳转之前的,即将离开的路由对应的参数,next是一个回调函数,一定要调用next方法来resolve这个钩子函数;
58.★★★ Vue中的单项数据流
- 单向数据流指只能从一个方向来修改状态。
- 数据从父级组件传递给子组件,只能单向绑定。
- 子组件内部不能直接修改从父级传递过来的数据。
59.★★★ Vue组件中的Data为什么是函数,根组件却是对象呢?
如果data是一个函数的话,这样每复用一次组件,就会返回一份新的data,类似于给每个组件实例创建一个私有的数据空间,让各个组件实例维护各自的数据。而单纯的写成对象形式,就使得所有组件实例共用了一份data,就会造成一个变了全都会变的结果。
所以说vue组件的data必须是函数。这都是因为js的特性带来的,跟vue本身设计无关。
60.★★★ 你做过哪些Vue的性能优化?
1、首屏加载优化
2、路由懒加载
{ path: '/', name: 'home', component: () => import('./views/home/index.vue'), meta: { isShowHead: true } }
3、开启服务器 Gzip
开启 Gzip 就是一种压缩技术,需要前端提供压缩包,然后在服务器开启压缩,文件在服务器压缩后传给浏览器,浏览器解压后进行再进行解析。首先安装 webpack 提供的compression-webpack-plugin进行压缩,然后在 vue.config.js:
const CompressionWebpackPlugin = require('compression-webpack-plugin') const productionGzipExtensions = ['js', 'css']......plugins: [ new CompressionWebpackPlugin( { algorithm: 'gzip', test: new RegExp('\\.(' + productionGzipExtensions.join('|') + ')$'), threshold: 10240, minRatio: 0.8 } )]....
4、启动 CDN 加速
我们继续采用 cdn 的方式来引入一些第三方资源,就可以缓解我们服务器的压力,原理是将我们的压力分给其他服务器点。
5、代码层面优化
- computed 和 watch 区分使用场景
computed: 是计算属性,依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值。当我们需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时,都要重新计算;
watch:类似于某些数据的监听回调 ,每当监听的数据变化时都会执行回调进行后续操作;当我们需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许我们执行异步操作 ( 访问一个 API ),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。
- v-if 和 v-show 区分使用场景 v-if 适用于在运行时很少改变条件,不需要频繁切换条件的场景;v-show 则适用于需要非常频繁切换条件的场景。这里要说的优化点在于减少页面中 dom 总数,我比较倾向于使用 v-if,因为减少了 dom 数量。
- v-for 遍历必须为 item 添加 key,且避免同时使用 v-if v-for 遍历必须为 item 添加 key,循环调用子组件时添加 key,key 可以唯一标识一个循环个体,可以使用例如 item.id 作为 key 避免同时使用 v-if,v-for 比 v-if 优先级高,如果每一次都需要遍历整个数组,将会影响速度。
6、Webpack 对图片进行压缩
7、避免内存泄漏
8、减少 ES6 转为 ES5 的冗余代码
Vue.js面试题(四)https://developer.aliyun.com/article/1399447