刷刷题4

80 阅读6分钟

为什么普通 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

先做一下对比

image.png

结论

for 性能优于 forEach , 主要原因如下:

  1. foreach 相对于 for 循环,代码减少了,但是 foreach 依赖 IEnumerable。在运行的时候效率低于 for 循环。
  2. for 循环没有额外的函数调用栈和上下文,所以它的实现最为简单。forEach:对于 forEach 来说,它的函数签名中包含了参数和上下文,所以性能会低于 for 循环。

base64 的编码原理是什么

Base64 编码是一种用于将二进制数据转换为可打印 ASCII 字符的编码方式。它的原理如下:

  1. 将原始数据划分为连续的字节序列。
  2. 将每个字节转换为 8 位二进制数。
  3. 将这些二进制数按照 6 位一组进行分组,不足 6 位的用 0 补齐。
  4. 将每个 6 位的二进制数转换为对应的十进制数。
  5. 根据 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 标签来监控和分析应用的性能瓶颈。

WebSocket 协议的底层原理是什么

blog.csdn.net/u012903034/…