web前端面试题之webpack和其他
- webpack前端性能优化
- webpack前端性能优化——包体积压缩82%、打包速度提升65%
1.externals 提取公共依赖包,可以使用 externals 来提取这些依赖包,打包时可以忽略它 然后在 index.html 中使用 CDN 引入依赖
<body> <script src="http://lib.baomitu.com/vue/2.6.14/vue.min.js"></script> <script src="http://lib.baomitu.com/vue-router/3.5.1/vue-router.min.js"></script> <script src="http://lib.baomitu.com/axios/1.2.1/axios.min.js"></script> <script src="http://lib.baomitu.com/echarts/5.3.2/echarts.min.js"></script> </body>复制代码
2、组件库的按需引入
为什么没有使用 externals 的方式处理组件库呢?
externals缺点:直接在html内引入的,失去了按需引入的功能,只能引入组件库完整的js和css
组件库按需引入的原理:最终只引入指定组件和对应的样式
elementUI 需要借助 babel-plugin-component[1] 插件实现,插件的作用如下:
通过该插件,最终只引入指定组件和样式,来实现减少组件库体积大小
减小三方依赖的体积【很多没有用到的语言包】
使用 moment-locales-webpack-plugin 插件,剔除掉无用的语言包
HappyPack 多线程打包
HappyPack 就能实现多线程打包,它把任务分解给多个子进程去并发的执行,子进程处理完后再把结果发送给主进程,来提升打包速度
Gzip压缩
html、js、css资源,使用 gzip 后通常可以将体积压缩70%以上
这里介绍下使用 webpack 进行 gzip 压缩的方式,使用 compression-webpack-plugin 插件
- 如何提高构建速度
功能和业务代码也会随着越多,相应的 webpack 的构建时间也会越来越久,构建时间与我们日常开发效率密切相关,当我们本地开发启动 devServer 或者 build 的时候,如果时间过长,会大大降低我们的工作效率,
常见的提升构建速度的手段有如下:
- 优化 loader 配置
- 合理使用 resolve.extensions
- 优化 resolve.modules
- 优化 resolve.alias
- 使用 DLLPlugin 插件
- 使用 cache-loader
- terser 启动多线程
- 合理使用 sourceMap
优化loader配置
在使用loader时,可以通过配置include、exclude、test属性来匹配部分文件,
module.exports = {
module: {
rules: [
{
// 如果项目源码中只有 js 文件就不要写成 /\.jsx?$/,提升正则表达式性能
test: /\.js$/,
// babel-loader 支持缓存转换出的结果,通过 cacheDirectory 选项开启
use: ['babel-loader?cacheDirectory'],
// 只对项目根目录下的 src 目录中的文件采用 babel-loader
include: path.resolve(__dirname, 'src'),
},
]
},
};
合理使用 resolve.extensions
通过resolve.extensions是解析到文件时自动添加拓展名
module.exports = {
...
extensions:[".warm",".mjs",".js",".json"]
}
当我们配置的时候,则不要随便把所有后缀都写在里面,这会调用多次文件的查找,这样就会减慢打包速度
优化 resolve.alias
alias给一些常用的路径起一个别名,特别当我们的项目目录结构比较深的时候,一个文件的路径可能是./../../的形式
通过配置alias以减少查找过程
module.exports = {
...
resolve:{
alias:{
"@":path.resolve(__dirname,'./src')
}
}
}
使用 cache-loader
在一些性能开销较大的 loader 之前添加 cache-loader,以将结果缓存到磁盘里,显著提升二次构建速度
保存和读取这些缓存文件会有一些时间开销,所以请只对性能开销较大的 loader 使用此 loader
module.exports = {
module: {
rules: [
{
test: /\.ext$/,
use: ['cache-loader', ...loaders],
include: path.resolve('src'),
},
],
},
};
terser 启动多线程
module.exports = {
optimization: {
minimizer: [
new TerserPlugin({
parallel: true,
}),
],
},
};
合理使用 sourceMap
打包生成 sourceMap 的时候,如果信息越详细,打包速度就会越慢。对应属性取值如下所示:
总结
可以看到,优化webpack构建的方式有很多,主要可以从优化搜索时间、缩小文件搜索范围、减少不必要的编译等方面入手
3.webpack的热更新
HMR 全称 Hot Module Replacement,可以理解为模块热替换,指在应用程序运行过程中,替换、添加、删除模块,而无需重新刷新整个应用
例如,我们在应用运行过程中修改了某个模块,通过自动刷新会导致整个应用的整体刷新,那页面中的状态信息都会丢失
如果使用的是 HMR,就可以实现只将修改的模块实时替换至应用中,不必完全刷新整个应用
在webpack中配置开启热模块也非常的简单,如下代码:
const webpack = require('webpack')
module.exports = {
// ...
devServer: {
// 开启 HMR 特性
hot: true
// hotOnly: true
}
}
通过上述这种配置,如果我们修改并保存css文件,确实能够以不刷新的形式更新到页面中
但是,当我们修改并保存js文件之后,页面依旧自动刷新了,这里并没有触发热模块
所以,HMR 并不像 Webpack 的其他特性一样可以开箱即用,需要有一些额外的操作
我们需要去指定哪些模块发生更新时进行HRM,如下代码:
if(module.hot){
module.hot.accept('./util.js',()=>{
console.log("util.js更新了")
})
}
Loader与Plugin对应的概
- loader 是文件加载器,能够加载资源文件,并对这些文件进行一些处理,诸如编译、压缩等,最终一起打包到指定的文件中
- plugin 赋予了 webpack 各种灵活的功能,例如打包优化、资源管理、环境变量注入等,目的是解决 loader 无法实现的其他事
常见的babel和plugin
下面介绍几个常用的插件用法:
HtmlWebpackPlugin
在打包结束后,⾃动生成⼀个 html ⽂文件,并把打包生成的js 模块引⼊到该 html 中
clean-webpack-plugin
删除(清理)构建目录
mini-css-extract-plugin
提取 CSS 到一个单独的文件中
前端安全
token是什么
token一般存放在localstorage中,每次请求接口都会带上
封装一个ajax接口都要配置什么
timeout: 8000,
url: '/api/pay/v1/queryByOrderNo',
method: 'POST',
data: data
headers: {'Content-Type': 'application/x-www-form-urlencoded', token: token,}
Content-Type:来告诉服务端如何处理请求的数据
说说 JavaScript 数字精度丢失的问题,解决方案?
解决方式:把小数放到位整数(乘倍数),再缩小回原来倍数(除倍数)
// 0.1 + 0.2
(0.1*10 + 0.2*10) / 10 == 0.3 // true
如何给localStorage设置一个过期时间?
,localstorage原生是不支持设置过期时间的,想要设置的话,就只能自己来封装一层逻辑来实现:
function set(key,value){
var curtime = new Date().getTime();//获取当前时间
localStorage.setItem(key,JSON.stringify({val:value,time:curtime}));//转换成json字符串序列
}
function get(key,exp)//exp是设置的过期时间
{
var val = localStorage.getItem(key);//获取存储的元素
var dataobj = JSON.parse(val);//解析出json对象
if(new Date().getTime() - dataobj.time > exp)//如果当前时间-减去存储的元素在创建时候设置的时间 > 过期时间
{
console.log("expires");//提示过期
}
else{
console.log("val="+dataobj.val);
}
}
———————————————————————————————————————————
大文件上传-分片
说说大文件分片和断点续传
如果上传文件过大,比如500M 1G 2G 那么大,可能上传超时和服务器的限制。
解决上面的问题可以用到分片上传:,每次只上传很小的一部分 比如2M,多上传几次就可以啦。
File
继承了Blob
的功能,Blob
对象表示原始数据,也就是二进制数据,它提供了对数据截取的方法slice
思路如下;
-
把大文件进行使用
slice
分段,比如分段成每个2M,发送到服务器携带一个标志,这里暂时用当前的时间戳,用于标识一个完整的文件 -
服务端保存各段文件
-
浏览器端所有分片上传完成,发送给服务端一个合并文件的请求
-
服务端根据文件标识、类型、各分片顺序进行文件合并
-
删除分片文件
---------------------------------------------------------------------------------------------------------------------------------
大文件上传-断点续传
分片上传的方式并不完美,因为大文件上传并不是短时间内就上传完成,如果期间断网,页面刷新了仍然需要重头上传
所以现在要解决的问题是:如何检测这些分片,不再重新上传即可。
方案;【上传的时候和本地保存的已经上传的分片的hash值做比对,来解决重复上传的问题】
-
为每个分段生成 hash 值,使用
spark-md5
三方模块 -
将上传成功的分段信息保存到
localStorage【
保存在客户端是最不保险的,最好让服务端增加一个接口。】
-
重新上传时,进行和本地分段 hash 值的对比,如果相同的话则跳过,继续下一个分段的上传
PS: 生成 hash 过程肯定也会耗费资源,但是和重新上传相比可以忽略不计了。
---------------------------------------------------------------------------------------------------------------------------------
JavaScript 中如何实现并发控制?【未完】
JavaScript 中如何实现并发控制?
async function asyncPool(poolLimit, array, iteratorFn) {
const ret = []; // 存储所有的异步任务
const executing = []; // 存储正在执行的异步任务
for (const item of array) {
// 调用iteratorFn函数创建异步任务
const p = Promise.resolve().then(() => iteratorFn(item, array));
ret.push(p); // 保存新的异步任务
// 当poolLimit值小于或等于总任务个数时,进行并发控制
if (poolLimit <= array.length) {
// 当任务完成后,从正在执行的任务数组中移除已完成的任务
const e = p.then(() => executing.splice(executing.indexOf(e), 1));
executing.push(e); // 保存正在执行的异步任务
if (executing.length >= poolLimit) {
await Promise.race(executing); // 等待较快的任务执行完成
}
}
}
return Promise.all(ret);
}
1.使用promise.race(),等待较快的任务执行完成
2.当每完成一个进行中的任务时,就移除已完成的任务
3.最后return Promise.all
---------------------------------------------------------------------------------------------------------------------------------
并发
并发:代表计算机能够同时执行多项任务
并行:对于多核处理器,可以在不同的核心上真正并行的执行任务
---------------------------------------------------------------------------------------------------------------------------------
JS while 循环的语法格式如下:
while (条件表达式) {
// 要执行的代码
}
---------------------------------------------------------------------------------------------------------------------------------
JavaScript 中如何实现异步串行?
async/await
!号称JS异步的终极解决方案,真不是盖的,下面就来看看async/await
的方案,
一个for循环解决串行异步问题,没有回调没有嵌套看起来瞬间舒服多了~
var urlArr = ['http://www.qq.com','http://www.qq.com','http://www.qq.com'];
function makeRequest(url){
return new Promise((resolve,reject)=>{
$.get(url).success((ret)=>{
resolve(ret)
}).fail(()=>{
reject()
})
})
}
async function makaPromiseList(dataArr,handler){
var result = [];
for(let item of dataArr){
var ret = await handler(item);
result.push(ret);
}
return result;
}
makaPromiseList(urlArr,makeRequest).then((ret)=>{
console.log(ret)
});
---------------------------------------------------------------------------------------------------------------------------------