1. 前端性能优化实战策略
✅ 加载优化
- 资源压缩:Webpack + Terser(JS)、CSSNano
- 代码分割 :
import()
动态导入,按路由拆分 - 预加载 :
<link rel="preload">
关键资源 - CDN 加速:静态资源分发全球节点
✅ 渲染优化
- 防阻塞 :CSS 放 head,JS 放 body 底部 or
async/defer
- 懒加载 :图片
loading="lazy"
,组件动态导入 - 骨架屏:首屏快速渲染占位结构
✅ 执行优化
- 防抖节流 :
scroll
/resize
事件控制频率 - Web Worker:耗时计算移出主线程
- 虚拟列表:长列表只渲染可视区域
Q1: defer 和 async 区别?
A1: defer
顺序执行,DOM 解析完再运行;async
加载完立即执行,不保证顺序 💥
Q2: 如何测量首屏时间?
A2: PerformanceObserver
监听 paint
条目,first-contentful-paint
✅
👉继续看?Q3: 为什么说"减少 reflow & repaint"是伪命题?
A3: 现代浏览器会批量更新,关键应避免强制同步布局(Force Sync Layout)🤯
2. 如何判断变量是数组?
✅ 正确方式(按优先级):
js
// 1. 最佳:Array.isArray()
Array.isArray([]) // true
// 2. 兼容老浏览器:Object.prototype.toString
Object.prototype.toString.call([]) === '[object Array]' // true
// 3. 原型链检查(不推荐)
arr.__proto__ === Array.prototype // 可被改写,不安全
❌ 错误方式:
js
// 类型判断失败
typeof [] === 'object' // true,但无法区分对象和数组
// 构造函数检查(跨 iframe 失效)
arr.constructor === Array // iframe 中的数组 constructor 不同
Q4: 为什么 Array.isArray 是最安全的?
A4: ES5 原生方法,不可篡改,且正确处理跨 iframe 场景 ✅
3. 变量 a 和 b 如何交换?
✅ 方式一:ES6 解构(推荐)
js
[a, b] = [b, a];
✅ 方式二:临时变量(最兼容)
js
let temp = a;
a = b;
b = temp;
✅ 方式三:数学运算(仅限数字)
js
a = a + b;
b = a - b;
a = a - b;
✅ 方式四:异或(仅限整数)
js
a ^= b;
b ^= a;
a ^= b;
Q5: 解构赋值底层怎么实现的?
A5: 引擎创建临时数组 [b, a]
,再按位置赋值,本质还是用了临时空间 💡
9. 事件委托(Event Delegation)
利用事件冒泡,将子元素事件绑定到父元素
html
<ul id="list">
<li>Item 1</li>
<li>Item 2</li>
<!-- 动态添加更多 -->
</ul>
js
document.getElementById('list').addEventListener('click', e => {
if (e.target.tagName === 'LI') {
console.log('Clicked:', e.target.textContent);
}
});
优势:
- 减少内存占用(一个监听器 vs 多个)
- 自动支持动态元素
- 提升性能
Q6: 如何阻止事件冒泡影响委托?
A6: e.stopPropagation()
,但慎用,会破坏委托机制 ❗
10. 标签生成的 DOM 结构是类数组
js
const divs = document.getElementsByTagName('div');
// divs 是 HTMLCollection,长这样:
{
0: <div>,
1: <div>,
length: 2,
[Symbol.iterator]: function() { ... }
}
⚠️它能调用 push 吗?
11. 类数组和数组的区别
特性 | 数组 | 类数组 |
---|---|---|
原型 | Array.prototype | Element.prototype |
方法 | 有 push/pop | 无数组方法 |
length | 自动更新 | 手动维护(部分自动) |
迭代 | 可迭代 | 部分可迭代(如 NodeList) |
js
// 共同点:有 length + 数字索引
arr[0] === list[0]; // true
Q7: Arguments 是类数组吗?
A7: 是!但箭头函数中不存在,已被 ...args
取代 ✅
12. DOM 类数组如何转成数组?
✅ 方式一:Array.from(推荐)
js
const arr = Array.from(document.querySelectorAll('div'));
✅ 方式二:扩展运算符
js
const arr = [...document.querySelectorAll('div')];
✅ 方式三:call 调用数组方法
js
const arr = Array.prototype.slice.call(document.getElementsByTagName('li'));
✅ 方式四:for 循环手动构建
js
const list = document.querySelectorAll('p');
const arr = [];
for (let i = 0; i < list.length; i++) {
arr.push(list[i]);
}
Q8: 为什么 NodeList 有些能迭代有些不能?
A8: querySelectorAll
返回静态 NodeList(可迭代),getElementsByTagName
返回动态集合(实时更新)💥
13. 单页面应用 vs 多页面应用
维度 | SPA | MPA |
---|---|---|
体验 | 流畅,像原生应用 | 有白屏,传统网页感 |
SEO | 需 SSR 支持 | 天然友好 |
首屏加载 | 初次大,后续快 | 每次独立加载 |
构建复杂度 | 高(路由、状态管理) | 低(HTML + JS) |
适用场景 | 后台系统、Web App | 内容站、营销页 |
Q9: 如何让 SPA 支持 SEO?
A9: 服务端渲染(SSR)或预渲染(Prerender)✅
15. localStorage API 全解析
js
// ✅ 增
localStorage.setItem('name', '张三');
// ✅ 查
localStorage.getItem('name'); // "张三"
// ✅ 改 → 同 setItem
// ✅ 删
localStorage.removeItem('name');
localStorage.clear(); // 清空所有
// ✅ 监听变化(跨标签页)
window.addEventListener('storage', e => {
console.log(e.key, e.oldValue, e.newValue);
});
注意事项:
- 存储字符串,对象需
JSON.stringify
- 容量约 5-10MB
- 同源策略限制
- 同步操作,大数据阻塞主线程
Q10: 如何存储对象?
A10: localStorage.setItem('user', JSON.stringify({name:'张三'}))
,读取时 JSON.parse
💡
Q11: localStorage 和 sessionStorage 区别?
A11: 前者永久存储(除非清除),后者关闭标签页即销毁 🌟