面试准备

为新工作准备下

defer 和 async

https://powerfulyang.com/post/42#%E8%84%9A%E6%9C%ACasyncdefer

协商缓存的场景

协商缓存,也称为验证缓存,是一种 Web 缓存策略,它允许客户端和服务器之间进行沟通,以确定客户端已缓存的资源是否仍然是最新的。它主要通过 HTTP 头部中的特定字段来实现,例如 ETagLast-Modified。以下是几种常见的协商缓存的应用场景:

  1. 静态资源更新:对于网站上的静态资源(如 CSS 文件、JavaScript 脚本、图片等),当这些资源更新不频繁时,可以使用协商缓存来避免不必要的网络传输。当浏览器请求一个资源时,服务器会返回该资源以及 ETagLast-Modified 头部。浏览器在下一次请求相同资源时,会发送 If-None-Match(对应 ETag)或 If-Modified-Since(对应 Last-Modified)头部,服务器根据这些头部决定资源是否已修改,从而决定是返回新的资源还是 304 状态码表示资源未修改。
    • 实际上字体文件用协商缓存体感是非常差的。js,css 文件的话肯定带 hash 然后用 disk cache 更好。静态资源肯定其实是没必要的。
  2. API 响应缓存:在某些情况下,API 的响应不会频繁改变(如某个特定用户的配置信息)。在这种情况下,可以利用协商缓存来减少不必要的数据传输。如果数据自上次请求以来没有变化,服务器可以返回 304 状态码,告诉客户端使用本地缓存的数据。
  3. 文章或博客内容:对于内容更新不频繁的网站部分,如博客文章、新闻报道等,可以使用协商缓存来提高访问速度和减少服务器负担。当用户重新访问这些内容时,如果内容没有更新,服务器将返回 304 状态码,从而减少数据传输。
  4. 用户特定数据:对于用户个性化设置或配置,这些数据通常不会频繁更改。通过协商缓存,可以有效减少对这类数据的重复请求,优化用户体验。
  5. 论坛或评论区更新:在社交平台、论坛或评论区,内容可能不会实时更新。通过使用协商缓存,可以在内容未发生变化时减少服务器响应负载,同时保证用户在内容更新时能够及时获取最新信息。
    • 其实主要还是要后端支持,复杂度感觉有一些的。主要就是返回数据集比较大的情况有一些优化,毕竟总是要发送一次请求,后端如果有缓存是优化,没有的话(进行计算查询的话,相比那点数据传输成本)也就那样了。

在实际应用中,协商缓存通常与强缓存结合使用,以实现更高效的缓存策略,减少服务器负担,加快内容加载速度,提升用户体验。正确配置协商缓存可以大大提高 Web 应用的性能和可扩展性。

js 和 css 会阻塞 DOM 加载?

阻塞 DOM 加载主要是指某些资源的加载和处理会暂停浏览器对文档其余部分的解析,直到该资源完全加载并处理完成。对于 CSS 和 JS,它们阻塞 DOM 加载的行为如下:

CSS

  • 不直接阻塞 DOM 加载:CSS 本身不会阻止 DOM 的解析。然而,它会阻塞渲染过程,因为浏览器需要构建 CSSOM(CSS 对象模型),它与 DOM 一起用于创建渲染树。因此,尽管 CSS 加载和解析不直接阻塞 DOM 的构建,但会阻塞页面的最终渲染,因为没有构建完整的渲染树,页面就不会被绘制。
  • 影响 JavaScript 执行:如果 JavaScript 试图操作尚未加载的 CSS 样式(例如,通过 getComputedStyle),那么 JavaScript 会阻塞,直到相关的 CSS 被加载和解析,这是因为 JS 需要准确的样式信息来执行操作。

JavaScript

  • 直接阻塞 DOM 加载:当浏览器遇到一个 <script> 标签时,它会暂停 DOM 的解析,转而加载并执行脚本。这是因为 JS 可能会修改 DOM 结构(如通过document.write),所以浏览器需要等待 JS 执行完成才能继续解析 DOM。这种行为导致 DOM 加载被阻塞。
  • asyncdefer 属性:使用<script async><script defer>可以改变 JS 对 DOM 加载的影响。async属性允许浏览器异步加载 JS 文件,而不阻塞 DOM 解析,但 JS 文件加载完成后会立即执行,可能会阻塞 DOM 解析的完成。defer 属性让 JS 文件在 DOM 解析完成后才执行,从而不阻塞 DOM 解析。

为了减少这些资源对 DOM 加载的影响,建议采取以下措施:

  • 优化 CSS 加载:将关键 CSS 直接内联在 HTML 中,确保外部样式表尽早加载。
  • 优化 JS 加载和执行:将脚本放在文档底部、使用 asyncdefer 属性,或采用其他 JavaScript 加载优化技术,如代码分割和懒加载。

通过这些方法,可以减少 CSS 和 JS 对 DOM 加载的阻塞,提高页面加载速度,改善用户体验。

超大表格渲染卡顿怎么解决

https://www.surely.cool/ 很多人推荐这个,可以看看

requestAnimationFrame

使用 requestAnimationFrame(RAF)来更新 DOM 是一种在动画或连续变化过程中优化性能的技术。它允许你在浏览器准备好绘制下一帧时执行一次更新,从而最大限度地减少不必要的渲染和布局计算。在虚拟滚动的上下文中,你可以使用 RAF 来同步滚动事件和 DOM 更新,以提供更平滑的滚动体验。

以下是一个简单的示例,展示了如何在处理滚动事件时使用 requestAnimationFrame

javascript
1let lastScrollY = window.scrollY;
2let ticking = false;
3
4function onScroll() {
5  lastScrollY = window.scrollY;
6
7  // 如果已经有一个 RAF 在队列中,则不再请求另一个
8  if (!ticking) {
9    window.requestAnimationFrame(updateDOM);
10    ticking = true;
11  }
12}
13
14function updateDOM() {
15  // 在这里更新 DOM,使用 lastScrollY 作为最新的滚动位置
16  // 例如,你可以根据滚动位置动态加载或卸载列表项
17
18  // 更新完成后,重置 ticking 状态,允许下一个 RAF 请求
19  ticking = false;
20}
21
22// 监听滚动事件
23window.addEventListener("scroll", onScroll);

在这个示例中,onScroll 函数在滚动事件发生时被调用,但它不会直接更新 DOM。相反,它设置了一个标志 ticking 并请求一个 RAF。如果在当前帧中已经有一个 RAF 请求(ticking 已经是 true),它不会重复请求。这确保了 updateDOM 函数(实际更新 DOM 的地方)每帧最多只被调用一次,无论滚动事件触发得有多频繁。

这种方法可以优化性能,因为它避免了在短时间内进行大量的 DOM 更新操作,这些操作可能导致布局抖动或帧速率下降。通过将实际的 DOM 更新与滚动事件的频繁触发解耦,你可以提供更流畅和响应更快的用户界面,尤其是在复杂的动态应用中,如实现虚拟滚动的场景。

渐进式加载

当检测到快速滚动时,可以只加载轻量级的内容(例如,仅文本,没有图片或复杂的布局),并在滚动停止后再加载完整内容。

预加载和缓存

  • 预加载:在用户接近可视区域的边缘时,提前加载额外的数据。这样,当用户到达新的位置时,数据已经准备好并可以快速渲染。
  • 缓存:保留已经渲染过的项在内存中,即使它们不再视口中。当用户滚动回之前的位置时,可以从缓存中快速恢复,而不是重新渲染。

DOM 缓存

DOM 缓存:对于已经渲染的 DOM 元素,可以使用一种机制(如对象池)来复用这些元素,而不是在滚动时销毁并重新创建。

内存管理:定期清理长时间未使用的缓存项,防止内存膨胀。这可能涉及到一些复杂的逻辑,比如跟踪元素的最后使用时间,设置缓存大小限制等。

DOM 缓存通常涉及到一种称为“对象池”(Object Pool)的设计模式,这种模式可以有效管理和复用大量对象(在这个场景中是 DOM 元素)。通过对象池,我们可以减少创建和销毁 DOM 元素的开销,从而提高应用性能,尤其是在频繁进行 DOM 操作的场景下,如大型列表或表格的滚动。以下是一个简化的示例,展示了如何实现 DOM 元素的对象池来复用列表项:

javascript
1class DOMPool {
2  constructor(createElement) {
3    this.createElement = createElement; // 函数,用于创建新的 DOM 元素
4    this.pool = []; // 存储可复用的 DOM 元素
5  }
6
7  // 从池中获取一个 DOM 元素,如果池为空,则创建一个新元素
8  acquire() {
9    if (this.pool.length > 0) {
10      return this.pool.pop();
11    } else {
12      return this.createElement();
13    }
14  }
15
16  // 将不再需要的 DOM 元素返回到池中以供以后复用
17  release(element) {
18    this.resetElement(element); // 重置元素状态,例如清除文本内容、样式等
19    this.pool.push(element);
20  }
21
22  // 重置 DOM 元素到初始状态,以便安全复用
23  resetElement(element) {
24    element.textContent = ""; // 示例:清除文本内容
25    // 根据需要添加其他重置逻辑,例如移除样式、事件监听器等
26  }
27}
28
29// 创建 DOM 元素的函数示例
30function createListItem() {
31  return document.createElement("li");
32}
33
34// 创建对象池实例
35const listItemPool = new DOMPool(createListItem);
36
37// 获取一个列表项元素
38const item = listItemPool.acquire();
39
40// 使用列表项
41item.textContent = "Hello, world!";
42document.body.appendChild(item);
43
44// 当不再需要这个元素时,将其释放回池中
45listItemPool.release(item);

在这个例子中,DOMPool 类负责管理 DOM 元素的池。acquire 方法用于获取一个元素,如果池中有可用元素则直接返回一个,如果没有,则调用 createElement 函数创建一个新元素。release 方法将不再需要的元素返回到池中,以便将来复用。在返回池中之前,resetElement 方法会被调用来重置元素的状态,确保复用时不会有副作用。

在实际应用中,对象池的具体实现可能更复杂,需要考虑更多的细节,比如如何高效管理池的大小,如何处理不同类型的 DOM 元素等。但基本原理是一致的,即通过复用 DOM 元素来减少创建和销毁的开销,从而提高性能。