为什么普通 for 循环的性能远远高于 forEach 的性能
首先问题说 for 循环优于 forEach 并不完全正确,循环次数不够多的时候, forEach 性能优于 for
// 循环十万次let arrs = new Array(100000);console.time('for');for (let i = 0; i < arrs.length; i++) {};console.timeEnd('for'); // for: 2.36474609375 msconsole.time('forEach');arrs.forEach((arr) => {});console.timeEnd('forEach'); // forEach: 0.825927734375 ms
循环次数越大, for 的性能优势越明显
// 循环 1 亿次let arrs = new Array(100000000);console.time('for');for (let i = 0; i < arrs.length; i++) {};console.timeEnd('for'); // for: 72.7099609375 msconsole.time('forEach');arrs.forEach((arr) => {});console.timeEnd('forEach'); // forEach: 923.77392578125 ms
先做一下对比
结论
for 性能优于 forEach , 主要原因如下:
- foreach 相对于 for 循环,代码减少了,但是 foreach 依赖 IEnumerable。在运行的时候效率低于 for 循环。
- for 循环没有额外的函数调用栈和上下文,所以它的实现最为简单。forEach:对于 forEach 来说,它的函数签名中包含了参数和上下文,所以性能会低于 for 循环。
base64 的编码原理是什么
Base64 编码是一种用于将二进制数据转换为可打印 ASCII 字符的编码方式。它的原理如下:
- 将原始数据划分为连续的字节序列。
- 将每个字节转换为 8 位二进制数。
- 将这些二进制数按照 6 位一组进行分组,不足 6 位的用 0 补齐。
- 将每个 6 位的二进制数转换为对应的十进制数。
- 根据 Base64 字符表,将十进制数转换为相应的可打印 ASCII 字符。
Base64 字符表由 64 个字符组成,通常使用以下字符:A-Z、a-z、0-9 以及字符"+"和"/"。这些字符可以通过索引值与相应的十进制数进行对应。
编码过程中,如果原始数据的长度不是 3 的倍数,会根据需要进行填充。填充通常使用字符"=",每个填充字符表示 4 位的零值。
解码时,按照相反的过程进行操作。将 Base64 编码后的字符串按照 4 个字符一组分组,并将每个字符转换回对应的十进制数。然后将这些十进制数转换为 6 位二进制数,并将这些二进制数连接起来。最后,将连接后的二进制数划分为 8 位一组,并将每个 8 位二进制数转换为对应的字节数据。
Base64 编码主要应用于在文本协议中传输或存储二进制数据,例如在电子邮件中传输附件或在 Web 中传输图像数据。它可以将二进制数据转换为 ASCII 字符,使其在不支持二进制传输的环境中能够正常处理。
如何做 promise 缓存?上一次调用函数的 promise 没有返回, 那么下一次调用函数依然返回上一个 promise?
promise实现前端缓存
举个常见的场景:在调用接口前都需要做一个权限check,伪代码如下。
function getDataFromServer(url, options){
return checkFromServer().then(()=>{
return axios(url,options);
});
}
function checkFromServer(){
return axios("/check").then((res)=>{
if(res.data.code===403){
throw new Error("permission deny!");
}
});
}
// 调用接口数据
getDataFromServer("/data",{});
上面的代码看起来没有什么问题。如果考虑并发状态下呢?
getDataFromServer("/data",{});
getDataFromServer("/data1",{});
getDataFromServer("/data3",{});
在这里会触发三次的/check请求,从实际情况出发的话,在短期内可能只需要一次/check就可以了,性能上也能保障最优。
改造一下上面的代码:
const checkPromise = null;
function getDataFromServer(url, options){
return checkFromServer().then(()=>{
return axios(url,options);
});
}
function checkFromServer(){
// 如果有缓存,则直接使用缓存
if(checkPromise){
return checkPromise;
}
checkPromise = axios("/check").then((res)=>{
if(res.data.code===403){
throw new Error("permission deny!");
}
// 5秒后清除缓存
setTimeout(()=>{
checkPromise = null;
},5000);
}).catch((err)=>{
checkPromise = null;
throw err;
});
return checkPromise;
}
// 调用接口数据
getDataFromServer("/data",{});
getDataFromServer("/data1",{});
getDataFromServer("/data3",{});
如上代码,既解决了/check被调用多次的问题,也解决了并发时候的问题。看起来好像没什么问题了。
再来考虑一个场景把,假设/check数据需要缓存5分钟,5分钟之内都不会再去请求接口并考虑刷新页面的情况呢。
那就比较复杂了,数据可能需要落地到localstorage,可能还需要考虑缓存淘汰机制等情况。
这里有现成写好的库cache-in-stroage。
快速应用:
import { cacheDec } from "cache-in-storage";
const getTimeSpan = async (isError = false) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (isError) {
return reject(new Error("test"));
}
resolve(Date.now());
}, 200);
});
};
// 对getTimeSpan进行缓存,并把缓存结果放入localStorage
const getTimeSpanWithCache = cacheDec(getTimeSpan, "keyInCache", { cache:true }, localStorage);
// 此时执行方法返回的值都是相同的
getTimeSpanWithCache().then(console.log);
getTimeSpanWithCache().then(console.log);
getTimeSpanWithCache(true).catch(console.error);
CSS 选择器有哪些、优先级如何?
! important>内联(style)> ID选择器 > 类选择器 > 标签选择器>通配符选择器
如何优化大规模 dom 操作的场景
1. 最小化 DOM 操作次数
- 批量更新:将多个小的 DOM 更新操作合并成一次大的更新。例如,可以先修改一个数组,然后一次性将整个数组渲染到 DOM 中。
- 文档碎片(DocumentFragment) :使用 DocumentFragment 来创建不在主文档树中的节点,然后一次性将它们添加到 DOM 中。这样可以减少多次的 DOM 插入操作。
2. 使用虚拟 DOM
- 使用 React, Vue 或 Angular:这些现代前端框架提供了虚拟 DOM 的实现,可以大幅度减少直接操作 DOM 的需要,同时自动处理 DOM 的高效更新。
- 手动实现虚拟 DOM:如果使用原生 JavaScript,可以手动实现或使用轻量级的虚拟 DOM 库(如 Snabbdom)来管理 DOM 更新。
3. 节流和防抖
- 节流(Throttling) :限制一个函数在一定时间内的调用次数。适用于如滚动、窗口大小调整等事件处理。
- 防抖(Debouncing) :延迟函数的执行直到停止触发事件一段时间后再执行。适用于输入框的实时搜索功能。
4. 延迟渲染
- 懒加载(Lazy Loading) :仅在需要时才加载和渲染内容,例如,使用滚动到视图时才加载图片或内容块。
- 按需渲染:只渲染用户当前视图中可见的部分,对于不在视口中的内容延迟加载或渲染。
5. 使用高效的 CSS 和 JavaScript
- CSS 选择器优化:避免使用复杂的选择器,尽量使用类选择器而非 ID 或标签选择器。
- CSS3 动画和转换:利用 CSS3 的
transform和opacity进行动画和状态改变,这些通常不会触发重排和重绘。 - Web Workers:对于计算密集型的任务,可以使用 Web Workers 在后台线程执行,避免阻塞 UI 线程。
6. 避免不必要的回流(Reflow)和重绘(Repaint)
- CSS 将多个样式规则合并:减少通过 JavaScript 直接修改样式属性的次数,尽量使用 CSS 类来一次性修改多个样式。
- 使用
requestAnimationFrame:在动画或复杂的计算后使用requestAnimationFrame来安排 DOM 更新,确保在浏览器重绘前进行。
7. 监控和分析
- 性能分析工具:使用 Chrome DevTools 的 Performance 标签来监控和分析应用的性能瓶颈。