目录
[3.1、自动移除代码中所有的 console.* 调用](#3.1、自动移除代码中所有的 console.* 调用)
一、图片处理
1.1、列表页用缩略图
注意:为什么要用「后端缩略图」?
(1)、前端直接压缩会卡顿、耗性能
(2)、后端生成缩略图体积小 5~10 倍
(3)、列表加载速度提升非常明显
(4)、节省用户流量
**实际应用:**后端返回的列表数组中图片原始大小( 见图2的Intrinsic size)可能很大,但是前端展示时( 见图2的Rendered size)只要很小,这样页面在渲染时不至于在上百条数据展示时页面卡死。

页面渲染:
html
<img :src="setimgUrl(item.img, { w: 300, hasWidth: true })" />
原图:https://test.com/a.jpg ===> 处理后:https://test.com/a-300.jpg
javascript
// 在main.js封装的全局方法:用于处理后端缩略图、拼接域名
Vue.prototype.setimgUrl = (url, options = {}) => {
const { w, h, hasWidth, ...others } = options
let query = ''
if (w) {
query += query ? '&w=' : 'w=' + _.floor(_.multiply(w))
}
if (h) {
query += query ? '&h=' : 'h=' + _.floor(_.multiply(h))
}
for (const key in others) {
query += `${query ? '&' : ''}${key}=${options[key]}`
}
if (!url) return ''
if (/((http|https):)?\/\/([^/]+)/i.test(url)) {
if (
(url.startsWith('https://test') || url.includes('custom.test.com')) &&
!url.startsWith('https://test-psd') && hasWidth ) {
let resultUrl = url.replace(/\.(png|jpg|jpeg)$/i, `-${w}.$1`)
return resultUrl
} else if (url.startsWith('https://test-psd') && hasWidth) {
// 如果是 psd 测试图片(阿里云 OSS)图片处理规则,直接拼接:?x-oss-process=image/resize,m_fixed,w_100
return url + `?x-oss-process=image/resize,m_fixed,w_${w}`
} else {
return url
}
// 图片是MD5的形式
} else if (url.indexOf('/') === -1) {
return imagePath + url + (query ? '?' : '') + query
} else {
return filePath + url
}
}
这是一个Vue 全局图片处理函数:(本质)项目里所有图片 URL 的中央处理器
(1)、自动生成缩略图 URL(按后端规则拼接宽度)
(2)、自动补全图片域名 / 路径
(3)、兼容多种图片来源,统一格式输出
1.2、批量校验尺寸
扩展:****naturalWidth是HTML<img> 元素的一个只读属性,表示图像的原始固有宽度****(单位是 CSS 像素),这个宽度不受 CSS 样式(如 width、max-width 或 CSS 缩放)的影响。见图2

html
<img id="myImg" src="example.jpg" style="width: 200px;">
<script>
const img = document.getElementById('myImg');
img.onload = () => {
console.log(img.width); // 200 (CSS 宽度)
console.log(img.naturalWidth); // 1000 (原始文件宽度)
};
// 如果图片未加载完成 / 对于SVG或没有固有尺寸的图片,naturalWidth也可能会是0。
</script>
**实际应用:**前端提交图片数据给后端前,要进行批量图片尺寸校验,提前找出不符合尺寸的图片
改良前(使用 Image 对象 + onload事件):

优点 :跨域资源支持(但需 CORS),兼容性好。
缺点:异步,必须等待加载完成。
改良后(直接读取已存在的 <img> 元素):

javascript
if (params.imageList && params.imageList.length > 0) {
const results = await this.mapLimit(params.imageList, 3, (item) =>
this.checkImageSize(item.imagePath)
)
const sizeFail = []
const loadFail = []
results.forEach((r, idx) => {
if (r.ok) return
if (r.reason === 'load') loadFail.push(idx + 1)
else sizeFail.push(idx + 1)
})
if (loadFail.length) {
this.loading = false
return this.$alert(
this.$t('第') +
loadFail.join('、') +
this.$t('张图片加载失败,请稍后重试保存'),
this.$t('错误提示')
)
}
if (sizeFail.length) {
this.loading = false
return this.$alert(
this.$t('第') +
sizeFail.join('、') +
this.$t(
'张图片不符合TIKTOK平台要求的尺寸:300 x 300 到 4000 x 4000像素'
),
this.$t('错误提示')
)
}
},
async mapLimit(list, limit, fn) {
const ret = []
let i = 0
async function worker() {
while (i < list.length) {
const idx = i++
ret[idx] = await fn(list[idx], idx)
}
}
await Promise.all(
Array.from({ length: Math.min(limit, list.length) }, worker)
)
return ret
},
checkImageSize(imagePath, retry = 2) {
return new Promise((resolve) => {
const img = new Image()
let settled = false
const timer = setTimeout(
() => finish({ ok: false, reason: 'load' }),
15000
)
const finish = (result) => {
if (settled) return
settled = true
clearTimeout(timer)
resolve(result)
}
img.onload = () => {
const width = img.naturalWidth
const height = img.naturalHeight
const diff = Math.abs(width - height)
const valid =
width >= 300 &&
width <= 4000 &&
height >= 300 &&
height <= 4000 &&
diff <= 1
finish({ ok: valid, reason: valid ? 'ok' : 'size', width, height })
}
img.onerror = () => {
if (retry > 0) {
clearTimeout(timer)
// 避开可能被截断的缓存,隔一会再拉
const sep = imagePath.includes('?') ? '&' : '?'
const next = `${imagePath.split('?')[0]}${sep}_r=${Date.now()}`
setTimeout(() => {
this.checkImageSize(next, retry - 1).then(finish)
}, 400)
return
}
finish({ ok: false, reason: 'load' })
}
img.src = imagePath
})
},
优点 :不需要额外请求,适用于页面上已有的图片。
缺点:需要处理未加载完成的情况。
二、解决BUG
2.1、弹窗和组件层级错乱(缓存问题)
当前端弹窗上的某个组件样式层级错乱/被遮挡/列表看不到,直接ctrl+f5就解决了

1. 正常刷新(F5)为什么不行?
浏览器为了快,会缓存CSS、JS、图片。虽然有时候部署了新版本,但浏览器本地还是旧的CSS文件,所以页面展示错乱/被遮挡。
F5 刷新时,浏览器优先用本地缓存,不会去服务器拿新样式。
2. Ctrl+F5 为什么立刻就好?
Ctrl+F5 = 强制不使用缓存 + 重新从服务器下载所有文件
扔掉旧 CSS、拉取最新的 CSS、最新 CSS 里:z-index 正确、层级正确、不遮挡了
总结:为什么偏偏是「弹窗 / 浮层 / 模态框」最容易出这个问题?
因为弹窗、抽屉、下拉框、提示框这些组件,极度依赖 z-index(层级)
永绝后患:
(1)、CSS 打包时加 hash
app.abc123.css → 改了代码自动变 app.def456.css
浏览器一看文件名变了,就不会用缓存。
(2)、HTML 加上禁用缓存头
不让浏览器缓存 index.html
(3)、强制清缓存部署
发布时刷新 CDN、清服务器缓存
|-------------------------------|----------------|-----------------|
| 问题 | 原因 | 解决方法 |
| 页面样式乱、布局错、弹窗被挡(样式类) | 浏览器缓存了旧CSS | 直接 Ctrl+F5 |
| 点击没反应、功能失效(JS功能类) | 缓存了旧JS文件 | Console(控制台) |
| 图片不更新/图标显示错误(静态资源类) | 图片 / 字体文件被缓存 | 直接 Ctrl+F5/后端 |
| 新页面入口不出现/导航菜单还是旧的(页面结构类) | index.html 被缓存 | 直接 Ctrl+F5/后端 |
| 接口请求还是旧地址/拿不到新字段 (接口/数据类) | 旧的 API 配置文件缓存 | Network(网络) /部署 |
三、性能优化
3.1、自动移除代码中所有的 console.* 调用
在生产环境中保留 console 语句主要会带来两个问题:
(1)、安全隐患:console.log 中打印的数据可能包含敏感的用户信息或业务数据
(2)、性能负担:数量庞大的 console 语句不仅会增加代码包的体积,运行时的打印操作本身也会消耗计算资源
以Vue2项目为例:此处使用babel-plugin-transform-remove-console插件
安装:npm install babel-plugin-transform-remove-console --save-dev
配置:在Babel配置文件(如 babel.config.js 或 .babelrc)中引入该插件:
javascript
module.exports = {
presets: ["@vue/cli-plugin-babel/preset"],
env: {
production: {
plugins: [
'transform-remove-console' // 配置
]
}
},
plugins: [
[
"component",
{
libraryName: "element-ui",
styleLibraryName: "~src/theme",
},
],
[
"import",
{
libraryName: "vxe-table",
style: true, // 样式是否也按需加载
},
],
],
};
3.2、Vue项目Gzip+Nginx打包性能优化
(1)、减少文件体积:压缩后的文件体积通常能减小 60%-70%,提升首屏加载速度。
(2)、节约带宽成本:更小的文件体积也意味着更少的流量消耗。
(3)、使用方法:这个插件通常在构建生产环境代码时使用,配合 Nginx 等服务器开启 gzip_static on (写在nginx.conf文件里)后,服务器就能优先发送压缩好的 .gz 文件,浏览器会自动解压。注意 :
gzip_static指令需要放在location块内,不要写在全局http或server块中。配置好后,执行nginx -s reload重启 Nginx。
以Vue2--Webpack项目为例:此处使用compression-webpack-plugin插件
安装:npm install compression-webpack-plugin --save-dev
配置:在 vue.config.js 中引入该插件:
javascript
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true,
chainWebpack: config => {
config
.plugin('gzip-plugin')
.use('compression-webpack-plugin', [
{
filename: '[path][base].gz',
algorithm: 'gzip',
test: /\.js$|\.html$|\.json$|\.css$|\.ttf$/,
threshold: 102400, // 只有大小大于该值的资源会被处理100KB =100 * 1024
deleteOriginalAssets: false // 删除原文件
}
])
.end()
},
})
注意:配置完成后,执行打包命令(如 npm run build),你就会在输出目录(如 dist 文件夹)中,看到 .js 和 .css 文件旁边多出了同名但后缀加了 .gz 的压缩文件。
以Vue3-- Vite项目为例:此处使用vite-plugin-compression插件
安装:npm install vite-plugin-compression -D
配置:在 vue.config.js 中引入该插件:
TypeScript
import compression from 'vite-plugin-compression'
export default defineConfig({
plugins: [
vue(),
compression({
ext: '.gz', // 生成的文件扩展名
compressionOptions: { level: 9 }, // 压缩级别,1-9,越高压缩率越大
algorithm: 'gzip', // 压缩算法,可选 'gzip' | 'brotli'
threshold: 51200, // 阈值:小于100kb的文件不压缩(压缩后反而可能变大)
filter:/\.(js|css|html|svg|json)$/i , // 排除已经压缩过的文件(图片、视频、压缩包本身)
deleteOriginFile: false, // 不要删除原始源文件,默认false,避免回退问题
}),
],
resolve: {
alias: { '@': '/src' },
},
})
3.3、防御性代码---属性安全访问
问题:
后端返回的 deleteRes.data 有时可能为空数组(例如没有数据、删除操作后、或者异常情况),前端未预判这种边界条件(类似情况在5年+前端也会出现)。对异步接口返回值的健壮性处理不足。
解决:
**?.**确保如果 deleteRes.data 不存在或不是数组,或者 0 为 undefined,表达式不会报错,而是返回 undefined。
**?? \[\]**在最终结果为 undefined 或 null 时,提供一个空数组作为备用值,保证 children 始终是一个可迭代的数组。

javascript
async loadFoldersData() {
try {
const [fileRes, deleteRes] = await Promise.all([
get('/getCenterFolder'),
get('/getFolderDelete')
])
if (fileRes.code !== 200) return
if (fileRes.data.length) {
this.deleteFolders = [
{
folderName: '已删除素材',
// children: deleteRes.data[0].children,
// 报错原因:deleteRes.data可能是空数组,访问[0]会返回undefined,导致无法访问children属性
children: deleteRes.data?.[0]?.children ?? [],
id: this.deleteId
}
]
}
this.folders = fileRes.data
this.$nextTick(() => {
this.$refs.tree.setCurrentKey(this.folderId || fileRes.data[0].id)
})
} catch (e) {
console.error(e)
}
},
总结:
这属于前端开发中非常典型的"接口数据容错与属性安全访问 "问题。其核心是在异步获取的数据结构不确定时,通过**防御性编码(可选链、空值合并、默认值)**来避免运行时错误,提升应用的健壮性。