1. 谈一谈对 MVVM 的理解 ?
参考答案:
MVVM是 Model-View-ViewModel 的缩写,他是一种设计思想,Model 代表数据模型,也可以在Model 中定义数据修改和操作的业务逻辑,view代表 UI 组件,他负责将数据模型转化成UI展示出来,view 是一个同步 View 和 Model 的对象。在 MVVM 架构中,View 和 Model 之间并没有直接联系,而是通过 ViewModel 进行交互,Model 和 ViewModel 之间的交互是双向的,因此,View 的变化会同步到 Model 中,而 Model的变化也会立即反应的 View 中,因此开发者只需关注业务逻辑,不需要手动操作 DOM,不需要关注数据状态的同步问题,复杂的数据状态维护完全由 MVVM 来统一管理。
2. 说一下 vue 的优点 ?
参考答案:
Vue 是一个构建数据驱动的 Web 界面的渐进式框架。 Vue 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件。核心是一个响应的数据绑定系统。 关于 Vue 的优点,主要有响应式编程、组件化开发、虚拟 DOM
- 响应式编程
这里的响应式不是 @media 媒体查询中的响应式布局,而是指 Vue 会自动对页面中某些数据的变化做出响应。这也就是 Vue 最大的优点,通过 MVVM 思想实现数据的双向绑定,让开发者不用再操作 DOM 对象,有更多的时间去思考业务逻辑。
- 组件化开发
Vue 通过组件,把一个单页应用中的各种模块拆分到一个一个单独的组件(component)中,我们只要先在父级应用中写好各种组件标签(占坑),并且在组件标签中写好要传入组件的参数(就像给函数传入参数一样,这个参数叫做组件的属性),然后再分别写好各种组件的实现(填坑),然后整个应用就算做完了。 组件化开发的优点:提高开发效率、方便重复使用、简化调试步骤、提升整个项目的可维护性、便于协同开发。
- 虚拟 DOM
在传统开发中,用 JQuery 或者原生的 JavaScript DOM 操作函数对 DOM 进行频繁操作的时候,浏览器要不停的渲染新的 DOM 树,导致在性能上面的开销特别的高。 而 Virtual DOM 则是虚拟 DOM 的英文,简单来说,他就是一种可以预先通过 JavaScript 进行各种计算,把最终的 DOM 操作计算出来并优化,由于这个 DOM 操作属于预处理操作,并没有真实的操作 DOM,所以叫做虚拟 DOM。最后在计算完毕才真正将 DOM 操作提交,将 DOM 操作变化反映到 DOM 树上。
3. Vue 实现双向数据绑定原理是什么?
参考答案:
Vue2.x 采用数据劫持结合发布订阅模式(PubSub 模式)的方式,通过 Object.defineProperty 来劫持各个属性的 setter、getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
当把一个普通 Javascript 对象传给 Vue 实例来作为它的 data 选项时,Vue 将遍历它的属性,用 Object.defineProperty 将它们转为 getter/setter。用户看不到 getter/setter,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化。
Vue 的数据双向绑定整合了 Observer,Compile 和 Watcher 三者,通过 Observer 来监听自己的 model 的数据变化,通过 Compile 来解析编译模板指令,最终利用 Watcher 搭起 Observer 和 Compile 之间的通信桥梁,达到数据变化->视图更新,视图交互变化(例如 input 操作)->数据 model 变更的双向绑定效果。
Vue3.x 放弃了 Object.defineProperty ,使用 ES6 原生的 Proxy,来解决以前使用 Object.defineProperty 所存在的一些问题。
4. 说一下对 Vue2.x 响应式原理的理解 ?
参考答案:
Vue 在初始化数据时,会使用 Object.defineProperty 重新定义 data 中的所有属性,当页面使用对应属性时,首先会进行依赖收集(收集当前组件的 watcher),如果属性发生变化会通知相关依赖进行更新操作(发布订阅)。
5. 解释一下对 Vue 生命周期的理解 ?
- 什么是 vue 生命周期
- vue 生命周期的作用是什么
- vue 生命周期有几个阶段
- 第一次页面加载会触发哪几个钩子
- DOM 渲染在哪个周期就已经完成
- 多组件(父子组件)中生命周期的调用顺序说一下
参考答案:
1.什么是 vue 生命周期 ?
对于 vue 来讲,生命周期就是一个 vue 实例从创建到销毁的过程。
2. vue 生命周期的作用是什么 ?
在生命周期的过程中会运行着一些叫做生命周期的函数,给予了开发者在不同的生命周期阶段添加业务代码的能力。
其实和回调是一个概念,当系统执行到某处时,检查是否有 hook(钩子),有的话就会执行回调。
通俗的说,hook 就是在程序运行中,在某个特定的位置,框架的开发者设计好了一个钩子来告诉我们当前程序已经运行到特定的位置了,会触发一个回调函数,并提供给我们,让我们可以在生命周期的特定阶段进行相关业务代码的编写。
3.vue 生命周期有几个阶段 ?
它可以总共分为 8 个阶段:创建前/后, 载入前/后,更新前/后,销毁前/销毁后。
- beforeCreate:是 new Vue( ) 之后触发的第一个钩子,在当前阶段 data、methods、computed 以及 watch 上的数据和方法都不能被访问。
- created:在实例创建完成后发生,当前阶段已经完成了数据观测,也就是可以使用数据,更改数据,在这里更改数据不会触发 updated 函数。可以做一些初始数据的获取,在当前阶段无法与 DOM 进行交互,如果非要想,可以通过 vm.$nextTick 来访问 DOM 。
- beforeMount:发生在挂载之前,在这之前 template 模板已导入渲染函数编译。而当前阶段虚拟 DOM 已经创建完成,即将开始渲染。在此时也可以对数据进行更改,不会触发 updated。
- mounted:在挂载完成后发生,在当前阶段,真实的 DOM 挂载完毕,数据完成双向绑定,可以访问到 DOM 节点,使用 $refs 属性对 DOM 进行操作。
- beforeUpdate:发生在更新之前,也就是响应式数据发生更新,虚拟 DOM 重新渲染之前被触发,你可以在当前阶段进行更改数据,不会造成重渲染。
- updated:发生在更新完成之后,当前阶段组件 DOM 已完成更新。要注意的是避免在此期间更改数据,因为这可能会导致无限循环的更新。
- beforeDestroy:发生在实例销毁之前,在当前阶段实例完全可以被使用,我们可以在这时进行善后收尾工作,比如清除计时器。
- destroyed:发生在实例销毁之后,这个时候只剩下了 DOM 空壳。组件已被拆解,数据绑定被卸除,监听被移出,子实例也统统被销毁。
4.第一次页面加载会触发哪几个钩子 ?
会触发 4 个钩子,分别是:beforeCreate、created、beforeMount、mounted
5.DOM 渲染在哪个周期就已经完成 ?
DOM 渲染是在 mounted 阶段完成,此阶段真实的 DOM 挂载完毕,数据完成双向绑定,可以访问到 DOM 节点。
6.多组件(父子组件)中生命周期的调用顺序说一下 ?
组件的调用顺序都是先父后子,渲染完成的顺序是先子后父。组件的销毁操作是先父后子,销毁完成的顺序是先子后父。
- 加载渲染过程:父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount- >子mounted->父mounted
- 子组件更新过程:父beforeUpdate->子beforeUpdate->子updated->父updated
- 父组件更新过程:父 beforeUpdate -> 父 updated
- 销毁过程:父beforeDestroy->子beforeDestroy->子destroyed->父destroyed
6. Vue 实现双向数据绑定原理是什么 ?
参考答案:
Vue2.x 采用数据劫持结合发布订阅模式(PubSub 模式)的方式,通过 Object.defineProperty 来劫持各个属性的 setter、getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
当把一个普通 Javascript 对象传给 Vue 实例来作为它的 data 选项时,Vue 将遍历它的属性,用 Object.defineProperty 将它们转为 getter/setter。用户看不到 getter/setter,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化。
Vue 的数据双向绑定整合了 Observer,Compile 和 Watcher 三者,通过 Observer 来监听自己的 model 的数据变化,通过 Compile 来解析编译模板指令,最终利用 Watcher 搭起 Observer 和 Compile 之间的通信桥梁,达到数据变化->视图更新,视图交互变化(例如 input 操作)->数据 model 变更的双向绑定效果。
Vue3.x 放弃了 Object.defineProperty ,使用 ES6 原生的 Proxy,来解决以前使用 Object.defineProperty 所存在的一些问题。
7. 说一下在 Vue2.x 中如何检测数组的变化 ?
参考答案:
Vue2.x 中实现检测数组变化的方法,是将数组的常用方法进行了重写。Vue 将 data 中的数组进行了原型链重写,指向了自己定义的数组原型方法。这样当调用数组 api 时,可以通知依赖更新。如果数组中包含着引用类型,会对数组中的引用类型再次递归遍历进行监控。这样就实现了监测数组变化。
流程:
- 初始化传入 data 数据执行 initData
- 将数据进行观测 new Observer
- 将数组原型方法指向重写的原型
- 深度观察数组中的引用类型
有两种情况无法检测到数组的变化。
- 当利用索引直接设置一个数组项时,例如 vm.items[indexOfItem] = newValue
- 当修改数组的长度时,例如 vm.items.length = newLength
不过这两种场景都有对应的解决方案。
利用索引设置数组项的替代方案
// vm.$set,Vue.set的一个别名
vm.$set(vm.items, indexOfItem, newValue)
修改数组的长度的替代方案
// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)
8. vue2.x 和 vuex3.x 渲染器的 diff 算法分别说一下 ?
参考答案:
简单来说,diff 算法有以下过程
- 同级比较,再比较子节点
- 先判断一方有子节点一方没有子节点的情况(如果新的 children 没有子节点,将旧的子节点移除)
- 比较都有子节点的情况(核心 diff)
- 递归比较子节点
正常 Diff 两个树的时间复杂度是 O(n^3),但实际情况下我们很少会进行跨层级的移动 DOM,所以 Vue 将 Diff 进行了优化,从O(n^3) -> O(n),只有当新旧 children 都为多个子节点时才需要用核心的 Diff 算法进行同层级比较。
Vue2 的核心 Diff 算法采用了双端比较的算法,同时从新旧 children 的两端开始进行比较,借助 key 值找到可复用的节点,再进行相关操作。相比 React 的 Diff 算法,同样情况下可以减少移动节点次数,减少不必要的性能损耗,更加的优雅。
Vue3.x 借鉴了 ivi 算法和 inferno 算法
在创建 VNode 时就确定其类型,以及在 mount/patch 的过程中采用位运算来判断一个 VNode 的类型,在这个基础之上再配合核心的 Diff 算法,使得性能上较 Vue2.x 有了提升。该算法中还运用了动态规划的思想求解最长递归子序列。
9. Vue 的路由实现
- 解释 hash 模式和 history 模式的实现原理?
- 说一下 router 与 route 的区别?
- vueRouter 有哪几种导航守卫?
- 解释一下 vueRouter 的完整的导航解析流程是什么?
参考答案:
1.解释 hash 模式和 history 模式的实现原理 ?
# 后面 hash 值的变化,不会导致浏览器向服务器发出请求,浏览器不发出请求,就不会刷新页面;通过监听 hashchange 事件可以知道 hash 发生了哪些变化,然后根据 hash 变化来实现更新页面部分内容的操作。
history 模式的实现,主要是 HTML5 标准发布的两个 API,pushState 和 replaceState,这两个 API 可以在改变 URL,但是不会发送请求。这样就可以监听 url 变化来实现更新页面部分内容的操作。
两种模式的区别:
- 首先是在 URL 的展示上,hash 模式有“#”,history 模式没有
- 刷新页面时,hash 模式可以正常加载到 hash 值对应的页面,而 history 没有处理的话,会返回 404,一般需要后端将所有页面都配置重定向到首页路由
- 在兼容性上,hash 可以支持低版本浏览器和
2.说一下 router 与 router 的区别 ?
$route 对象表示当前的路由信息,包含了当前 URL 解析得到的信息。包含当前的路径,参数,query 对象等。
- $route.path:字符串,对应当前路由的路径,总是解析为绝对路径,如 "/foo/bar"。
- $route.params: 一个 key/value 对象,包含了 动态片段 和 全匹配片段,如果没有路由参数,就是一个空对象。
- route.query∗:一个key/value对象,表示URL查询参数。例如对于路径∗/foo?user=1∗,则有∗route.query*:一个 key/value 对象,表示 URL 查询参数。例如对于路径 */foo?user=1*,则有 *route.query∗:一个key/value对象,表示URL查询参数。例如对于路径∗/foo?user=1∗,则有∗route.query.user == 1,如果没有查询参数,则是个空对象。
- $route.hash:当前路由的 hash 值 (不带 #) ,如果没有 hash 值,则为空字符串。
- $route.fullPath:完成解析后的 URL,包含查询参数和 hash 的完整路径。
- $route.matched:数组,包含当前匹配的路径中所包含的所有片段所对应的配置参数对象。
- $route.name:当前路径名字
- $route.meta:路由元信息
$route 对象出现在多个地方:
- 组件内的 this.$route 和 route watcher 回调(监测变化处理)
- router.match(location) 的返回值
- scrollBehavior 方法的参数
- 导航钩子的参数,例如 router.beforeEach 导航守卫的钩子函数中,to 和 from 都是这个路由信息对象。
$router 对象是全局路由的实例,是 router 构造方法的实例。
$router 对象常用的方法有:
- push:向 history 栈添加一个新的记录
- go:页面路由跳转前进或者后退
- replace:替换当前的页面,不会向 history 栈添加一个新的记录
3.vueRouter 有哪几种导航守卫 ?
- 全局前置/钩子:beforeEach、beforeR-esolve、afterEach
- 路由独享的守卫:beforeEnter
- 组件内的守卫:beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave
5.解释一下 vueRouter 的完整的导航解析流程是什么 ?
一次完整的导航解析流程如下:
- 导航被触发。
- 在失活的组件里调用离开守卫。
- 调用全局的 beforeEach 守卫。
- 在重用的组件里调用 beforeRouteUpdate 守卫(2.2+)。
- 在路由配置里调用 beforeEnter。
- 解析异步路由组件。
- 在被激活的组件里调用 beforeRouteEnter。
- 调用全局的 beforeResolve 守卫(2.5+)。
- 导航被确认。
- 调用全局的 afterEach 钩子。
- 触发 DOM 更新。
- 用创建好的实例调用 beforeRouteEnter 守卫中传给 next 的回调函数。
10. vuex 是什么?怎么使用它?什么场景下我们会使用到 vuex ?
参考答案:
1.vuex 是什么 ?
vuex 是一个专为 Vue 应用程序开发的状态管理器,采用集中式存储管理应用的所有组件的状态。每一个 vuex 应用的核心就是 store(仓库)。“store” 基本上就是一个容器,它包含着应用中大部分的状态 (state)。
2.为什么需要 vuex ?
由于组件只维护自身的状态(data),组件创建时或者路由切换时,组件会被初始化,从而导致 data 也随之销毁。
3.使用方法 ?
在 main.js 引入 store,注入。只用来读取的状态集中放在 store 中, 改变状态的方式是提交 mutations,这是个同步的事物,异步逻辑应该封装在 action 中。
4.什么场景下会使用到 vuex ?
如果是 vue 的小型应用,那么没有必要使用 vuex,这个时候使用 vuex 反而会带来负担。组件之间的状态传递使用 props、自定义事件来传递即可。
但是如果涉及到 vue 的大型应用,那么就需要类似于 vuex 这样的集中管理状态的状态机来管理所有组件的状态。例如登录状态、加入购物车、音乐播放等,总之只要是开发 vue 的大型应用,都推荐使用 vuex 来管理所有组件状态。
11. 说一下 v-for 与 v-show 的区别 ?
参考答案:
- 共同点:都是动态显示 DOM 元素
- 区别点:
- 手段v-if 是动态的向 DOM 树内添加或者删除 DOM 元素v-show 是通过设置 DOM 元素的 display 样式属性控制显隐
- 编译过程v-if 切换有一个局部编译/卸载的过程,切换过程中合适地销毁和重建内部的事件监听和子组件v-show 只是简单的基于 css 切换
- 编译条件v-if 是惰性的,如果初始条件为假,则什么也不做。只有在条件第一次变为真时才开始局部编译v-show 是在任何条件下(首次条件是否为真)都被编译,然后被缓存,而且 DOM 元素保留
- 性能消耗v-if 有更高的切换消耗v-show 有更高的初始渲染消耗
- 使用场景v-if 适合运营条件不大可能改变v-show 适合频繁切换
12. Vue 中如何进行组件的使用?Vue 如何实现全局组件的注册?
参考答案:
要使用组件,首先需要使用 import 来引入组件,然后在 components 属性中注册组件,之后就可以在模板中使用组件了。
可以使用 Vue.component 方法来实现全局组件的注册。
13. vue-cli 工程相关
- 构建 vue-cli 工程都用到了哪些技术?他们的作用分别是什么?
- vue-cli 工程常用的 npm 命令有哪些?
参考答案:
构建 vue-cli 工程都用到了哪些技术?他们的作用分别是什么?
- vue.js:vue-cli 工程的核心,主要特点是双向数据绑定和组件系统。
- vue-router:vue 官方推荐使用的路由框架。
- vuex:专为 Vue.js 应用项目开发的状态管理器,主要用于维护 vue 组件间共用的一些 变量 和 方法。
- axios(或者 fetch、ajax):用于发起 GET 、或 POST 等 http请求,基于 Promise 设计。
- vux等:一个专为vue设计的移动端UI组件库。
- webpack:模块加载和vue-cli工程打包器。
- eslint:代码规范工具
vue-cli 工程常用的 npm 命令有哪些?
- 下载 node_modules 资源包的命令:npm install
- 启动 vue-cli 开发环境的 npm命令:npm run dev
- vue-cli 生成 生产环境部署资源 的 npm命令:npm run build
- 用于查看 vue-cli 生产环境部署资源文件大小的 npm命令:npm run build --report
14. nextTick 的作用是什么?他的实现原理是什么?
参考答案:
作用:vue 更新 DOM 是异步更新的,数据变化,DOM 的更新不会马上完成,nextTick 的回调是在下次 DOM 更新循环结束之后执行的延迟回调。
实现原理:nextTick 主要使用了宏任务和微任务。根据执行环境分别尝试采用
- Promise:可以将函数延迟到当前函数调用栈最末端
- MutationObserver :是 H5 新加的一个功能,其功能是监听 DOM 节点的变动,在所有 DOM 变动完成后,执行回调函数
- setImmediate:用于中断长时间运行的操作,并在浏览器完成其他操作(如事件和显示更新)后立即运行回调函数
- 如果以上都不行则采用 setTimeout 把函数延迟到 DOM 更新之后再使用,原因是宏任务消耗大于微任务,优先使用微任务,最后使用消耗最大的宏任务。
15. Vue 组件的 data 为什么必须是函数 ?
参考答案:
组件中的 data 写成一个函数,数据以函数返回值形式定义。这样每复用一次组件,就会返回一份新的 data,类似于给每个组件实例创建一个私有的数据空间,让各个组件实例维护各自的数据。而单纯的写成对象形式,就使得所有组件实例共用了一份 data,就会造成一个变了全都会变的结果
16. 说一下 watch 与 computed 的区别是什么?以及他们的使用场景分别是什么?
参考答案:
区别:
- 都是观察数据变化的(相同)
- 计算属性将会混入到 vue 的实例中,所以需要监听自定义变量;watch 监听 data 、props 里面数据的变化;
- computed 有缓存,它依赖的值变了才会重新计算,watch 没有;
- watch 支持异步,computed 不支持;
- watch 是一对多(监听某一个值变化,执行对应操作);computed 是多对一(监听属性依赖于其他属性)
- watch 监听函数接收两个参数,第一个是最新值,第二个是输入之前的值;
- computed 属性是函数时,都有 get 和 set 方法,默认走 get 方法,get 必须有返回值(return)
watch 的 参数:
- deep:深度监听
- immediate :组件加载立即触发回调函数执行
computed 缓存原理:
conputed本质是一个惰性的观察者;当计算数据存在于 data 或者 props里时会被警告;
vue 初次运行会对 computed 属性做初始化处理(initComputed),初始化的时候会对每一个 computed 属性用 watcher 包装起来 ,这里面会生成一个 dirty 属性值为 true;然后执行 defineComputed 函数来计算,计算之后会将 dirty 值变为 false,这里会根据 dirty 值来判断是否需要重新计算;如果属性依赖的数据发生变化,computed 的 watcher 会把 dirty 变为 true,这样就会重新计算 computed 属性的值。
17. 说一下 ref 的作用是什么 ?
参考答案:
ref 的作用是被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的 $refs 对象上。其特点是:
- 如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素
- 如果用在子组件上,引用就指向组件实例
所以常见的使用场景有:
- 基本用法,本页面获取 DOM 元素
- 获取子组件中的 data
- 调用子组件中的方法
18. 说一下你知道的 vue 修饰符都有哪些 ?
参考答案:
在 vue 中修饰符可以分为 3 类:
- 事件修饰符
- 按键修饰符
- 表单修饰符
事件修饰符
在事件处理程序中调用 event.preventDefault 或 event.stopPropagation 方法是非常常见的需求。尽管可以在 methods 中轻松实现这点,但更好的方式是:methods 只有纯粹的数据逻辑,而不是去处理 DOM 事件细节。
为了解决这个问题,vue 为 v-on 提供了事件修饰符。通过由点 . 表示的指令后缀来调用修饰符。
常见的事件修饰符如下:
- .stop:阻止冒泡。
- .prevent:阻止默认事件。
- .capture:使用事件捕获模式。
- .self:只在当前元素本身触发。
- .once:只触发一次。
- .passive:默认行为将会立即触发。
按键修饰符
除了事件修饰符以外,在 vue 中还提供了有鼠标修饰符,键值修饰符,系统修饰符等功能。
- .left:左键
- .right:右键
- .middle:滚轮
- .enter:回车
- .tab:制表键
- .delete:捕获 “删除” 和 “退格” 键
- .esc:返回
- .space:空格
- .up:上
- .down:下
- .left:左
- .right:右
- .ctrl:ctrl 键
- .alt:alt 键
- .shift:shift 键
- .meta:meta 键
表单修饰符
vue 同样也为表单控件也提供了修饰符,常见的有 .lazy、.number 和 .trim。
- .lazy:在文本框失去焦点时才会渲染
- .number:将文本框中所输入的内容转换为number类型
- .trim:可以自动过滤输入首尾的空格
19. 如何实现 vue 项目中的性能优化?
参考答案:
编码阶段
- 尽量减少 data 中的数据,data 中的数据都会增加 getter 和 setter,会收集对应的 watcher
- v-if 和 v-for 不能连用
- 如果需要使用 v-for 给每项元素绑定事件时使用事件代理
- SPA 页面采用 keep-alive 缓存组件
- 在更多的情况下,使用 v-if 替代 v-show
- key 保证唯一
- 使用路由懒加载、异步组件
- 防抖、节流
- 第三方模块按需导入
- 长列表滚动到可视区域动态加载
- 图片懒加载
SEO 优化
- 预渲染
- 服务端渲染 SSR
打包优化
- 压缩代码
- Tree Shaking/Scope Hoisting
- 使用 cdn 加载第三方模块
- 多线程打包 happypack
- splitChunks 抽离公共文件
- sourceMap 优化
用户体验
- 骨架屏
- PWA
还可以使用缓存(客户端缓存、服务端缓存)优化、服务端开启 gzip 压缩等。
20. vue 中的 spa 应用如何优化首屏加载速度 ?
参考答案:
优化首屏加载可以从这几个方面开始:
- 请求优化:CDN 将第三方的类库放到 CDN 上,能够大幅度减少生产环境中的项目体积,另外 CDN 能够实时地根据网络流量和各节点的连接、负载状况以及到用户的距离和响应时间等综合信息将用户的请求重新导向离用户最近的服务节点上。
- 缓存:将长时间不会改变的第三方类库或者静态资源设置为强缓存,将 max-age 设置为一个非常长的时间,再将访问路径加上哈希达到哈希值变了以后保证获取到最新资源,好的缓存策略有助于减轻服务器的压力,并且显著的提升用户的体验
- gzip:开启 gzip 压缩,通常开启 gzip 压缩能够有效的缩小传输资源的大小。
- http2:如果系统首屏同一时间需要加载的静态资源非常多,但是浏览器对同域名的 tcp 连接数量是有限制的(chrome 为 6 个)超过规定数量的 tcp 连接,则必须要等到之前的请求收到响应后才能继续发送,而 http2 则可以在多个 tcp 连接中并发多个请求没有限制,在一些网络较差的环境开启 http2 性能提升尤为明显。
- 懒加载:当 url 匹配到相应的路径时,通过 import 动态加载页面组件,这样首屏的代码量会大幅减少,webpack 会把动态加载的页面组件分离成单独的一个 chunk.js 文件
- 预渲染:由于浏览器在渲染出页面之前,需要先加载和解析相应的 html、css 和 js 文件,为此会有一段白屏的时间,可以添加loading,或者骨架屏幕尽可能的减少白屏对用户的影响体积优化
- 合理使用第三方库:对于一些第三方 ui 框架、类库,尽量使用按需加载,减少打包体积
- 使用可视化工具分析打包后的模块体积:webpack-bundle- analyzer 这个插件在每次打包后能够更加直观的分析打包后模块的体积,再对其中比较大的模块进行优化
- 提高代码使用率:利用代码分割,将脚本中无需立即调用的代码在代码构建时转变为异步加载的过程
- 封装:构建良好的项目架构,按照项目需求就行全局组件,插件,过滤器,指令,utils 等做一 些公共封装,可以有效减少我们的代码量,而且更容易维护资源优化
- 图片懒加载:使用图片懒加载可以优化同一时间减少 http 请求开销,避免显示图片导致的画面抖动,提高用户体验
- 使用 svg 图标:相对于用一张图片来表示图标,svg 拥有更好的图片质量,体积更小,并且不需要开启额外的 http 请求
- 压缩图片:可以使用 image-webpack-loader,在用户肉眼分辨不清的情况下一定程度上压缩图片
21. vue 中相同逻辑如何进行抽离 ?
参考答案:
可以使用 vue 里面的混入(mixin)技术。混入(mixin)提供了一种非常灵活的方式,来将 vue 中相同的业务逻辑进行抽离。
例如:
- 在 data 中有很多是公用数据
- 引用封装好的组件也都是一样的
- methods、watch、computed 中也都有大量的重复代码
当然这个时候可以将所有的代码重复去写来实现功能,但是我们并不不推荐使用这种方式,无论是工作量、工作效率和后期维护来说都是不建议的,这个时候 mixin 就可以大展身手了。
一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。说白了就是给每个生命周期,函数等等中间加入一些公共逻辑。
混入技术特点
- 当组件和混入对象含有同名选项时,这些选项将以恰当的方式进行“合并”。比如,数据对象在内部会进行递归合并,并在发生冲突时以组件数据优先。
- 同名钩子函数将合并为一个数组,因此都将被调用。另外,混入对象的钩子将在组件自身钩子之前调用。
- 值为对象的选项,例如 methods、components 和 directives,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对。