总结
整体下来的感觉就是 “字节的技术确实是厉害” 感觉能不能进还挺玄学的
祝大家工作顺利 心想事成~
代码
1. 实现数组里的拍平 数组本身的拍平
// flat
const nestedArray = [1, [2, 3], [4, [5, 6]]];
const flattenedArray = nestedArray.flat(Infinity); // 使用 Infinity 作为深度,确保完全拍平
console.log(flattenedArray); // [1, 2, 3, 4, 5, 6]
// reduce
function flattenArray(arr) {
return arr.reduce((accumulator, currentValue) => {
return accumulator.concat(Array.isArray(currentValue) ? flattenArray(currentValue) : currentValue);
}, []);
}
const nestedArray = [1, [2, 3], [4, [5, 6]]];
const flattenedArray = flattenArray(nestedArray);
console.log(flattenedArray); // [1, 2, 3, 4, 5, 6]
// 递归
unction flattenArray(arr) {
let result = [];
for (let i = 0; i < arr.length; i++) {
if (Array.isArray(arr[i])) {
result = result.concat(flattenArray(arr[i]));
} else {
result.push(arr[i]);
}
}
return result;
}
const nestedArray = [1, [2, 3], [4, [5, 6]]];
const flattenedArray = flattenArray(nestedArray);
console.log(flattenedArray); // [1, 2, 3, 4, 5, 6]
2. 实现事件 on emit once
class EventEmitter {
constructor() {
this.events = {};
}
// 添加事件监听器
on(eventName, callback) {
if (!this.events[eventName]) {
this.events[eventName] = [];
}
this.events[eventName].push(callback);
}
// 触发事件并调用所有监听器
emit(eventName, ...args) {
if (this.events[eventName]) {
this.events[eventName].forEach((callback) => {
callback(...args);
});
}
}
// 添加一次性事件监听器
once(eventName, callback) {
const wrapper = (...args) => {
this.off(eventName, wrapper);
callback(...args);
};
this.on(eventName, wrapper);
}
// 移除事件监听器
off(eventName, callback) {
if (this.events[eventName]) {
this.events[eventName] = this.events[eventName].filter(
(cb) => cb !== callback
);
// 如果没有监听器了,则删除该事件
if (this.events[eventName].length === 0) {
delete this.events[eventName];
}
}
}
}
// 使用示例
const eventEmitter = new EventEmitter();
eventEmitter.on("event", (data) => console.log("on:", data));
eventEmitter.once("event", (data) => console.log("once:", data));
eventEmitter.emit("event", "Hello, World!"); // 输出: on: Hello, World! 和 once: Hello, World!
eventEmitter.emit("event", "Hello again!"); // 只输出: on: Hello again!
项目
1. 说一下对地图和 ECharts 这块业务场景上存在的理解?
2. 按需加载处理撒点 说一下优化前痛点是什么 优化后效果怎么样?
3. 函数式弹窗 从 pinia 角度来讲 从状态分级上的完成上做了什么?
4. 函数式弹窗发散 在当前的场景下除了这种优化方案 还有哪些优化方案?
5. 获取撒点位置为什么不是 getBoundingClinRect 而是百度地图 API 的 getBound()?
6. 按需加载是在撒点这里实现的 那这个优化前优化后有哪些指标反映了什么效果吗?
7. 地图大量数据进行撒点 原本是想把这个渲染速度和效率变得更高 但现在走 worker 可能要等待这个结果 反而更慢了 需要怎么处理
在处理地图上的大量数据撒点时,使用 Web Workers 的初衷是为了避免阻塞主线程,以便主线程可以继续响应用户交互。然而,如果你发现使用 Web Workers 后性能反而下降了 怎么办?
- 减少数据传输:
- 如果在 Web Worker 和主线程之间传输大量数据,这可能会成为性能瓶颈。尽量减少传输的数据量,例如只传输索引或 ID,然后在主线程中根据这些索引或 ID 获取实际的数据。
- 使用 Transferable Objects(如 ArrayBuffer 或 TypedArray)来避免数据复制,这样数据可以直接在 Worker 和主线程之间移动,而不需要复制。
- 并行处理:
- 如果可能的话,尝试将数据分成多个部分,并在多个 Web Workers 中并行处理。这样可以同时处理多个数据块,从而加快整体处理速度
- 优化数据处理:
- 在 Web Worker 内部优化数据处理算法,确保没有不必要的计算或内存使用。
- 如果撒点逻辑复杂,考虑是否可以在 Web Worker 中预先计算一些结果,以减少主线程上的计算负担。
- 异步渲染:
- 不要等待所有 Web Worker 的结果都返回后再进行渲染。相反,一旦有结果返回,就立即在主线程上进行渲染。这可以通过使用 requestAnimationFrame 或其他异步渲染技术来实现。
- 使用虚拟滚动或分页:
- 如果地图上的数据点太多,无法一次性全部渲染,考虑使用虚拟滚动或分页技术。这样,只有用户当前可见的数据点才会被渲染,从而大大减少渲染负担。
- 检查其他瓶颈:
- 使用浏览器的开发者工具来检查性能瓶颈。这可能包括网络请求、内存使用、CPU 使用等。
- 如果发现瓶颈在 Web Worker 之外,例如在网络请求或 DOM 操作上,那么优化这些方面可能会带来更大的性能提升。
- 回退到主线程:
- 如果在尝试了上述所有优化后仍然发现 Web Worker 的性能不如主线程,那么考虑回退到在主线程上直接处理数据。在某些情况下,由于浏览器优化和 JavaScript 引擎的改进,主线程可能实际上比 Web Worker 更快。
- 利用 WebGPU 或其他图形 API:
- 如果你的应用需要处理大量的图形数据,并且性能是关键,那么考虑使用 WebGPU 或其他图形 API 来加速渲染。这些 API 可以直接访问 GPU 硬件,从而提供比传统 Web 技术更高的性能。
9. Web Worker 的多线程是如何实现的?
10. Web Worker 处理场景有哪些 如果此时有个场景 此时组件要渲染 但是 worker 里面的数据还没有处理完 这时候怎么办?
Web Workers 是浏览器端的一个强大特性,允许在后台线程中执行脚本,这些线程与主线程独立,可以并行地处理一些复杂的计算或 I/O 任务,从而不会阻塞主线程(也就是 UI 线程),保证页面的流畅性。 当组件需要渲染,但 Web Worker 中的数据还没有处理完时,有几种处理方式:
使用占位符或加载指示器:在数据加载期间,可以显示一个占位符或加载指示器(如一个旋转的图标或进度条),告诉用户数据正在加载中。
异步渲染:在数据到达之前,组件可以进行基本的渲染,但不包含依赖于该数据的内容。当数据加载完成后,通过某种机制(如状态更新或事件监听)触发组件的重新渲染,将新的数据呈现出来。
取消或中止请求:如果组件不再需要这些数据(例如,用户已经导航到另一个页面或组件),可以取消或中止 Web Worker 中的任务,以释放资源。
使用缓存:如果数据是静态的或更新频率不高,可以考虑将数据缓存起来,以便在需要时快速获取。这样,即使 Web Worker 中的数据还没有处理完,也可以从缓存中获取数据并立即渲染组件。
优化数据加载和处理:尽量优化数据的加载和处理过程,以减少用户等待的时间。例如,可以使用数据分块、延迟加载等技术来减少一次性加载的数据量;或者使用更高效的算法来处理数据。
11. 异步组件是如何使用的?
12. 地图撒点:菜单栏是通过循环遍历 监听的是什么 详细讲解一下 为什么这么做?
13. 三级菜单栏管理是通过循环遍历进行查找的 那么如何优化?
15. 判断数据类型的方式?
16. vue 中组件通信?
17. provide 和 inject 实现多级传递的原理?
- 总结:通过依赖注入的方式实现多级传递
- 总结:会将父组件的的 provides 关联成当前组件实例 provides 对象原型上的属性,当在 inject 获取数据的时候,则会根据原型链的规则进行查找,找不到的话则会返回用户自定义的默认值
- 核心也就是从当前组件实例的父组件上取 provides 对象,然后再查找父组件 provides 上有没有对应的属性。因为父组件的 provides 是通过原型链的方式和父组件的父组件进行了关联,如果父组件上没有,那么会通过原型链的方式再向上取,这也实现了不管组件层级多深,总是可以找到对应的 provide 的提供方数据
19. 事件触发机制的原理?
20. http 的缓存机制?
21. 缓存过旧 是怎么判断的?
协商缓存的定义:客户端检查到资源超过有效期(即强制缓存未命中)时。此时,客户端会向服务器发出一个带有验证信息的请求,询问服务器该资源是否已经更新
验证信息的类型:
- 验证信息主要有两种类型:基于“最后修改时间”(Last-Modified)的验证和基于“实体标签”(ETag)的验证。
- Last-Modified:当服务器首次返回响应时,会携带一个 Last-Modified 字段,表示该资源最后修改的时间。客户端在发起验证请求时,会将这个时间作为 If-Modified-Since 字段的值发送给服务器。服务器会比较这个时间与资源当前的最后修改时间,如果相同,则返回 304 状态码,告诉客户端可以使用本地缓存;如果不同,则返回 200 状态码和新的资源内容。
- ETag:ETag 是服务器用来唯一标识资源的一个字符串。当客户端发起验证请求时,会将服务器之前返回的 ETag 作为 If-None-Match 字段的值发送给服务器。服务器会比较这个 ETag 与资源当前的 ETag,如果相同,则返回 304 状态码;如果不同,则返回 200 状态码和新的资源内容。
ETag 的优先级: 在同时存在 Last-Modified 和 ETag 的情况下,ETag 的优先级通常更高。即,服务器会首先比较 ETag 是否一致,如果不一致,则不再查看 Last-Modified 字段;如果 ETag 一致,但 Last-Modified 不一致,则通常也会返回 304 状态码,但这不是绝对的,具体行为可能取决于服务器的实现。
触发时机:点击浏览器的刷新按钮、某些类型的页面跳转、或者通过编程方式(如 JavaScript)发起的带有验证信息的请求。
总结:HTTP 协商缓存通过客户端和服务器之间的交互来判断缓存是否过旧。主要验证方式包括基于“最后修改时间”的验证和基于“实体标签”的验证。其中,ETag 的优先级通常更高。当缓存验证失败(即资源已更新)时,服务器会返回新的资源内容,客户端会更新本地缓存;当缓存验证成功时,客户端会继续使用本地缓存。
22. http1.1 1.0 2.0 三个的字段是怎么判断?
23. http1.1 1.0 2.0 详细描述?
- HTTP 1.0:
- 无状态、无连接:HTTP 1.0 是一种无状态、无连接的应用层协议。每次请求都需要与服务器建立一个 TCP 连接,服务器处理完成以后立即断开 TCP 连接。这种无连接的特性意味着每次发送请求都需要进行一次 TCP 连接,而 TCP 的连接释放过程又相对费事,因此会导致网络利用率变低。
- 队头阻塞:HTTP 1.0 规定下一个请求必须在前一个请求响应到达之前才能发送。如果前一个请求响应一直不到达,那么下一个请求就不发送,后面的请求就会阻塞。
- 缓存:HTTP 1.0 主要使用 header 里的协商缓存(如 last-modified 和 if-modified-since)和强缓存(如 Expires)作为缓存判断的标准。
- 其他问题:HTTP 1.0 没有 host 域,因此在一台服务器上只能存在一个 IP 地址对应一个 Web 服务。此外,HTTP 1.0 不支持断点续传功能,每次都会传送全部的页面和数据。
- HTTP 1.1
- 持久连接(Keep-Alive):HTTP 1.1 默认使用持久连接,允许在一个 TCP 连接上发送多个请求和响应,从而减少了建立和关闭连接的次数,提高了网络利用率。
- 管道化(Pipelining):HTTP 1.1 支持管道化,允许客户端在收到前一个请求的响应之前发送下一个请求,但这并不意味着服务器会并行处理这些请求。
- 灵活的请求/响应头:HTTP 1.1 增加了更多的请求/响应头字段,提供了更丰富的元数据。
- 更完善的缓存控制:通过引入新的缓存控制机制(如 ETag、Cache-Control),HTTP 1.1 提供了更精细的缓存控制。 安全性:HTTP 1.1 本身并没有提供安全性功能,但可以通过 HTTPS(HTTP Secure)来提供加密的通信。
- HTTP 2.0
- 二进制协议:HTTP 2.0 采用了二进制格式而非文本格式,有效地减少了数据传输量,提高了数据传输的可靠性和效率。
- 多路复用(Multiplexing):HTTP 2.0 支持在一个单一的 TCP 连接上并发地处理多个请求和响应,显著减少了建立新连接的开销和延迟。
- 头部压缩(Header Compression):通过使用 HPACK 算法对请求和响应头信息进行压缩,减少了数据传输量。
- 服务器推送(Server Push):允许服务器在客户端请求之前主动推送资源到客户端,提高了客户端的响应速度。
- 流量控制(Flow Control):通过流控制窗口和流控制令牌等机制,防止客户端或服务器端接收数据过快而无法处理的情况。
- 安全性增强:HTTP 2.0 强制使用 TLS(Transport Layer Security)加密协议,提供了更强的安全性保护。
24. js 数组和链表有什么关系和区别?
- 数组和链表是两种常见的数据结构 用于存储元素
- 不同点:内部实现 访问方式 性能特性
- 使用场景:
- 数组适用于需要频繁访问元素且元素数量相对固定的场景
- 链表适用于需要频繁插入和删除元素且元素数量不确定的场景
- 数组:
- 内部实现:数组在内存中是一段连续的空间 每个元素都占用相同内存的大小 通过索引可以快速的访问任意位置的元素
- 访问方式:数组提供索引进行访问 可以快速访问任意元素(时间复杂度为 O(1))
- 插入和删除:在数组开头、中间插入删除元素 会移动其他元素 来保证数组的连续性 因此效率较低(时间复杂度 O(n)) 在数组末尾插入或删除元素效率高(时间 O(1))
- 链表:
- 链表中的节点在内存中的存储是不连续的 每个节点包含该元素和指向下一节点的指针 通过指针链表中的节点可以连接起来形成链式结构
- 访问方式:需要从头开始遍历 直到目标节点 速度比数组慢(时间复杂度 O(n))
- 插入和删除:在链表的开头或中间插入元素只需要修改相关节点的指针 而不需要移动其他节点 效率较高(时间复杂度 O(1))
25. js 中数组通过索引取值的底层原理?
- 涉及到 js 引擎内部对数组的内存管理和数据结构实现
- 内存布局:js 引擎为数组分配了一块连续的内存空间来存储数组元素 这些元素会按照他们在数组的顺序进行排列
- 索引与偏移量:当通过索引取值时 js 引擎会计算该元素在内存中的偏移量 该偏移量通常为索引乘以元素的大小(js 中大多数类型 数字、字符串会对象引用等 他们的大小都是固定的) 然后 js 引擎会通过这个偏移量从数组的起始地址开始查找
26. 防抖的机制和适用场景?
27. 如果不做防抖有什么问题?
- 性能消耗过大:如果事件频繁被触发(窗口大小调整 页面滚动 输入事件等) 如果每次都执行 会消耗大量的 CPU 资源 降低页面性能
- UI 响应迟缓:如果回调函数中执行了 DOM 操作 频繁出发会阻塞 UI 线程
- 资源浪费:如果回调函数中涉及到网络请求或数据库操作 会导致大量的资源浪费