一些乱七八糟的前端八股问题

三葉Leaves Author

给 LLM 的提示词:
下面,我需要你扮演一位专业、深入浅出的现代前端开发老师。我会给你发一系列面试题,你不光要告诉我怎么优雅的回答这些题目,还要帮我了解搞懂背后的知识点和原理,以便我能在遇到类似的问题的时候仍可以自己解决。

JS 部分

栈和堆访问速度哪个更快?

  1. 基本数据类型(Primitive Types),如 Number, String, Boolean, null, undefined, Symbol, BigInt。这些类型的值通常大小固定,结构简单。因此,它们的值会直接存储在栈内存中。

  2. 引用数据类型(Reference Types),主要是 Object(包括普通对象、数组、函数等)。这些类型的值大小不固定,可以非常复杂。

引用数据类型数据实体会存储在堆内存中,指向该实体的引用地址(或叫指针) 存在栈内存中。

关于栈和堆的访问速度栈的访问速度通常要比堆快得多

这主要是由它们各自的数据结构和管理方式决定的:

  • 结构上:栈是一种“后进先出”(LIFO)的线性数据结构,它的内存分配和释放非常有规律,只需要移动一个指针即可,操作非常高效。可以把它想象成一个只有一个开口的盒子,放东西(入栈)和取东西(出栈)都在这个开口,非常直接。

  • 内存管理上:栈内存由 JavaScript 引擎自动管理。当一个函数执行时,会创建一个执行上下文并压入栈中,函数执行完毕后,其执行上下文就会被自动弹出并销毁。这个过程非常快,开销很小。

  • 堆则不同:堆是一个相对无序的内存区域,用于存储大小不一的对象。在堆上分配内存需要引擎去寻找一个大小合适的空闲内存块,这个过程相对复杂。同样,垃圾回收器需要定期扫描堆内存,找出不再被引用的对象并回收它们,这个过程(垃圾回收)也比栈的自动弹出要复杂和耗时得多。

所以,总结来说,栈因为其简单的结构和高效的自动管理机制,访问速度远快于需要复杂分配和回收机制的堆。

setTimeout 和 setInterval 哪个更准确,为什么?

由于 JavaScript 事件循环机制的存在,两者都不是绝对准确的。它们都只能保证在指定的毫秒数之后,将回调函数推入任务队列,而真正的执行时机要等待主线程空闲。

但如果非要比较,使用链式调用的 setTimeout (即递归 setTimeout) 通常被认为比 setInterval 更准确、更可靠

  • 递归 setTimeout 的优势:它是在上一个回调函数执行完毕之后,才去设置下一个定时器。这就保证了两次执行之间至少会有一个我们设定的时间间隔。它不会因为单次任务执行时间过长而导致回调堆积,程序的执行节奏更加稳健和可预测。

所以,在需要循环执行异步任务的场景,比如轮询服务器状态,我通常会优先选择使用递归的 setTimeout 模式,以避免 setInterval 可能带来的问题。

工程部分

用 Vue 实现一个文本编辑器?

在 Vue 中如何渲染用户想要的自定义文本,比如有各种颜色、各种字体大小(类似于用 Vue 实现一个文本编辑器)?

  • 有一种办法是用 v-html 结合 DOMPurify 库来实现,后者用于防止 XSS 攻击。这个方法简单直接但是稍微有点 low ,我重点说下面一种。

我们使用 “数据驱动渲染”

更安全(因为我们不解析HTML),数据结构更清晰,也更便于做后续的逻辑处理和状态管理,更符合 Vue 的数据驱动思想,但可能需要自己实现或适配从编辑器到这种数据格式的转换。

我们不直接存储 HTML 字符串,而是将用户的文本内容解析成一个结构化的数据格式,通常是 JSON 数组。例如:

1
2
3
4
5
[
{ "text": "这是", "style": {} },
{ "text": "红色", "style": { "color": "red" } },
{ "text": "的文字", "style": {} }
]

然后,在 Vue 模板中,我们使用 v-for 遍历这个数组,为每一段文本动态地应用样式。比如用一个 <span> 标签包裹,并通过 :style 指令绑定它的样式对象。

至于把原始用户输入的数据变成结构化数据的方法,核心函数可见:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/**
* @description 核心转换函数:将 DOM 节点树转换为结构化数据数组
* @param {Node} parentNode - 要开始转换的父节点
*/
domToStructuredData(parentNode) {
const segments = [];
const childNodes = parentNode.childNodes;

for (const node of childNodes) {
// 1. 如果是文本节点 (Node.TEXT_NODE)
if (node.nodeType === 3) {
// 获取父元素的样式,作为当前文本的基础样式
const style = this.getInheritedStyle(node.parentNode);
segments.push({
text: node.textContent,
style: style,
});
}
// 2. 如果是元素节点 (Node.ELEMENT_NODE),通常是我们的 <span>
else if (node.nodeType === 1) {
// 递归!继续深入这个元素节点,处理它的子节点
// 递归返回的结果是一个数组,我们把它合并到当前的结果中
segments.push(...this.domToStructuredData(node));
}
// 其他节点类型(如注释节点)我们忽略
}

return segments;
},

/**
* @description 辅助函数:获取一个节点的有效样式
*/
getInheritedStyle(node) {
const style = {};
// 我们只关心我们定义的样式,这里简化为 color
if (node && node.style && node.style.color) {
style.color = node.style.color;
}
// 在真实场景中,这里会检查更多样式,如 fontSize, fontWeight 等
return style;
}
  • 标题: 一些乱七八糟的前端八股问题
  • 作者: 三葉Leaves
  • 创建于 : 2025-07-29 00:00:00
  • 更新于 : 2025-08-13 16:31:27
  • 链接: https://blog.oksanye.com/e49a8e80f586/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论