Frequently Asked Questions on Interviews
前端性能优化
首屏优化的方法?(9个)
提升⾸屏的加载速度,是前端性能优化中「最重要」的环节,
1. 路由懒加载
1.1. 原因
SPA 项⽬,⼀个路由对应⼀个⻚⾯,如果不做处理,项⽬打包后,会把所有⻚⾯打包成⼀个 ⽂件,「当⽤户打开⾸⻚时,会⼀次性加载所有的资源」,造成⾸⻚加载很慢,降低⽤户体验
1.2. 原理
E S 6 的动态地加载模块— — i m p o r t ( )
w e b p a ck C h u n k N a m e 作用是 w e b p a ck 在打包的时候,对异步引入的库代码(l o d a s h ) 进行代码分割时,设置代码块的名字。w e b p a ck 会将任何一个异步模块与相同的块名称组合到相同的异步块中
1.2.3 代码示例
// 通过webpackChunkName设置分割后代码块的名字
const Home = () => import(/* webpackChunkName: "home" */ "@/views/home/index.vue");
const MetricGroup = () => import(/* webpackChunkName: "metricGroup" */ "@/views/metricGroup/index.vue");
…………
const routes = [
{
path: "/",
name: "home",
component: Home
},
{
path: "/metricGroup",
name: "metricGroup",
component: MetricGroup
},
…………
]
2. 组件懒加载
2.1. 使用场景
有时资源拆分的过细也不好,可能会造成浏览器 h t t p 请求的增多
- 该⻚面的 J S 文件体积大,导致⻚面打开慢,可以通过组件懒加载进行资源拆分,利用浏览器并行下载资源,提升下载速度(比如首⻚)
- 该组件不是一进入⻚面就展示,需要一定条件下才触发(比如弹框组件)
- 该组件复用性高,很多⻚面都有引入,利用组件懒加载抽离出该组件,一方面可以很好利用缓存,同时也可以减少⻚面的 J S 文件大小(比如表格组件、图形组 件等)
2.2. 一个例子:点击弹窗组件,该弹窗不是一进入页面就要加载而是需要用户手动触发了点击才展示
项目打包后,发现 h o m e . j s 和 a b o u t . j s 均包括了该弹框组件的代码(在 d i s t 文件中搜索d i a l o g I n f o 弹框组件)
2.3. 代码示例
<script>
const dialogInfo = () => import(/* webpackChunkName: "dialogInfo" */ '@/components/dialogInfo');
export default {
name: 'homeView',
components: {
dialogInfo
}
}
</script>
3. 合理使用Tree shaking
消除无用的 J S 代码,减少代码体积
3.1. 原理
依赖于 E S 6 的模块特性,E S 6 模块依赖关系是确定的,和运行时的状 态无关,可以进行可靠的静态分析,这就是 t r e e - s h a k i n g 的基础
静态分析就是不需要执行代码,就可以从字面量上对代码进行分析。E S 6 之前的模块化,比如 C o m m o n J S 是动态加载,只有执行后才知道引用的什么模块,就不能通过静态分析去做优化,正是基于这个基础上,才使得 t r e e - s h a k i n g 成为可能
3.2. Tree shaking 并不是万能的!!!
无法通过静态分析判断出一个对象的哪些变量未被使用,所以 t r e e - s h a k i n g 只对使用 e x p o r t 导出的变量生效
4.⻣架屏优化白屏时⻓
S PA 单⻚应用,无论 v u e 还是 r e a c t ,最初的 h t m l 都是空白的, 需要通过加载 J S 将内容挂载到根节点上,这套机制的副作用:会造成⻓时间的白屏
4.1. 骨架屏插件v u e - s k e l e t o n - w e b p a c k - p l u g i n
- 安装
n p m i v u e - s k e l e t o n - w e b p a c k - p l u g i n
- vue.config.js配置
// 骨架屏
const SkeletonWebpackPlugin = require("vue-skeleton-webpack-plugin");
module.exports = {
configureWebpack: {
plugins: [
new SkeletonWebpackPlugin({
// 实例化插件对象
webpackConfig: {
entry: {
app: path.join(__dirname, './src/skeleton.js') // 引入骨架屏入口文件
}
},
minimize: true, // SPA 下是否需要压缩注入 HTML 的 JS 代码
quiet: true, // 在服务端渲染时是否需要输出信息到控制台
router: {
mode: 'hash', // 路由模式
routes: [
// 不同页面可以配置不同骨架屏
// 对应路径所需要的骨架屏组件id,id的定义在入口文件内
{ path: /^\/home(?:\/)?/i, skeletonId: 'homeSkeleton' },
{ path: /^\/detail(?:\/)?/i, skeletonId: 'detailSkeleton' }
]
}
})
]
}
}
- 新建 skeleton.js 入口文件
// skeleton.js
import Vue from "vue";
// 引入对应的骨架屏页面
import homeSkeleton from "./views/homeSkeleton";
import detailSkeleton from "./views/detailSkeleton";
export default new Vue({
components: {
homeSkeleton,
detailSkeleton,
},
template: `
<div>
<homeSkeleton id="homeSkeleton" style="display:none;" />
<detailSkeleton id="detailSkeleton" style="display:none;" />
</div>
`,
});
5.⻓列表虚拟滚动
首⻚中不乏有需要渲染⻓列表的场景,当渲染条数过多时,所需要的渲染时间会很⻓,滚动时还会造成⻚面卡顿,整体体验非常不好
5.1. 定义
「虚拟滚动— — 指的是只渲染可视区域的列表项,非可⻅区域的」不渲染,在滚动时动态更新可视区域,该方案在优化大量数据渲染时效果是很明显的
5.2. 虚拟滚动基本原理
计算出 t o t a l H e i g h t 列表总高度,并在触发时滚动事件时根据 s c r o l l To p 值不断更新s t a r t I n d e x 以及 e n d I n d e x ,以 此从列表数据 l i s t D a t a 中截取对应元素
5.3. 虚拟滚动插件
虚拟滚动的插件有很多,比如 v u e - v i r t u a l - s c r o l l e r 、v u e - v i r t u a l - s c r o l l - l i s t 、r e a c t - t i ny v i r t u a l - l i s t 、r e a c t - v i r t u a l i z e d 等
5.4. vue-virtual-scroller
// 安装插件
npm install vue-virtual-scroller
// main.js
import VueVirtualScroller from 'vue-virtual-scroller'
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
Vue.use(VueVirtualScroller)
// 使用
<template>
<RecycleScroller
class="scroller"
:items="list"
:item-size="32"
key-field="id"
v-slot="{ item }">
<div class="user"> {{ item.name }} </div>
</RecycleScroller>
</template>
该插件主要有 RecycleScroller.vue、DynamicScroller.vue 这两个组件,其中 RecycleScroller 需要 item 的高度为静态的,也就是列表每个 item 的高度都是一致的,而 DynamicScroller 可以兼容 item 的高度为动态的情况
6.Web Worker 优化⻓任务
由于浏览器 G U I 渲染线程与 J S 引擎线程是互斥的关系,当⻚面中有很多⻓任务时,会造成⻚面 U I 阻塞,出现界面卡顿、掉帧等情况
6.1. 哪些是长任务?
打开控制台,选择 Pe r f o r m a n c e 工具,点击 S t a r t 按钮,展开 M a i n 选项,会发现有很多红色的三⻆,这些就属于⻓任务(⻓任务:执行时间超过 5 0 m s 的任务)
6.2. Web Worker 的通信时⻓
假如一个运算执行时⻓为 1 0 0 m s ,但是通信时⻓为 3 0 0 m s , 用了 We b Wo r k e r 可能会更慢
什么是通信时长?
比如新建一个 w e b wo r k e r, 浏 览器会加载对应的 wo r k e r. j s 资源,下图中的 Ti m e 是这个资源的通信时⻓(也叫加载时⻓)
6.3. 什么时候使用web worker?
当任务的运算时⻓ - 通信时⻓ > 5 0 m s ,推荐使用 We b Wo r k e r
6.4. 代码示例
let sum = 0;
for (let i = 0; i < 200000; i++) {
for (let i = 0; i < 10000; i++) {
sum += Math.random()
}
}
// worker.js
onmessage = function (e) {
// onmessage获取传入的初始值
let sum = e.data;
for (let i = 0; i < 200000; i++) {
for (let i = 0; i < 10000; i++) {
sum += Math.random()
}
}
// 将计算的结果传递出去
postMessage(sum);
}
7.requestAnimationFrame 制作动画
r e q u e s t A n i m a t i o n F r a m e 是浏览器专⻔为动画提供的 A P I ,它的刷新频率与显示器的频率保持一致,使用该 a p i 可以解决用 s e t Ti m e o u t / s e t I n t e r va l 制作动画卡顿的情况
- s e t Ti m e o u t / s e t I n t e r va l 属于 J S 引擎 ,r e q u e s t A n i m a t i o n Fra m e 属于 G U I 引擎J S 引擎与G U I 引擎 是互斥的,也就是说 G U I 引擎在渲染时会阻塞 J S 引擎的计算
- r e q u e s t A n i m a t i o n Fra m e 刷新频率是固定且准确的,但 s e t Ti m e o u t / s e t I n t e r va l 是宏任务,根据事件轮询机制,其他任务会阻塞或延迟 j s 任务的执行,会出现定时器不准的情况
- 当⻚面被隐藏或最小化时,s e t Ti m e o u t / s e t I n t e r va l 定时器仍会在后台执行动画任务,而使用 r e q u e s t A n i m a t i o n Fra m e 当⻚面处于未激活的状态下,屏幕刷新 任务会被系统暂停
8.JS 的 6 种加载方式
8.1. 正常模式
< s c r i p t s r c = " i n d e x . j s " > < / s c r i p t >
这种情况下 J S 会阻塞 d o m 渲染,浏览器必须等待 i n d e x . j s 加载和执行完成后才能去做其它事情
8.2. async 模式
< s c r i p t a s y n c s r c = " i n d e x . j s " > < / s c r i p t >
a s y n c 模式下,它的加载是异步 的,J S 不会阻塞 D O M 的渲染,a s y n c 加载是无顺序的, 当它加载结束,J S 会立即执行
使用场景:若该 J S 资源与 D O M 元素没有依赖关系,也不会产生其他资源所需要的数据时,可以使用 a s y n c 模式,比如埋点统计
8.3. defer 模式
< s c r i p t d e f e r s r c = " i n d e x . j s " > < / s c r i p t >
d e f e r 模式下,J S 的加载也是异步的,d e f e r 资源会在 D O M C o n t e n t L o a d e d 执行之前,并且 d e f e r 是有顺序的加载。如果有多个设置了 d e f e r 的 s c r i p t 标签存在,则会按照引入的前后顺序执行,即便是后面的 s c r i p t 资源先返回
使用场景:
一般情况下都可以使用 d e f e r ,特别是需要控制资源加载顺序时
8.3.1. defer和async的相同点和区别
8.3.1.1. Defer 和 async 的相同点
- 加载文件时不阻塞页面渲染
- 对于 inline 的 script 无效
- 使用这两个属性的脚本中不能调用 document.write 方法
- 有脚本的 onload 的事件回调
8.3.1.2. Defer 和 async 的区别
- html4.0中定义了defer;html5.0中定义了async
- 浏览器的支持不同
- 加载时机
- 具有 async 属性的脚本都在它下载结束之后立刻执行,同时会在 window 的 load 事件之前执行。所以就有可能出现脚本执行顺序被打乱的情况;
- 具有 defer 属性的脚本都是在页面解析完毕之后,按照原本的顺序执行,同时会在 document 的 DOMContentLoaded 之前执行。
页面的生命周期
DOMContentLoaded —— 浏览器已完全加载 HTML,并构建了 DOM 树,但像<img>
和样式表之类的外部资源可能尚未加载完成。
—DOM 已经就绪,因此处理程序可以查找 DOM 节点,并初始化接口。
load —— 浏览器不仅加载完成了 HTML,还加载完成了所有外部资源:图片,样式等。
—DOM 已经就绪,因此处理程序可以查找 DOM 节点,并初始化接口。
beforeunload/unload —— 当用户正在离开页面时。
- beforeunload 事件 —— 用户正在离开:我们可以检查用户是否保存了更改,并询问他是否真的要离开。
- unload 事件 —— 用户几乎已经离开了,但是我们仍然可以启动一些操作,例如发送统计数据。
8.4. module 模式
< s c r i p t t y p e = " m o d u l e " > i m p o r t { a } f r o m ' . / a . j s ' < / s c r i p t >
t y p e = " m o d u l e "
在主流的现代浏览器中,s c r i p t 标签的属性可以加上 t y p e = " m o d u l e " ,浏览器会对其内部的 i m p o r t 引用发起 H T T P 请求,获取模块内容。这时 s c r i p t 的行为会像是 d e f e r 一样,在后台下载,并且等待 D O M 解析Vi t e 就是利用浏览器支持原生的 e s m o d u l e 模块,开发时跳过打包的过程,提升编译效率
8.5. preload
< l i n k r e l = " p r e l o a d " a s = " s c r i p t " h r e f = " i n d e x . j s " >
8.5.1. 定义
关键字 preload 作为元素 的属性 rel 的值,表示用户十分有可能需要在当前浏览中加载目标资源,所以浏览器必须预先获取和缓存对应资源。(mdn)
8.5.2. 特点
- p r e l o a d 加载的资源是在浏览器渲染机制之前进行处理的,并且不会阻塞 o n l o a d 事件;
- p r e l o a d 加载的 J S 脚本其加载和执行的过程是分离的,即 p r e l o a d 会预加载相应的脚本代码,待到需要时自行调用;
8.6. prefetch
< l i n k r e l = " p r e f e t c h " a s = " s c r i p t " h r e f = " i n d e x . j s " >
8.6.1. 定义
prefetch是利用浏览器的空闲时 间,加载⻚面将来可能用到的资源的一种机制;通常可以用于加载其他⻚面(非首⻚)所需要的资源,以便加快后续⻚面的打开速度
8.6.2. 特点
- pretch加载的资源可以获取非当前⻚面所需要的资源,并且将其放入缓存至少 5 分钟(无论资源是否可以缓存)
- 当⻚面跳转时,未完成的 prefetch请求不会被中断
8.7. 加载方式总结
async、defer是script标签的专属属性,对于网⻚中的其他资源,可以通过 l i n k 的p r e l o a d 、p r e f e t ch 属性来预加 载如今现代框架已经将 p r e l o a d 、p r e f e t ch 添加到打包流程中了,通过灵活的配置,去使用这些预加载功能,同时我们也可以审时度势地向 s c r i p t 标签添加 a s y n c 、d e f e r 属性去处理资源,这样可以显著提升性能
9.图片的优化
9.1. 图片的动态裁剪
很多云服务,比如阿里云或七牛云,都提供了图片的动态裁剪功能,效果很棒,确实是钱没有白花只需在图片的 u r l 地址上动态添加参数,就可以得到你所需要的尺寸大小,比如: h t t p : / / 7 x k v 1 q . c o m 1 . z 0 . g l b . c l o u d d n . c o m / g r a p e . j p g ? i m a g e V i e w 2 / 1 / w / 2 0 0 / h / 2 0 0
9.2. 图片的懒加载
对于一些图片量比较大的首⻚,用户打开⻚面后,只需要呈现出在屏幕可视区域内的图片,当用户滑动⻚面时,再去加载出现在屏幕内的图片,以优化图片的加载效果
9.3. 实现原理
由于浏览器会自动对⻚面中的 i m g 标签的 s rc 属性发送请求并下载 图片,可以通过 h t m l 5 自定义属性 d a t a - x x x 先暂存 s rc 的值,然后在图片出现在屏幕可视区域的时候,再将d a t a - x x x 的值重新赋值到 i m g 的 s rc 属性即可
<img src="" alt="" data-src="./images/1.jpg">
<img src="" alt="" data-src="./images/2.jpg">
9.4. 插件示例vue-lazyload
// 安装
npm install vue-lazyload
// main.js 注册
import VueLazyload from 'vue-lazyload'
Vue.use(VueLazyload)
// 配置项
Vue.use(VueLazyload, {
preLoad: 1.3,
error: 'dist/error.png', // 图片加载失败时的占位图
loading: 'dist/loading.gif', // 图片加载中时的占位图
attempt: 1
})
// 通过 v-lazy 指令使用
<ul>
<li v-for="img in list">
<img v-lazy="img.src" :key="img.src" >
</li>
</ul>
9.5. 使用字体图标
字体图标是⻚面使用小图标的不二选择,最常用的就是 i c o n f o n t
- 轻量级:一个图标字体要比一系列的图像要小。一旦字体加载了,图标就会⻢上渲染出来,减少了 h t t p 请求
- 灵活性:可以随意的改变颜色、产生阴影、透明效果、旋转等
- 兼容性:几乎支持所有的浏览器,请放心使用
9.6. 图片转 base64 格式
将小图片转换为 b a s e 6 4 编码字符串,并写入 H T M L 或者 C S S 中 ,减少 h t t p 请求
9.6.1. 优缺点
- 它处理的往往是非常小的图片,因为 B a s e 6 4 编码后,图片大小 会膨胀为原文件的4 / 3 ,如果对大图也使用 B a s e 6 4 编码,后者的体积会明显增加,即便减少了 h t t p 请求,也无法弥补这庞大的体积带来的性能开销,得不偿失
- 在传输非常小的图片的时候,B a s e 6 4 带来的文件体积膨胀、以及 浏览器解析 B a s e 6 4 的时间开销,与它节省掉的 h t t p 请求开销相比,可以忽略不计,这时候才能真正体现出它在性能方面的优势
9.6.2. 插件示例url-loader
// 安装
npm install url-loader --save-dev
// 配置
module.exports = {
module: {
rules: [{
test: /.(png|jpg|gif)$/i,
use: [{
loader: 'url-loader',
options: {
// 小于 10kb 的图片转化为 base64
limit: 1024 * 10
}
}]
}]
}
};
jQuery
jquery选择器分类及相应的写法?
1. 基本选择器
1.1. ID选择器
$(‘#app’)
1.2. 类选择器
$(‘.class’)
1.3. 元素选择器
$(‘h1’)
1.4. *
匹配所有元素
$(‘*’)
1.5. 并集选择器
2. 层次选择器
2.1. 后代选择器
2.2. 子选择器
2.3. 同辈选择器
2.4. 同辈选择器
3. 过滤选择器
3.1. 基本过滤选择器
3.2. 内容过滤选择器
3.3. 可见性过滤选择器
3.4. 属性过滤选择器
3.5. 表单属性过滤选择器
4. 表单选择器
浏览器
当我们在 web 浏览器的地址栏中输入:www.baidu.com,具体发生了什么?
- 对www.baidu.com这个网址进行DNS域名解析,得到对应的IP地址
- 根据这个 IP,找到对应的服务器,发起 TCP 的三次握手
- 建立 TCP 连接后, 发起 HTTP 请求
- 服务器响应 HTTP 请求,浏览器得到 html 代码
- 浏览器解析 html 代码,并请求 html 代码中的资源(如 js、css、图片等)(先得到 html 代码,才能去找这些资源)
- 服务器响应对应的资源
- 响应数据完毕, 四次挥手,关闭 TCP 连接
- 浏览器对页面进行渲染呈现给用户
浏览器会对输入的信息进行以下判断:
- 检查输入的内容是否是一个合法的 URL 链接。
- 是,则判断输入的 URL 是否完整。如果不完整,浏览器可能会对域名进行猜测,补全前缀或者后缀。
- 否,将输入内容作为搜索条件,使用用户设置的默认搜索引擎来进行搜索。大部分浏览器会从历史记录、书签等地方开始查找我们输入的网址,并给出智能提示。
TCP和UDP的区别
- 连接方式:TCP是面向连接的协议,UDP是无连接的协议
- 可靠性:TCP提供可靠的传输,保证数据的完整性和顺序性,而UDP不保证数据的完整性和顺序性
- 速度:UDP比TCP更快,因为它不需要建立连接和维护连接状态
- 传输方式:TCP是基于字节流的传输方式,UDP是基于数据报的传输方式
TCP协议的工作流程如下:
- 客户端向服务器发送连接请求(SYN)
- 服务器收到连接请求后,回复确认请求(SYN+ACK)
- 客户端收到确认请求后,回复确认请求(ACK),完成连接
- 数据传输完成后,客户端和服务器分别发送关闭连接请求(FIN)
- 对方收到关闭请求后,回复确认(ACK)
- 双方都收到对方的关闭请求和确认后,关闭连接
UDP协议的工作流程如下
- 客户端向服务器发送数据报
- 服务器收到数据报后,直接处理数据并回复确认
- 客户端收到确认后,继续发送下一个数据报
- 如果数据报丢失或损坏,客户端不会重传,而是直接忽略
tcp和udp的适用场景?
- TCP适用于可靠传输的场景,如文件传输、电子邮件传输等
- UDP适用于实时性要求高、数据量小、丢失数据不会影响结果的场景,如视频直播、语音通话等
CSS
visibility和display的区别
- visibility:hidden可以隐藏某个元素,但隐藏的元素仍需占用与未隐藏之前一样的空间。也就是说,该元素虽然被隐藏了,但仍然会影响布局。
- display:none可以隐藏某个元素,且隐藏的元素不会占用任何空间。也就是说,该元素不但被隐藏了,而且该元素原本占用的空间也会从页面布局中消失。
object-fit
object-fit属性
object-fit CSS 属性指定可替换元素(例如:
<img>
或<video>
)的内容应该如何适应到其使用高度和宽度确定的框。
原图:
-
object-fit:fill
:被替换的内容正好填充元素的内容框。整个对象将完全填充此框。如果对象的宽高比与内容框不相匹配,那么该对象将被拉伸以适应内容框。
-
object-fit:contain
:被替换的内容将被缩放,以在填充元素的内容框时保持其宽高比。整个对象在填充盒子的同时保留其长宽比,因此如果宽高比与框的宽高比不匹配,该对象将被添加“黑边”。(长边全部显示)
-
object-fit:cover
:被替换的内容在保持其宽高比的同时填充元素的整个内容框。如果对象的宽高比与内容框不相匹配,该对象将被剪裁以适应内容框。(短边全部显示,短边会占满容器!)
-
object-fit:none
:被替换的内容将保持其原有的尺寸。(原本的分辨率显示)
-
object-fit:scale-down
:内容的尺寸与 none 或 contain 中的一个相同,取决于它们两个之间谁得到的对象尺寸会更小一些。
-
总结,fill拉伸填充,contain显示全整个图像,适应长边,cover适应短边可能会被裁剪,none显示原图像(像素比例不变,也不会被拉伸,但是可能会显示不全),scale-down取决于contain和none其中取得尺寸更小的那个属性(down!down!down!)
webpack
webpack 是一个用于现代 JavaScript 应用程序的「静态模块打包工具」。我们可以使用webpack管理模块。因为在webpack看来,项目中的所有资源皆为模块,通过分析模块间的依赖关系,在其内部构建出一个依赖图,最终编绎输出模块为 HTML、JavaScript、CSS 以及各种静态文件(图片、字体等),让我们的开发过程更加高效。
webpack的作用?
- 模块打包。可以将不同模块的文件打包整合在一起,并且保证它们之间的引用正确,执行有序。利用打包我们就可以在开发的时候根据我们自己的业务自由划分文件模块,保证项目结构的清晰和可读性。
- 编译兼容。在前端的“上古时期”,手写一堆浏览器兼容代码一直是令前端工程师头皮发麻的事情,而在今天这个问题被大大的弱化了,通过webpack的Loader机制,不仅仅可以帮助我们对代码做polyfill,还可以编译转换诸如.less,.vue,.jsx这类在浏览器无法识别的格式文件,让我们在开发的时候可以使用新特性和新语法做开发,提高开发效率。
- 能力扩展。通过webpack的Plugin机制,我们在实现模块化打包和编译兼容的基础上,可以进一步实现诸如按需加载,代码压缩等一系列功能,帮助我们进一步提高自动化程度,工程效率以及打包输出的质量。
webpack的构建流程
webpack的运行流程是一个串行的过程,从启动到结束会依次执行以下流程:
- 初始化参数:从配置文件和 Shell 语句中读取与合并参数,得出最终的参数
- 开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 方法开始执行编译
- 确定入口:根据配置中的 entry 找出所有的入口文件
- 编译模块:从入口文件出发,调用所有配置的 loader 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理
- 完成模块编译:在经过上一步使用 loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系
- 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会
- 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统
在以上过程中,webpack会在特定的时间点广播出特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,并且插件可以调用webpack提供的 API 改变webpack的运行结果。
简单说:
- 初始化:启动构建,读取与合并配置参数,加载 Plugin,实例化 Compiler
- 编译:从 entry 出发,针对每个 Module 串行调用对应的 loader 去翻译文件的内容,再找到该 Module 依赖的 Module,递归地进行编译处理
- 输出:将编译后的 Module 组合成 Chunk,将 Chunk 转换成文件,输出到文件系统中
常见的loader?
默认情况下,webpack只支持对js和json文件进行打包,但是像css、html、png等其他类型的文件,webpack则无能为力。因此,就需要配置相应的loader进行文件内容的解析转换。
常用的loader如下:
- image-loader:加载并且压缩图片文件。
- less-loader:加载并编译 LESS 文件。
- sass-loader:加载并编译 SASS/SCSS 文件。
- css-loader:加载 CSS,支持模块化、压缩、文件导入等特性,使用css-loader必须要配合使用style-loader。
- style-loader:用于将 CSS 编译完成的样式,挂载到页面的 style 标签上。需要注意 loader 执行顺序,style-loader 要放在第一位,loader 都是从后往前执行。
- babel-loader:把 ES6 转换成 ES5
- postcss-loader:扩展 CSS 语法,使用下一代 CSS,可以配合 autoprefixer 插件自动补齐 CSS3 前缀。
- eslint-loader:通过 ESLint 检查 JavaScript 代码。
- vue-loader:加载并编译 Vue 组件。
- file-loader:把文件输出到一个文件夹中,在代码中通过相对 URL 去引用输出的文件 (处理图片和字体)
- url-loader:与 file-loader 类似,区别是用户可以设置一个阈值,大于阈值会交给
- file-loader 处理,小于阈值时返回文件 base64 形式编码 (处理图片和字体)
常见的plugin?
webpack中的plugin赋予其各种灵活的功能,例如打包优化、资源管理、环境变量注入等,它们会运行在webpack的不同阶段(钩子 / 生命周期),贯穿了webpack整个编译周期。目的在于「解决 loader 无法实现的其他事」。
常用的plugin如下:
- HtmlWebpackPlugin:简化 HTML 文件创建 (依赖于 html-loader)
- mini-css-extract-plugin: 分离样式文件,CSS 提取为独立文件,支持按需加载 (替代 extract-text-webpack-plugin)
- clean-webpack-plugin: 目录清理
loader 和 plugin 的区别?
- loader是文件加载器,能够加载资源文件,并对这些文件进行一些处理,诸如编译、压缩等,最终一起打包到指定的文件中;plugin赋予了webpack各种灵活的功能,例如打包优化、资源管理、环境变量注入等,目的是解决 loader无法实现的其他事。
- 在运行时机上,loader 运行在打包文件之前;plugin则是在整个编译周期都起作用。
- 在配置上,loader在module.rules中配置,作为模块的解析规则,类型为数组。每一项都是一个 Object,内部包含了 test(类型文件)、loader、options (参数)等属性;plugin在 plugins中单独配置,类型为数组,每一项是一个 plugin 的实例,参数都通过构造函数传入。
webpack 的热更新原理是?
模块热替换(HMR - hot module replacement),又叫做热更新,在不需要刷新整个页面的同时更新模块,能够提升开发的效率和体验。热更新时只会局部刷新页面上发生了变化的模块,同时可以保留当前页面的状态,比如复选框的选中状态等。
热更新的核心就是客户端从服务端拉去更新后的文件,准确的说是 chunk diff (chunk 需要更新的部分),实际上webpack-dev-server与浏览器之间维护了一个websocket,当本地资源发生变化时,webpack-dev-server会向浏览器推送更新,并带上构建时的hash,让客户端与上一次资源进行对比。客户端对比出差异后会向webpack-dev-server发起 Ajax 请求来获取更改内容(文件列表、hash),这样客户端就可以再借助这些信息继续向webpack-dev-server发起 jsonp 请求获取该chunk的增量更新。
后续的部分(拿到增量更新之后如何处理?哪些状态该保留?哪些又需要更新?)由HotModulePlugin 来完成,提供了相关 API 以供开发者针对自身场景进行处理,像react-hot-loader和vue-loader都是借助这些 API 实现热更新。
如何提高 webpack 的构建速度?
- 代码压缩
- JS 压缩
webpack 4.0默认在生产环境的时候是支持代码压缩的,即mode=production模式下。实际上webpack 4.0默认是使用terser-webpack-plugin这个压缩插件,在此之前是使用 uglifyjs-webpack-plugin,两者的区别是后者对 ES6 的压缩不是很好,同时我们可以开启 parallel参数,使用多进程压缩,加快压缩。 - CSS 压缩
CSS 压缩通常是去除无用的空格等,因为很难去修改选择器、属性的名称、值等。可以使用另外一个插件:css-minimizer-webpack-plugin。 - HTML 压缩
使用HtmlWebpackPlugin插件来生成 HTML 的模板时候,通过配置属性minify进行 html 优化。
- JS 压缩
module.exports = {
plugin:[
new HtmlwebpackPlugin({
minify:{
minifyCSS: false, // 是否压缩css
collapseWhitespace: false, // 是否折叠空格
removeComments: true // 是否移除注释
}
})
]
}
-
图片压缩
配置image-webpack-loader -
Tree Shaking
Tree Shaking是一个术语,在计算机中表示消除死代码,依赖于 ES Module 的静态语法分析(不执行任何的代码,可以明确知道模块的依赖关系)。在webpack实现Tree shaking有两种方案:
usedExports:通过标记某些函数是否被使用,之后通过 Terser 来进行优化的
module.exports = {
...
optimization:{
usedExports
}
}
使用之后,没被用上的代码在webpack打包中会加入unused harmony export mul注释,用来告知Terser在优化时,可以删除掉这段代码。
sideEffects:跳过整个模块/文件,直接查看该文件是否有副作用
sideEffects用于告知webpack compiler哪些模块时有副作用,配置方法是在package.json中设置sideEffects属性。如果sideEffects设置为false,就是告知webpack可以安全的删除未用到的exports。如果有些文件需要保留,可以设置为数组的形式,如:
"sideEffecis":[
"./src/util/format.js",
"*.css" // 所有的css文件
]
- 缩小打包域
排除webpack不需要解析的模块,即在使用loader的时候,在尽量少的模块中去使用。可以借助 include和exclude这两个参数,规定loader只在那些模块应用和在哪些模块不应用。 - 减少 ES6 转为 ES5 的冗余代码
使用bable-plugin-transform-runtime插件 - 提取公共代码
通过配置CommonsChunkPlugin插件,将多个页面的公共代码抽离成单独的文件
webpack版本号(package.json中版本的定义)
- 1.2.1
指定版本,限定只使用 1.2.1 版本 - ^1.0.0
可使用 >=1.0.0 且 <2.0.0 的版本
意义:与指定版本相容的版本
作用:次前缀最左边的非0版本号不允许改变,之后的版本号可为更高的版本,例如:
^1.1.0 可使用 >=1.1.0 且 <2.0.0
^0.0.3 可使用 >=0.0.3 且 <0.0.4 - latest
当前发布的最新版本
这是一个通用标记,详情参考dist-tag官方文件,使用npm install所安装的就是标记latest的版本(默认情况)
常见标记有:next、stable、beta、canary - ^5.x
可使用 >=5.0.0 且 <6.0.0 的版本
- 可使用 >=0.0.0
1 可使用 >=1.0.0 且 <2.0.0
1.2 可使用 >=1.2.0 且 <1.3.0
- ~0.1.1
可使用 >=0.1.1 且 <0.2.0 的版本
~ 意义:约定于这个版本
~ 作用:如果有次版本号,则允许修订为更高的版本,否则允许次版本号为更高的版本,例如:
~1 可使用 >=1.0.0 且 <2.0.0 >=
3.0.0
指定基础版本,可使用 3.0.0 以上版本
其他操作符有:<、<=、>、>=、=
可使用空格表示 AND,|| 表示 OR,例如:
1.2.7 || >=1.2.9 <2.0.0 表示可包含 1.2.7、1.2.9 和 1.4.6,不可包含 1.2.8 或 2.0.0
1.30.2 - 2.30.2
可使用 >=1.30.2 且 <=2.30.2
如果尾部有缺少版本段,先被替换成 0后再进行对比,例如:
1.30 - 2.30.2 同 1.30.0 - 2.30.2- git://github.com/user/project.git#commit-ish
以 Git URL 的形式
Typescript
type 和 interface
type 和 interface 的相同点
- 都可以描述一个对象或者函数
interface User {
name: string
age: number
}
interface SetUser {
(name: string, age: number): void;
}
type User = {
name: string
age: number
};
type SetUser = (name: string, age: number)=> void;
- 都允许拓展(extends)
interface 和 type 都可以拓展,并且两者并不是相互独立的,也就是说 interface 可以 extends type, type 也可以 extends interface 。 虽然效果差不多,但是两者语法不同。
// interface extends interface
interface Name {
name: string;
}
interface User extends Name {
age: number;
}
// type extends type
type Name = {
name: string;
}
type User = Name & { age: number };
// interface extends type
type Name = {
name: string;
}
interface User extends Name {
age: number;
}
// type extends interface
interface Name {
name: string;
}
type User = Name & {
age: number;
}
type 和 interface 的不同点
- type 可以而 interface 不行
- type 可以声明基本类型别名,联合类型,元组等类型
// 基本类型别名
type Name = string
// 联合类型
interface Dog {
wong();
}
interface Cat {
miao();
}
type Pet = Dog | Cat
// 具体定义数组每个位置的类型
type PetList = [Dog, Pet]
- type 语句中还可以使用 typeof 获取实例的 类型进行赋值
// 当你想获取一个变量的类型时,使用 typeof
let div = document.createElement('div');
type B = typeof div
- interface 可以而 type 不行
- interface 能够声明合并
interface User {
name: string
age: number
}
interface User {
sex: string
}
/*
User 接口为 {
name: string
age: number
sex: string
}
*/