🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
## vue 的实例生命周期***** ![](https://img.kancloud.cn/53/87/53878c86c1f564961c1a3f9b342e1ebf_1200x2800.png) ## 请问 v-if 和 v-show 有什么区别***** 参考答案: v-show 指令是通过修改元素的 display 的 CSS 属性让其显示或者隐藏 v-if 指令是直接销毁和重建 DOM 达到让元素显示和隐藏的效果 ## 为什么避免 v-if 和 v-for 用在一起***** 参考答案: vue2.x 中v-for优先级高于v-if,vue3.x 相反。所以2.x 版本中在一个元素上同时使用 v-if 和 v-for 时,v-for 会优先作用,造成性能浪费;3.x 版本中 v-if 总是优先于 v-for 生效,导致v-if访问不了v-for中的变量。 v3 start V3 版本查看官方文档的处理方式 https://cn.vuejs.org/guide/essentials/list.html#v-for-with-v-if ![](https://img.kancloud.cn/e0/bd/e0bd9879509bd55fd6d9deec77af656d_1133x1042.png) v3 end 解析: 一般我们在两种常见的情况下会倾向于这样做: * 为了过滤一个列表中的项目 (比如 v-for="user in users" v-if="user.isActive")。在这种情形下,请将 users 替换为一个计算属性 (比如 activeUsers),让其返回过滤后的列表。 * 为了避免渲染本应该被隐藏的列表 (比如 v-for="user in users" v-if="shouldShowUsers")。这种情形下,请将 v-if 移动至容器元素上 (比如 ul、ol)。 当 Vue 处理指令时,v-for 比 v-if 具有更高的优先级,所以这个模板: ~~~html <ul> <li v-for="user in users" v-if="user.isActive" :key="user.id" > {{ user.name }} </li> </ul> ~~~ 将会经过如下运算: ~~~js this.users.map(function (user) { if (user.isActive) { return user.name } }) ~~~ 因此哪怕我们只渲染出一小部分用户的元素,也得在每次重渲染的时候遍历整个列表,不论活跃用户是否发生了变化。 通过将其更换为在如下的一个计算属性上遍历: ~~~js computed: { activeUsers: function () { return this.users.filter(function (user) { return user.isActive }) } } ~~~ ~~~html <ul> <li v-for="user in activeUsers" :key="user.id" > {{ user.name }} </li> </ul> ~~~ 我们将会获得如下好处: * 过滤后的列表只会在 users 数组发生相关变化时才被重新运算,过滤更高效。 * 使用 v-for="user in activeUsers" 之后,我们在渲染的时候只遍历活跃用户,渲染更高效。 * 解耦渲染层的逻辑,可维护性 (对逻辑的更改和扩展) 更强。 为了获得同样的好处,我们也可以把: ~~~html <ul> <li v-for="user in users" v-if="shouldShowUsers" :key="user.id" > {{ user.name }} </li> </ul> ~~~ 更新为: ~~~html <ul v-if="shouldShowUsers"> <li v-for="user in users" :key="user.id" > {{ user.name }} </li> </ul> ~~~ 通过将 v-if 移动到容器元素,我们不会再对列表中的每个用户检查 shouldShowUsers。取而代之的是,我们只检查它一次,且不会在 shouldShowUsers 为否的时候运算 v-for。 反例: ~~~html <ul> <li v-for="user in users" v-if="user.isActive" :key="user.id" > {{ user.name }} </li> </ul> <ul> <li v-for="user in users" v-if="shouldShowUsers" :key="user.id" > {{ user.name }} </li> </ul> ~~~ 好例子 ~~~html <ul> <li v-for="user in activeUsers" :key="user.id" > {{ user.name }} </li> </ul> <ul v-if="shouldShowUsers"> <li v-for="user in users" :key="user.id" > {{ user.name }} </li> </ul> ~~~ ## vue 中 key 值的作用***** 参考答案: 需要使用 key 来给每个节点做一个唯一标识,Diff 算法就可以正确的识别此节点,找到正确的位置区插入新的节点 所以一句话,key 的作用主要是为了高效的更新虚拟 DOM ## $nextTick 的使用*** 参考答案: 1、什么是 Vue.nextTick()? 定义:在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。 所以就衍生出了这个获取更新后的 DOM 的 Vue 方法。所以放在 Vue.nextTick()回调函数中的执行的应该是会对 DOM 进行操作的 js 代码; 理解:nextTick(),是将回调函数延迟在下一次 dom 更新数据后调用,简单的理解是:当数据更新了,在 dom 中渲染后,自动执行该函数, ~~~ <template> <div class="hello"> <div> <button id="firstBtn" @click="testClick()" ref="aa">{{testMsg}}</button> </div> </div> </template> <script> export default { name: 'HelloWorld', data () { return { testMsg:"原始值", } }, methods:{ testClick:function(){ let that=this; that.testMsg="修改后的值"; console.log(that.$refs.aa.innerText); //that.$refs.aa获取指定DOM,输出:原始值 } } } </script> ~~~ 使用 this. $nextTick() ~~~js methods: { testClick: function() { let that = this; that.testMsg = "修改后的值"; that.$nextTick(function() { console.log(that.$refs.aa.innerText); //输出:修改后的值 }); } } ~~~ 注意:Vue 实现响应式并不是数据发生变化之后 DOM 立即变化,而是按一定的策略进行 DOM 的更新。$nextTick 是在下次 DOM 更新循环结束之后执行延迟回调,在修改数据之后使用 $nextTick,则可以在回调中获取更新后的 DOM, 2、什么时候需要用的 Vue.nextTick()?? 1、Vue 生命周期的 created()钩子函数进行的 DOM 操作一定要放在 Vue.nextTick()的回调函数中,原因是在 created()钩子函数执行的时候 DOM 其实并未进行任何渲染,而此时进行 DOM 操作无异于徒劳,所以此处一定要将 DOM 操作的 js 代码放进 Vue.nextTick()的回调函数中。与之对应的就是 mounted 钩子函数,因为该钩子函数执行时所有的 DOM 挂载已完成。 ~~~js created() { let that = this; that.$nextTick(function() { //不使用this.$nextTick()方法会报错 that.$refs.aa.innerHTML = "created中更改了按钮内容"; //写入到DOM元素 }); } ~~~ 2、当项目中你想在改变 DOM 元素的数据后基于新的 dom 做点什么,对新 DOM 一系列的 js 操作都需要放进 Vue.nextTick()的回调函数中;通俗的理解是:更改数据后当你想立即使用 js 操作新的视图的时候需要使用它 ~~~ <template> <div class="hello"> <h3 id="h">{{testMsg}}</h3> </div> </template> <script> export default { name: 'HelloWorld', data () { return { testMsg:"原始值", } }, methods:{ changeTxt:function(){ let that=this; that.testMsg="修改后的文本值"; //vue数据改变,改变dom结构 let domTxt=document.getElementById('h').innerText; //后续js对dom的操作 console.log(domTxt); //输出可以看到vue数据修改后DOM并没有立即更新,后续的dom都不是最新的 if(domTxt==="原始值"){ console.log("文本data被修改后dom内容没立即更新"); }else { console.log("文本data被修改后dom内容被马上更新了"); } }, } } </script> ~~~ 正确的用法是:vue 改变 dom 元素结构后使用 vue.$nextTick()方法来实现 dom 数据更新后延迟执行后续代码 ~~~js changeTxt: function() { let that = this; that.testMsg = "修改后的文本值"; //修改dom结构 that.$nextTick(function() { //使用vue.$nextTick()方法可以dom数据更新后延迟执行 let domTxt = document.getElementById('h').innerText; console.log(domTxt); //输出可以看到vue数据修改后并没有DOM没有立即更新, if (domTxt === "原始值") { console.log("文本data被修改后dom内容没立即更新"); } else { console.log("文本data被修改后dom内容被马上更新了"); } }); } ~~~ 3、在使用某个第三方插件时 ,希望在 vue 生成的某些 dom 动态发生变化时重新应用该插件,也会用到该方法,这时候就需要在 $nextTick 的回调函数中执行重新应用插件的方法。 Vue.nextTick(callback) 使用原理: 原因是,Vue 是异步执行 dom 更新的,一旦观察到数据变化,Vue 就会开启一个队列,然后把在同一个事件循环 (event loop) 当中观察到数据变化的 watcher 推送进这个队列。如果这个 watcher 被触发多次,只会被推送到队列一次。这种缓冲行为可以有效的去掉重复数据造成的不必要的计算和 DOm 操作。而在下一个事件循环时,Vue 会清空队列,并进行必要的 DOM 更新。 当你设置 vm.someData = 'new value',DOM 并不会马上更新,而是在异步队列被清除,也就是下一个事件循环开始时执行更新时才会进行必要的 DOM 更新。如果此时你想要根据更新的 DOM 状态去做某些事情,就会出现问题。。为了在数据变化之后等待 Vue 完成更新 DOM ,可以在数据变化之后立即使用 Vue.nextTick(callback) 。这样回调函数在 DOM 更新完成后就会调用。 ## vue 的双向数据绑定的原理**** 参考答案: Vue2.0 实现双向数据绑定的原理就是利用了 Object.defineProperty() 这个方法重新定义了对象获取属性值(get)和设置属性值(set)的操作来实现的。 Vue3.0 用原生 Proxy 替换 Object.defineProperty。 ## vue slot是做什么的?*** 参考答案:主要是让组件的可扩展性更强,简单点说就是,能够在组件内写其他内容 解析: ### 插槽 在 2.6.0 中,我们为具名插槽和作用域插槽引入了一个新的统一的语法 (即 v-slot 指令)。它取代了 slot 和 slot-scope 这两个目前已被废弃但未被移除且仍在文档中的 attribute。 #### 插槽内容 Vue 实现了一套内容分发的 API,这套 API 的设计灵感源自 Web Components 规范草案,将 元素作为承载分发内容的出口。 它允许你像这样合成组件: ~~~html <navigation-link url="/profile"> Your Profile </navigation-link> ~~~ 然后你在 的模板中可能会写为: ~~~js <a v-bind:href="url" class="nav-link" > <slot></slot> </a> ~~~ #### 编译作用域 ## vue 组件中 data 为什么必须是函数*** 参考答案: 在 new Vue() 中,data 是可以作为一个对象进行操作的,然而在 component 中,data 只能以函数的形式存在,不能直接将对象赋值给它,这并非是 Vue 自身如此设计,而是跟 JavaScript 特性相关,我们来回顾下 JavaScript 的原型链 ~~~js var Component = function() {}; Component.prototype.data = { message: "Love" }; var component1 = new Component(), component2 = new Component(); component1.data.message = "Peace"; console.log(component2.data.message); // Peace ~~~ 以上**两个实例都引用同一个原型对象,当其中一个实例属性改变时,另一个实例属性也随之改变,只有当两个实例拥有自己的作用域时,才不会互相干扰**!!!!!这句是重点!!!!! ~~~js var Component = function() { this.data = this.data(); }; Component.prototype.data = function() { return { message: "Love" }; }; var component1 = new Component(), component2 = new Component(); component1.data.message = "Peace"; console.log(component2.data.message); // Love ~~~ ## 什么是 vue 生命周期和生命周期钩子函数?***** 参考答案: vue 的生命周期就是 vue 实例从创建到销毁的过程 解析: ![](https://img.kancloud.cn/e7/fd/e7fd4f1d7d436f5f2b397fcd46be65d3_598x641.png) ![](https://img.kancloud.cn/ee/c4/eec46d66531752c9c3c43a4e76ff71a3_630x439.png) ## vue 更新数组时触发视图更新的方法**** 参考答案: 1.vue.set 可以设置对象或数组的值,通过 key 或数组索引,可以触发视图更新 ~~~ 数组修改 Vue.set(array, indexOfItem, newValue) this.array.$set(indexOfItem, newValue) 对象修改 Vue.set(obj, keyOfItem, newValue) this.obj.$set(keyOfItem, newValue) ~~~ 2.vue.delete 删除对象或数组中元素,通过 key 或数组索引,可以触发视图更新 ~~~ 数组修改 Vue.delete(array, indexOfItem) this.array.$delete(indexOfItem) 对象修改 Vue.delete(obj, keyOfItem) this.obj.$delete(keyOfItem) ~~~ 3. 数组对象直接修改属性,可以触发视图更新 ~~~ this.array[0].show = true; this.array.forEach(function(item){ item.show = true; }); ~~~ 4. splice 方法修改数组,可以触发视图更新 ~~~ this.array.splice(indexOfItem, 1, newElement) ~~~ 5. 数组整体修改,可以触发视图更新 ~~~ var tempArray = this.array; tempArray[0].show = true; this.array = tempArray; ~~~ 6. 用 Object. assign 或 lodash. assign 可以为对象添加响应式属性,可以触发视图更新 ~~~ //Object.assign的单层的覆盖前面的属性,不会递归的合并属性 this.obj = Object.assign({},this.obj,{a:1, b:2}) //assign与Object.assign一样 this.obj = _.assign({},this.obj,{a:1, b:2}) //merge会递归的合并属性 this.obj = _.merge({},this.obj,{a:1, b:2}) ~~~ 7.vue 提供了如下的数组的变异方法,可以触发视图更新 ~~~ push() pop() shift() unshift() splice() sort() reverse() ~~~ ## 什么是 vue 的计算属性?**** 参考答案:先来看一下计算属性的定义: 当其依赖的属性的值发生变化的时,计算属性会重新计算。反之则使用缓存中的属性值。 计算属性和vue中的其它数据一样,都是响应式的,只不过它必须依赖某一个数据实现,并且只有它依赖的数据的值改变了,它才会更新。 ## $route和$router的区别*** 参考答案:$route 是路由信息对象,包括path,params,hash,query,fullPath,matched,name 等路由信息参数。 而 $router 是路由实例对象,包括了路由的跳转方法,钩子函数等 ## watch的作用是什么**** 参考答案:watch 主要作用是监听某个数据值的变化。和计算属性相比除了没有缓存,作用是一样的。 借助 watch 还可以做一些特别的事情,例如监听页面路由,当页面跳转时,我们可以做相应的权限控制,拒绝没有权限的用户访问页面。 ## 实现通信方式***** 参考答案: ~~~ 方式1: props 1) 通过一般属性实现父向子通信 2) 通过函数属性实现子向父通信 3) 缺点: 隔代组件和兄弟组件间通信比较麻烦 方式2: vue自定义事件 1) vue内置实现, 可以代替函数类型的props a. 绑定监听: <MyComp @eventName="callback" b. 触发(分发)事件: this.$emit("eventName", data) 2) 缺点: 只适合于子向父通信 方式3: 消息订阅与发布 1) 需要引入消息订阅与发布的实现库, 如: pubsub-js a. 订阅消息: PubSub.subscribe('msg', (msg, data)=>{}) b. 发布消息: PubSub.publish(‘msg’, data) 2) 优点: 此方式可用于任意关系组件间通信 方式4: vuex 1) 是什么: vuex是vue官方提供的集中式管理vue多组件共享状态数据的vue插件 2) 优点: 对组件间关系没有限制, 且相比于pubsub库管理更集中, 更方便 方式5: slot 1) 是什么: 专门用来实现父向子传递带数据的标签 a. 子组件 b. 父组件 2) 注意: 通信的标签模板是在父组件中解析好后再传递给子组件的 ~~~ ## axios有哪些常用方法?**** 参考答案: ~~~ 一、axios.get(url[, config]) //get请求用于列表和信息查询 二、axios.delete(url[, config]) //删除 三、axios.post(url[, data[, config]]) //post请求用于信息的添加 四、axios.put(url[, data[, config]]) //更新操作 ~~~ ## computed 和 watch 的差异?**** 参考答案: ~~~ (1)computed 是计算一个新的属性,并将该属性挂载到 Vue 实例上,而 watch 是监听已经存在且已挂载到 Vue 实例上的数据,所以用 watch 同样可以监听 computed 计算属性的变化。 (2)computed 本质是一个惰性求值的观察者,具有缓存性,只有当依赖变化后,第一次访问 computed 属性,才会计算新的值。而 watch 则是当数据发生变化便会调用执行函数。 (3)从使用场景上说,computed 适用一个数据被多个数据影响,而 watch 适用一个数据影响多个数据。 ~~~ 详细资料可以参考:[《做面试的不倒翁:浅谈 Vue 中 computed 实现原理》](https://juejin.im/post/5b98c4da6fb9a05d353c5fd7)[《深入理解 Vue 的 watch 实现原理及其实现方式》](https://juejin.im/post/5af908ea5188254265399009) ## vue 常用的修饰符?**** 参考答案: ~~~ .prevent: 提交事件不再重载页面;.stop: 阻止单击事件冒泡;.self: 当事件发生在该元素本身而不是子元素的时候会触发; ~~~ ## vue 中 key 值的作用?**** 参考答案: ~~~ vue 中 key 值的作用可以分为两种情况来考虑。 第一种情况是 v-if 中使用 key。由于 Vue 会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染。因此当我们使用 v-if 来实现元素切换的时候,如果切换前后含有相同类型的元素,那么这个元素就会被复用。如果是相同的 input 元素,那么切换前后用户的输入不会被清除掉,这样是不符合需求的。因此我们可以通过使用 key 来唯一的标识一个元素,这个情况下,使用 key 的元素不会被复用。这个时候 key 的作用是用来标识一个独立的元素。 第二种情况是 v-for 中使用 key。用 v-for 更新已渲染过的元素列表时,它默认使用“就地复用”的策略。如果数据项的顺序发生了改变,Vue 不会移动 DOM 元素来匹配数据项的顺序,而是简单复用此处的每个元素。因此通过为每个列表项提供一个 key 值,来以便 Vue 跟踪元素的身份,从而高效的实现复用。这个时候 key 的作用是为了高效的更新渲染虚拟 DOM。 ~~~ 详细资料可以参考:[《Vue 面试中,经常会被问到的面试题 Vue 知识点整理》](https://segmentfault.com/a/1190000016344599)[《Vue2.0 v-for 中 :key 到底有什么用?》](https://www.zhihu.com/question/61064119)[《vue 中 key 的作用》](https://www.cnblogs.com/RainyBear/p/8563101.html) ## computed 和 watch 区别?**** 参考答案: ~~~ computed 是计算属性,依赖其他属性计算值,并且 computed 的值有缓存,只有当计算值变化才会返回内容。 watch 监听到值的变化就会执行回调,在回调中可以进行一些逻辑操作。 ~~~ ## keep-alive 组件有什么作用?**** 参考答案: ~~~ 如果你需要在组件切换的时候,保存一些组件的状态防止多次渲染,就可以使用 keep-alive 组件包裹需要保存的组件。 ~~~ ## vue 双向数据绑定原理?**** 参考答案: ~~~ vue 通过使用双向数据绑定,来实现了 View 和 Model 的同步更新。vue 的双向数据绑定主要是通过使用数据劫持和发布订阅者模式来实现的。 首先我们通过 Object.defineProperty() 方法来对 Model 数据各个属性添加访问器属性,以此来实现数据的劫持,因此当 Model 中的数据发生变化的时候,我们可以通过配置的 setter 和 getter 方法来实现对 View 层数据更新的通知。 数据在 html 模板中一共有两种绑定情况,一种是使用 v-model 来对 value 值进行绑定,一种是作为文本绑定,在对模板引擎进行解析的过程中。 如果遇到元素节点,并且属性值包含 v-model 的话,我们就从 Model 中去获取 v-model 所对应的属性的值,并赋值给元素的 value 值。然后给这个元素设置一个监听事件,当 View 中元素的数据发生变化的时候触发该事件,通知 Model 中的对应的属性的值进行更新。 如果遇到了绑定的文本节点,我们使用 Model 中对应的属性的值来替换这个文本。对于文本节点的更新,我们使用了发布订阅者模式,属性作为一个主题,我们为这个节点设置一个订阅者对象,将这个订阅者对象加入这个属性主题的订阅者列表中。当 Model 层数据发生改变的时候,Model 作为发布者向主题发出通知,主题收到通知再向它的所有订阅者推送,订阅者收到通知后更改自己的数 据。 ~~~ 详细资料可以参考:[《Vue.js 双向绑定的实现原理》](http://www.cnblogs.com/kidney/p/6052935.html?utm_source=gold_browser_extension) ## vue 中的性能优化***** 参考答案: 1)编码优化 * 尽量减少data中的数据,data中的数据都会增加getter和setter,会收集对应的watcher * v-if和v-for不能连用 * 如果需要使用v-for给每项元素绑定事件时使用事件代理 * SPA 页面采用keep-alive缓存组件 * 在更多的情况下,使用v-if替代v-show * key保证唯一 * 使用路由懒加载、异步组件 * 防抖、节流 * 第三方模块按需导入 * 长列表滚动到可视区域动态加载 * 图片懒加载 2)用户体验优化 * 骨架屏 * PWA(渐进式WEB应用) * 还可以使用缓存(客户端缓存、服务端缓存)优化、服务端开启gzip压缩等。 3)SEO优化 * 预渲染 * 服务端渲染SSR 4)打包优化 * 压缩代码; * Tree Shaking/Scope Hoisting; * 使用cdn加载第三方模块; * 多线程打包happypack; * splitChunks抽离公共文件; * sourceMap优化; 说明:优化是个大工程,会涉及很多方面