概念类
vue.js是什么?它的特点(优点是什么)?
vue是一个渐进式JavaScript框架
- 特点:
- MVVM 模式;代码简洁体积小,运行效率高,适合移动PC端开发;可以轻松引入 Vue 插件或其他的第三方库进行开发;
- 组件化采用组件化模式,提高代码复用率,且让代码更好维护;
- 声明式编码,让编码人员无需直接操作DOM,提高开发效率;
- 使用虚拟DOM+优秀的Diff算法,尽量复用DOM节点;
Vue和JQuery的区别在哪,为什么JQuery会被放弃选用Vue
- jQuery是直接操作DOM,Vue不直接操作DOM,Vue的数据与视图是分开的,Vue只需要操作数据即可;
- 在操作DOM频繁的场景里,jQuery的操作DOM行为是频繁的,而Vue利用虚拟DOM的技术,大大提高了更新DOM时的性能;
- Vue中不倡导直接操作DOM,开发者只需要把大部分精力放在数据层面上;
- Vue集成的一些库,大大提高开发效率,比如Vuex,Router等;
阐述一下你所理解的MVVM响应式原理
Model-View-ViewModel
- M:Model 表示数据模型层,对应data中的数据;
- V:view 表示视图层,view视图模版;
- VM:ViewModel 表示vue实例对象,是 View 和 Model 层的桥梁,数据绑定到 viewModel 层并自动渲染到页面中,视图变化通知 viewModel 层更新数据;
MVVM和MVC有什么区别
- MVVM即Model-View-ViewModel的简写。即模型-视图-视图模型。模型(Model)指的是后端传递的数据。视图(View)指的是所看到的页面。视图模型(ViewModel)是mvvm模式的核心,它是连接view和model的桥梁。它有两个方向:一是将模型(Model)转化成视图(View),即将后端传递的数据转化成所看到的页面。实现的方式是:数据绑定。二是将视图(View)转化成模型(Model),即将所看到的页面转化成后端的数据。实现的方式是:DOM 事件监听。这两个方向都实现的,我们称之为数据的双向绑定。
- MVC是Model-View- Controller的简写。即模型-视图-控制器。M和V指的意思和MVVM中的M和V意思一样。C即Controller指的是页面业务逻辑。使用MVC的目的就是将M和V的代码分离。MVC是单向通信。也就是View跟Model,必须通过Controller来承上启下。MVC和MVVM的区别并不是VM完全取代了C,只是在MVC的基础上增加了一层VM,只不过是弱化了C的概念,ViewModel存在目的在于抽离Controller中展示的业务逻辑,而不是替代Controller,其它视图操作业务等还是应该放在Controller中实现。也就是说MVVM实现的是业务逻辑组件的重用,使开发更高效,结构更清晰,增加代码的复用性。
虚拟DOM的优缺点
优点:
- 保证性能下限: 框架的虚拟 DOM 需要适配任何上层 API 可能产生的操作,它的一些 DOM 操作的实现必须是普适的,所以它的性能并不是最优的;但是比起粗暴的 DOM 操作性能要好很多,因此框架的虚拟 DOM 至少可以保证在你不需要手动优化的情况下,依然可以提供还不错的性能,即保证性能的下限;
- 无需手动操作 DOM: 我们不再需要手动去操作 DOM,只需要写好 View-Model 的代码逻辑,框架会根据虚拟 DOM 和 数据双向绑定,帮我们以可预期的方式更新视图,极大提高我们的开发效率;
- 跨平台: 虚拟 DOM 本质上是 JavaScript 对象,而 DOM 与平台强相关,相比之下虚拟 DOM 可以进行更方便地跨平台操作,例如服务器渲染、weex 开发等等。 缺点:
- 无法进行极致优化: 虽然虚拟 DOM + 合理的优化,足以应对绝大部分应用的性能需求,但在一些性能要求极高的应用中虚拟 DOM 无法进行针对性的极致优化。
- 首次渲染大量DOM时,由于多了一层虚拟DOM的计算,会比innerHTML插入慢。
vue脚手架目录各文件夹(public+assets文件夹的区别)
- node_modules文件夹:项目依赖文件夹
- public文件夹:一般放置一些静态资源(如网页图标),在webpack打包时,会原封不动的打包到dist文件夹。
- src文件夹:
-
- 1.assets文件夹:一般放置多个组件共用的静态资源,在webpack 打包时,会把此静态资源当作一个模块,打包到JS文件中。
-
-
- components文件夹:一般放置非路由组件。
-
-
-
- app.vue文件:唯一的根组件。
-
-
-
- main.js文件:程序的入口文件,也是整个程序最先执行的文件。
-
- babel.config.js文件:babel配置文件。
- package.json文件:相当于项目的“身份证”,记录了项目的相关信息(如名字、依赖、运行方式等等)。
- package-lock.json文件:跟踪被安装的每个软件包的确切版本,以便产品可以以相同的方式被 100% 复制(即使软件包的维护者更新了软件包)。
- README.md文件:项目的说明性文件
项目中的package.json文件有什么作用,它里面都有哪些内容
package.json 文件其实就是对项目或者模块包的描述,里面包含许多元信息。比如项目名称,项目版本,项目执行入口文件,项目贡献者等等。npm install 命令会根据这个文件下载所有依赖模块。
组件中的data为什么是一个函数?
data之所以是一个函数,是因为一个组件可能会多处调用,而每一次调用就会执行data函数并返回新的数据对象,这样,可以避免多处调用之间的数据污染。因为如果 data 是一个对象的话,这些实例用的是同一个构造函数。为了保证组件的数据独立,要求每个组件都必须通过 data 函数返回一个对象作为组件的状态。
一些常见的vue的内部指令
- v-text:更新元素的textContent
- v-html:更新元素的innerHTML
- v-show:根据表达式的真假值,切换元素的CSS中display属性。
- v-if:根据表达式的值的成立与否来有条件的渲染元素。在切换时元素及它的数据绑定/组件被销毁并重建。
- v-else:前一兄弟元素必须有v-if或v-else-if
- v-else-if:前一兄弟元素必须有v-if或v-else-if
- v-for:列表循环渲染,数组,对象,数字,字符串都可以
- v-on:缩写是@,绑定事件
- v-bind:缩写是:,用于动态绑定各种变量
- v-model:双向绑定表单项的值
- v-slot:缩写是#,插槽名
- v-once:元素和组件只渲染一次
- v-pre:跳过这个元素和它的子元素的编译过程。可以用来显示原始Mustache标签。跳过大量没有指令的节点会加快编译。
- v-cloak:这个指令保持在元素上直到关联实例结束编译。
如何设置绑定样式(动态class,动态style)
class样式 写法:class="xxx" xxx可以是字符串、对象、数组。
- 字符串写法适用于:类名不确定,要动态获取。
- 对象写法适用于:要绑定多个样式,个数不确定,名字也不确定。
- 数组写法适用于:要绑定多个样式,个数确定,名字也确定,但不确定用不用。 style样式
- :style="{fontSize: xxx}"其中xxx是动态值。
- :style="[a,b]"其中a、b是样式对象。
watch有哪些属性,分别有什么用
首先确认 watch是一个对象,一定要当成对象来用。对象就有键,有值。
- 键:就是你要监控的那个家伙,比如说$route,这个就是要监控路由的变化。或者是data中的某个变量。\
- 值:
- 可以是函数:就是当你监控的家伙变化时,需要执行的函数,这个函数有两个形参,第一个是当前值,第二个是变化后的值。
- 也可以是函数名:不过这个函数名要用单引号来包裹。
- 第三种情况值是包括选项的对象:选项包括有三个。
-
- 第一个
handler:其值是一个回调函数。即监听到变化时应该执行的函数。
- 第一个
-
- 第二个是
deep:其值是true或false;确认是否深入监听。(一般监听时是不能监听到对象属性值的变化的,数组的值变化可以听到。)
- 第二个是
-
- 第三个是
immediate:其值是true或false;确认是否以当前的初始值执行handler的函数。immediate默认是false
- 第三个是
computed和watch的区别
计算属性computed
- 支持缓存,只有依赖数据发生改变,才会重新进行计算
- 不支持异步,当computed内有异步操作时无效,无法监听数据的变化
- computed 属性值会默认走缓存,计算属性是基于它们的响应式依赖进行缓存的,也就是基于data中声明过或者父组件传递的props中的数据通过计算得到的值
- 如果一个属性是由其他属性计算而来的,这个属性依赖其他属性,是一个多对一或者一对一,一般用computed
- 如果computed属性属性值是函数,那么默认会走get方法;函数的返回值就是属性的属性值;在computed中的,属性都有一个get和一个set方法,当数据变化时,调用set方法。 侦听属性watch
- 不支持缓存,数据变,直接会触发相应的操作;
- watch支持异步;
- 监听的函数接收两个参数,第一个参数是最新的值;第二个参数是输入之前的值;
- 当一个属性发生变化时,需要执行对应的操作;一对多;
- 监听数据必须是data中声明过或者父组件传递过来的props中的数据,当数据变化时,触发其他操作,函数有两个参数,immediate:组件加载立即触发回调函数执行,deep: 深度监听,为了发现对象内部值的变化,复杂类型的数据时使用,例如数组中的对象内容的改变,注意监听数组的变动不需要这么做。注意:deep无法监听到数组的变动和对象的新增,参考vue数组变异,只有以响应式的方式触发才会被监听到。 watch和computed各自处理的数据关系场景不同。
- watch擅长处理的场景:一个数据影响多个数据
- computed擅长处理的场景:一个数据受多个数据影响
vue的el属性和$mount优先级
同:均是用来挂载对象的,作用相同,但是使用方法有所差异(如下实例);
异:el是vue的一个option,$mount是vue的一个实例方法;
vue响应式是怎么实现的(响应式数据)?
如何监控数据的变化:两种方法
- Vue 2.x defineProperty(es5) 把这些 property 全部转为 getter/setter
- Vue 3.x Proxy(es6)
1.vue初始化时会先将data里的数据进行Object.defineProperty get和set操作,每个属性在get时都会初始化一个dep对象,用于保存watcher(这个属性的订阅者)。在set时会触发dep中保存的watcher的更新方法,更新所有的订阅者。
2.然后进行template编译,编译时发现{{}}数据或动态绑定的data属性会创建一个watcher,然后读取对应data属性,在读取属性值时会触发属性的get操作,同时该属性的dep会将watcher保存下来。。
3.最后在操作data属性的变化时会触发属性的set操作,进而触发该属性的所有watcher的更新操作。
数组和对象类型的值变化的时候,通过defineReactive方法,借助了defineProperty,将所有的属性添加了getter和setter。用户在取值和设置的时候,可以进行一些操作。
缺陷:只能监控最外层的属性,如果是多层的,就要进行全量递归。
get里面会做依赖搜集(dep[watcher, watcher])
set里面会做数据更新(notify,通知watcher更新)
请问不需要响应式的数据应该怎么处理
- 方法一:将数据定义在data之外
- 方法二:Object.freeze() 用于冻结对象,禁止对于该对象(数组本质为数组,也可以对数组)的属性进行修改;(不能添加删除修改任何,原型也不行)
谈谈vue的双向数据绑定,数据劫持
- vue是通过Object.defineProperty()给data里的是数据添加getter/setter来实现数据劫持的。
- vm有两个方向:一是将模型(Model)转化成视图(View),即将后端传递的数据转化成所看到的页面。实现的方式是:数据绑定。二是将视图(View)转化成模型(Model),即将所看到的页面转化成后端的数据。实现的方式是:DOM 事件监听。这两个方向都实现的,我们称之为数据的双向绑定。
如果让你实现一个基本的数据双向绑定,你是什么思路?
数据双向绑定 采用数据劫持结合发布者-订阅者模式 我们已经知道实现数据的双向绑定,首先要对数据进行劫持监听,所以我们需要设置一个监听器Observer,用来监听所有属性。如果属性发上变化了,就需要告诉订阅者Watcher看是否需要更新。因为订阅者是有很多个,所以我们需要有一个消息订阅器Dep来专门收集这些订阅者,然后在监听器Observer和订阅者Watcher之间进行统一管理的。接着,我们还需要有一个指令解析器Compile,对每个节点元素进行扫描和解析,将相关指令对应初始化成一个订阅者Watcher,并替换模板数据或者绑定相应的函数,此时当订阅者Watcher接收到相应属性的变化,就会执行对应的更新函数,从而更新视图。因此接下去我们执行以下3个步骤,实现数据的双向绑定: 1.实现一个监听器Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者。
2.实现一个订阅者Watcher,可以收到属性的变化通知并执行相应的函数,从而更新视图。
3.实现一个解析器Compile,可以扫描和解析每个节点的相关指令,并根据初始化模板数据以及初始化相应的订阅器
为什么只对数据劫持,而要对数组进行方法重写
因为对象最多也就几十个属性,拦截起来数量不多,但是数组可能会有几百几千项,拦截起来非常耗性能,所以直接重写数组原型上的方法,是比较节省性能的方案
Vue数据监视
vue中是如何检测数组变化的呢?
Vue 将被侦听的数组的变更方法进行了包裹,所以它们也将会触发视图更新。这些被包裹过的方法包括:
- push()
- pop()
- shift()
- unshift()
- splice()
- sort()
- reverse()
使用过插槽么?用的是具名插槽还是匿名(默认)插槽或作用域插槽
插槽:让父组件可以向子组件的指定位置插入html结构,也是组件间通信的一种方式
- 默认插槽:插槽未命名
- 具名插槽:具名插槽其实就是给插槽取个名字。一个子组件可以放多个插槽,而且可以放在不同的地方,而父组件填充内容时,可以根据这个名字把内容填充到对应插槽中。
- 作用域插槽:带数据的插槽。子组件的数据来源于父组件,但是子组件决定不了外观与结构,html结构在父组件里,父组件里的html结构渲染的时候,子组件里面的数据又进行回传。把子组件之前收到的数据回传给父组件使用。
Vue组件之间的通信方式
- props:用于父子组件通信 父传子
注意:如果父组件给子组件传递的数据(函数),本质其实是子组件给父组件传递数据;
注意:如果父组件给子组件传递的数据(不是函数),本质就是父组件给子组件传递数据; - 自定义事件:
$on$emit可以实现子给父传递数据 - 插槽:作用域插槽可父子组件通信,详细见上
$ref:父子通信$attrs和$listeners
attrs属性当中是获取不到的
$listeners,它也是组件实例自身的一个属性,它可以获取到父组件给子组件传递自定义事件v-model父组件通过v-model传递值给子组件时,会自动传递一个value的prop属性,在子组件中通过this.$emit(‘input’,val)自动修改v-model绑定的值$parent和$children父子组件通信- 全局时间总线:
this.$bus.$emitthis.$bus.$on$bus是全能 - vuex
mapstatemapgetterstate仓库管理传递数据 全能 - pubsub-js: vue当中几乎不用,react使用比较多 全能
如果子组件改变props里的数据会发生什么
如果修改的是基本类型,则会报错。如下:
num: Number,
}
created() {
this.num = 999
}
改变的props数据是引用类型,如下
item: {
default: () => ({}),
}
}
created() {
// 不报错,并且父级数据会跟着变
this.item.name = 'sanxin';
// 会报错,跟基础类型报错一样
this.item = 'sss'
},
想要实现两个毫无关系的组件传递数据时 sessionStorage、localStorage
首相想到的就是路由的query传递参数,但是query适合传递单个数值的简单参数,所以如果想要传递对象之类的复杂信息,就可以通过Web Storage实现 sessionStorage:为每一个给定的源维持一个独立的存储区域,该区域在页面会话期间可用(即只要浏览器处于打开状态,包括页面重新加载和恢复)。 localStorage:同样的功能,但是在浏览器关闭,然后重新打开后数据仍然存在。 注意:无论是session还是local存储的值都是字符串形式。如果我们想要存储对象,需要在存储前JSON.stringify()将对象转为字符串,在取数据后通过JSON.parse()将字符串转为对象。
路由组件和非路由组件区别:
- 放置位置不同:非路由组件放在components中,路由组件放在pages或views中
- 使用方法不同:非路由组件通过标签使用,路由组件通过路由使用
- 但是在main.js注册完路由后,所有的路由和非路由组件身上都会拥有route属性
路由的两种跳转方式
- 声明式导航router-link标签 ,可以把router-link理解为一个a标签,它也可以加class修饰
<router-link to=""> - 编程式导航 :声明式导航能做的编程式都能做,而且还可以处理一些业务
this.$router.push()/replace()
router
$router:一般进行编程式导航进行路由跳转,this.$router.push()/replace()push后退回到上一页,replace本页顶替覆盖上页,后退会回到上上页
$route: 一般获取路由信息(name path params等)
注意this.$store只能在组件中使用,不能再js文件中使用。如果要在js中使用,需要引入import store from '@/store';
如果指定name与params配置, 且已在占位符后?,但params中数据是一个"", 无法跳转
解决1: 不指定params 解决2: 指定params参数值为undefined
路由组件能不能传递props数据?
- 可以: 可以将query或且params参数映射/转换成props传递给路由组件对象
- 实现: props: (route)=>({keyword1:route.params.keyword, keyword2: route.query.keyword })
Vue-router有哪几种钩子函数
- 全局导航钩子:
- router.beforeEach(to, from, next): 路由改变前的钩子
- router.beforeResolve : 在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,该钩子函数就被调用
- router.afterEach : 路由改变后的钩子
- 路由独享购子:
- beforeEnter
- 组件导航钩子:
- beforeRouteEnter 在进入当前组件对应的路由前调用
- beforeRouteUpdate 在当前路由改变,但是该组件被复用时调用
- beforeRouteLeave 在离开当前组件对应的路由前调用
导航守卫
路由全局守卫 一般写在路由配置文件route/index.js文件夹中
- 项目当中任何路由变化都可以检测到,通过条件判断可不可以进行路由跳转。
- 前置守卫:
router.beforeEach(async(to, from, next) => {}路由跳转之前可以做一些事情。 - 后置守卫:
router.afterEach() => {}路由跳转已经完成在执行。
路由独享守卫 一般写在路由配置文件route.js文件夹的具体路由配置位置中 - 需要在配置路由的地方使用,专门负责某一个路由
beforeEnter: (to, from, next) => {}
组件内守卫 - 组件内守卫需要书写在组件内部
beforeRouteEnterbeforeRouteUpdatebeforeRouteLeave
说说vue的生命周期
Vue生命周期钩子是如何实现的
Vue的生命周期钩子是回调函数,当创建组件实例的过程中会调用相应的钩子方法。 内部会对钩子进行处理,将钩子函数维护成数组的形式。
生命周期的实例方法有哪些
vm.$mount(),返回vm,可链式调用其它实例方法;(不常用)
const myVue = Vue.extend({ template: '<div>Hello!</div>' }) new myVue().$mount('#warp') //同上 new myVue({el:'#warp'}) // 在文档之外渲染并且随后挂载($mount不传参数) const component = new myVue().$mount() document.getElementById('app').appendChild(component.$el)
vm.$forceUpdate(),强制Vue实例重新渲染,不是重新加载组件,会触发beforeUpdate和updated这两个钩子函数,不会触发其他的钩子函数。它仅仅影响实例本身和插入插槽内容的子组件,而不是所有子组件;
vm.$nextTick(),参数为callback,等待视图全部更新后执行,回调函数的this自动绑定到调用它的实例上;
vm.$destroy(),销毁一个实例。清理它与其它实例的连接,解绑全部指令及事件监听器,但不能清理实例的DOM和data,会触发beforeDestroy和destroyed两个钩子函数。
vue中父子组件的生命周期
父子组件的生命周期是一个嵌套的过程 渲染的过程 父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted 子组件更新过程 父beforeUpdate->子beforeUpdate->子updated->父updated 父组件更新过程 父beforeUpdate->父updated 销毁过程 父beforeDestroy->子beforeDestroy->子destroyed->父destroyed
render函数
- 在main.js文件中配置过render: h => h(App),因为main.js中引入的是一个残缺版的vue,“node_modules/vue/types/index",相当于指只引入了vue的核心,没有引入模版解析器,所以这样是无法解析vue组件里的template模版里面的内容的,所以用render函数来解析。 为什么不引入完整版vue呢,因为没必要,占了内存。相当于铺瓷砖,买瓷砖+买工人=铺好的瓷砖+工人;而铺瓷砖+雇佣工人=铺好的瓷砖
mixin
mixin与vuex的区别
- vuex:用来做状态管理的,里面定义的变量在每个组件中均可以使用和修改,在任一组件中修改此变量的值之后,其他组件中此变量的值也会随之修改。
- Mixins:可以定义共用的变量,在每个组件中使用,引入组件中之后,各个变量是相互独立的,值的修改在组件中不会相互影响。 mixin与公共组件的区别
- 组件:在父组件中引入组件,相当于在父组件中给出一片独立的空间供子组件使用,然后根据props来传值,但本质上两者是相对独立的。 mixin选项合并冲突时
- 数据对象同名———在内部会进行浅合并,在和组件的数据发生冲突时,以组件数据优先。
- 钩子函数同名———同名钩子函数将混合为一个数组,因此都将被调用,混入对象mixins的钩子将在组件自身钩子之前调用。
- 值为对象的选项———例如在methods, components 和 directives 中,将被混合为同一个对象。两个对象键名冲突时,取组件对象的键值对。
Vue中的nextTick的实现原理
this.$nextTick(回调函数) 作用:在下一次DOM更新结束后执行其指定的回调函数 使用场景:当改变数据后,要基于更新后的新dom进行某些操作时,要在nextTick指定的回调函数中执行
vue的SSR是什么,有什么好处
SSR就是服务端渲染
- 基于nodejs serve服务环境开发,所有html代码在服务端渲染
- 数据返回给前端,然后前端进行“激活”,即可成为浏览器识别的html代码
- SSR首次加载更快,有更好的用户体验,有更好的seo优化,因为爬虫能看到整个页面的内容,如果是vue项目,由于数据还要经过解析,这就造成爬虫并不会等待你的数据加载完成,所以其实Vue项目的seo体验并不是很好
Vuex的相关知识点
juejin.cn/post/684490… vue中实现集中式状态(数据)管理的一个插件,对vue应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信
- vuex的使用场景
- 多个组件依赖于同一状态
- 来自不同组件的行为需要变更同一状态
描述组件渲染和更新过程?
Promise.all
Promise.all可以将多个Promise实例包装成一个新的Promise实例。同时,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值。
vue中 keep-alive 组件的作用
- 比如: 有一个列表页面和一个 详情页面,那么用户就会经常执行打开详情=>返回列表=>打开详情这样的话 列表 和 详情 都是一个频率很高的页面,那么就可以对列表组件使用
<keep-alive></keep-alive>进行缓存,这样用户每次返回列表的时候,都能从缓存中快速渲染,而不是重新渲染。 - 1、属性:
include:字符串或正则表达式。只有匹配的组件会被缓存。
exclude:字符串或正则表达式。任何匹配的组件都不会被缓存。 - 2、用法:
包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。和<transition>相似,<keep-alive>是一个抽象组件:它自身不会渲染一DOM 元素,也不会出现在父组件链中。
使用类
为什么v-for和v-if不建议用在一起
v-for优先级高于v-if,如果连在一起使用的话会把v-if给每一个元素都添加上,重复运行于每一个v-for循环中,会造成性能浪费(每次渲染都会先循环再进行条件判断)
如果避免出现这种情况,则在外层嵌套template(页面渲染不生成dom节点),在这一层进行v-if判断,然后在内部进行v-for循环(如果条件出现在循环内部,可通过计算属性computed提前过滤掉那些不需要显示的项)
v-if和v-show有什么区别,各自的使用场景有哪些
v-if是通过控制dom元素的删除和生成来实现显隐,每一次显隐都会使组件重新跑一遍生命周期,因为显隐决定了组件的生成和销毁
v-show 只是切换当前DOM元素的css属性的显示与隐藏
v-if(true或false)在切换过程中会频繁的操作dom,消耗性能;而v-show则在页面初次加载时就渲染完成,后期不会占用更多的性能花销。所以,如果需要频繁地切换,则使用v-show指令比较好。反之则使用v-if 比较好。
V-html会导致哪些问题?
- XSS 攻击
- v-html 会替换标签内部的元素
为什么不建议用index做key,为什么不建议用随机数做key,diff算法
- 用组件唯一的 id(一般由后端返回)作为它的 key,实在没有的情况下,可以在获取到列表的时候通过某种规则为它们创建一个 key,并保证这个 key 在组件整个生命周期中都保持稳定。
- 别用 index 作为 key,和没写基本上没区别,因为不管你数组的顺序怎么颠倒,index 都是 0, 1, 2 这样排列,导致 Vue 会复用错误的旧子节点,做很多额外的工作。
- 千万别用随机数作为 key,不然旧节点会被全部删掉,新节点重新创建
Vue的Key的作用
key 的作用主要是 可以唯一的确定一个DOM元素,让diff算法更加高效,实现高效的更新虚拟 DOM,提高性能。
计算变量时,methods和computed哪个好
1.computed:当计算属性依赖的变量发生变更时,才会重新执行计算 2.methods:只要页面重新渲染,就会重新执行计算 所以计算变量时,computed会好一些,因为computed会有缓存,当计算属性依赖的变量发生变更时,才会重新执行计算。
为什么需要进行二次封装axios
请求拦截器,响应拦截器:可以在发请求之前处理一些业务( 比如请求发出去之前 用请求头带点参数(uuid,token)给服务器 ),当服务器响应数据返回以后,可以处理一些事情 axios发送请求返回结果是Promise对象
axios与async await函数配合使用
- axios发送请求返回结果是Promise对象,而async函数一般跟promise对象,且返回的结果就是promise的结果(如果跟的不是一个promise对象会返回一个成功的promise,成功的promise里的数据放的就是该数据)
- 由于我们的promis是异步请求,我们发现请求需要花费时间,但是它是异步的,所有后面的console.log(“result”);console.log(result)会先执行,等我们的请求得到响应后,才执行console.log(“res”);console.log(res),这也符合异步的原则,但是我们如果在请求下面啊执行的是将那个请求的结果赋值给某个变量,这样就会导致被赋值的变量先执行,并且赋值为undefine,因为此时promise还没有完成。
- 所以我们引入了async await,async写在函数名前,await卸载api函数前面。await含义是async标识的函数体内的并且在await标识代码后面的代码先等待await标识的异步请求执行完,再执行。这也使得只有reqCateGoryList执行完,result 得到返回值后,才会执行后面的输出操作。
开发一个前端模块可以概括为以下几个步骤
- 写静态页面、拆分为静态组件;
- 发请求(API);
- vuex(actions、mutations、state三连操作);
- 组件获取仓库数据,动态展示;
你所做的项目如何解决跨域问题的?
- 跨域可以jsonp,cors,代理
- 本项目用的webpack本地代理
- 在webpack.config.js(vue.config.js)进行代理配置(devServer-proxy)
使用vue的重要原则
1.所被Vue管理的函数,最好写成普通函数,这样this的指向才是vm 或 组件实例对象。 2.所有不被Vue所管理的函数(定时器的回调函数、ajax的回调函数、Promise的回调函数等),最好写成箭头函数,这样this的指向才是vm 或 组件实例对象。
vue-lazy load 图片懒加载
- 还没有加载得到目标图片时, 先显示loading图片
- 在
进入可视范围才加载请求目标图片
路由懒加载
- 原因:当打包构建应用时,JS包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了
- 使用:
- 定义比如foo 函数 配置路由时component: foo()
- 直接配置路由时写函数 component: () => import('@/pages/Home')
控制组件的显示与隐藏
- footer在登录注册页面是不存在的,所以要隐藏,v-if 或者 v-show
- 这里使用v-show,因为v-if会频繁的操作dom元素消耗性能,v-show只是通过样式将元素显示或隐藏
- 配置路由的时候,可以给路由配置元信息meta,
- 在路由的原信息中定义show属性,用来给v-show赋值,判断是否显示footer组件
关于商品搜索功能的动态搜素业务
在每个三级列表和搜索按钮加一个点击触发事件,只要点击了就执行搜索函数。但这样就会生成很多回调函数,很耗性能。 最佳方法:我们每次进行新的搜索时,我们的query和params参数中的部分内容肯定会改变,而且这两个参数是路由的属性。我们可以通过监听路由信息的变化来动态发起搜索请求。 如下图所示,$route是组件的属性,所以watch是可以监听的(watch可以监听组件data中所有的属性)
工作当中是否自己封装过一些通用的组件
分页器
报错类解决
编程式路由跳转到当前路径且参数没有变化时会抛出 NavigationDuplicated 错误,如何解决
原因分析:
vue-router3版本之后, 引入了push和replace()的promise的语法, promise需要传递一个成功或者失败的回调, 且内部会判断如果要跳转的路径和参数都没有变化, 会抛出一个失败的promise
方案1: 在进行跳转时, 指定跳转成功的回调函数或catch错误,但用几次要写几次,没有一劳永逸的解决
方案2: 修正Vue原型上的push和replace方法 优秀完美的解决方法
解决多个swiper冲突的问题 定义可复用的轮播组件
this.$refs.可以, 只会匹配, 当前组件中的对应元素
解决swiper动态页面轮播的bug
- 原因:要求: 创建swiper对象必须要在轮播列表页面显示之后执行才可以 ==> 否则轮播效果有问题
- 尝试1.之前我放在mounted里,因为觉得mounted是已经挂载完毕了,我觉得这时候结构肯定已经有了,但是在mounted里new Swiper之后发现没生效,还是不行。但是页面里涉及到了异步数据,里面v-for遍历的是服务器给我返回的数据。而此时服务器根本还没返回数据给仓库,仓库里数据都没有,所以根本没有数据遍历,此时的结构都是不完整的。
- 反思1.是否能放在updated里,可以实现,但是未考虑全面,因为如果此组件之后还写了别的响应式数据,只要别的地方更新一次,你写在updated的new Swiper就会重新执行一次,浪费性能。
- 方法::使用watch+this.nextTick(将回调延迟到下次 DOM 更新循环(更新界面)之后执行。这时候一定数据也回来了,v-for也执行完了,完整的页面已经都有了。此时轮播图就解决了。 nextTick(() => {// 回调函数在界面更新之后执行)
关于访问undefined的属性值会引起红色警告(假报错)的原因和解决方法
以获取商品categoryView信息为例,categoryView是一个对象。
const getters = {
categoryView(state){
return state.goodInfo.categoryView
}
}
- 原因:假设我们网络故障,导致goodInfo的数据没有请求到,即goodInfo是一个空的对象,当我们去调用getters中的
return state.goodInfo.categoryView时,因为goodInfo为空,所以也不存在categoryView,即我们getters得到的categoryView为undefined。所以我们在html使用该变量时就会出现没有该属性的报错。 - 即:网络正常时不会出错,一旦无网络或者网络问题就会报错。
- 总结:所以我们在写getters的时候要养成一个习惯在返回值后面加一个||条件。即当属性值undefined时,会返回||后面的数据,这样就不会报错。
- 如果返回值为对象加||{},数组:||[ ]。
- 此处categoryView为对象,所以将getters代码改为
return state.goodInfo.categoryView||{}
关于浏览器控制台懒加载的问题
ajax请求之后显示的内容 没有疑问 是在置空了三个id之后的。当然全部是空的。但是在这个ajax请求发送之前 id参数是还没有置空的 所那此处发的请求 参数里id应该是在的。但是 在网络list请求里看得到我的id还在 为什么我的vue和控制台里面的参数 却还是置空了 按道理这时候还没有置空!
解答:我发送这个ajax请求之前输出控制台的参数 其实输出的时候 我点进去看的瞬间 控制台给我反馈回来的是代码全部执行完之后的数据 而不是这个步骤下当时的数据 所以里面的东西也空了 但是我的网络list请求里是我当前那个步骤下出去的数据 所以是对的。
node严格同步的 就不会有这问题
优化类方案
优化高频事件触发处理: 利用lodash进行函数节流处理
区分节流防抖,详见其他篇笔记
优化减小打包文件: 对依赖资源包实现按需引入
- 比如element-ui只引入messagebox,button
- 比如lodash只引入throttle
优化减少组件对象数量: 使用编程式导航代替声明式导航
优化事件处理效率: 利用事件委托
编程式导航+事件委托优化三级联动组件路由跳转业务 比如三级标签列表有很多,每一个标签都是一个页面链接,我们要实现通过点击表现进行路由跳转。
- 对于导航式路由,我们有多少个a标签就会生成多少个router-link标签,这样当我们频繁操作时会出现卡顿现象。
- 对于编程式路由,我们是通过触发点击事件实现路由跳转。同理有多少个a标签就会有多少个触发函数。虽然不会出现卡顿,但是也会影响性能。
- 上面两种方法无论采用哪一种,都会影响性能。我们提出一种:编程时导航+事件委派 的方式实现路由跳转。事件委派即把子节点的触发事件都委托给父节点。这样只需要一个回调函数goSearch就可以解决。 但是,有两个问题需要解决
- 如何确定我们点击的一定是a标签呢?如何保证我们只能通过点击a标签才跳转呢?
- 为三个等级的a标签添加自定义属性date-categoryName绑定商品标签名称来标识a标签(其余的标签是没有该属性的)
2.如何获取子节点标签的商品名称和商品id(我们是通过商品名称和商品id进行页面跳转的) - 为三个等级的a标签再添加自定义属性data-category1Id、data-category2Id、data-category3Id来获取三个等级a标签的商品id,用于路由跳转。
- 我们可以通过在函数中传入event参数,获取当前的点击事件,通过event.target属性获取当前点击节点,再通过dataset属性获取节点的属性信息。
优化请求执行的位置, 减少请求次数
比如全局组件TypeNav的请求业务优化
- 我们在三级列表全局组件TypeNav中的mounted进行了请求一次商品分类列表数据。
- 由于Vue在路由切换的时候会销毁旧路由,当我们再次使用三级列表全局组件时还会发一次请求。
- 当我们在包含三级列表全局组件的不同组件之间进行切换时,都会进行一次信息请求。
- 由于信息都是一样的,出于性能的考虑我们希望该数据只请求一次,所以我们把这次请求放在App.vue的mounted中
this.$store.dispatch。(根组件App.vue的mounted只会执行一次) 注意:虽然main.js也是只执行一次,但是不可以放在main.js中。因为只有组件的身上才会有$store属性。
sprite精灵图
在网站中有很多小图片的时候,一定要把这些小图片合并为一张大的图片,然后通过background分割到需要展示的图片。
浏览器请求资源的时候,同源域名请求资源的时候有最大并发限制,chrome为6个,就比如你的页面上有10个相同CDN域名小图片,那么需要发起10次请求去拉取,分两次并发。第一次并发请求回来后,发起第二次并发。
如果你把10个小图片合并为一张大图片的画,那么只用一次请求即可拉取下来10个小图片的资源。减少服务器压力,减少并发,减少请求次数。
懒加载 图片懒加载 路由懒加载
缓存
缓存的原理就是更快读写的存储介质+减少IO+减少CPU计算=性能优化。而性能优化的第一定律就是:优先考虑使用缓存。
缓存的主要手段有:浏览器缓存、CDN、反向代理、本地缓存、分布式缓存、数据库缓存。
使用keep-alive来缓存组件
作用:实现组件缓存,保持这些组件的状态,以避免反复渲染导致的性能问题。 需要缓存组件 频繁切换,不需要重复渲染 场景:tabs标签页 后台导航,vue性能优化 原理:Vue.js内部将DOM节点抽象成了一个个的VNode节点,keep-alive组件的缓存也是基于VNode节点的而不是直接存储DOM结构。它将满足条件(pruneCache与pruneCache)的组件在cache对象中缓存起来,在需要重新渲染的时候再将vnode节点从cache对象中取出并渲染。