1. JavaScript 是单线程的吗?
没错,JavaScript 是 一种 单线程语言。这意味着它只有 一个调用栈和一个内存堆。每次只执行一组指令。
此外,JavaScript 是同步和阻塞 的性质。这意味着代码是逐行执行的,一个任务必须在下一个任务开始之前完成。
然而,JavaScript 也具有异步能力,允许某些操作独立于主执行线程执行。这通常通过回调、Promise、async/await 和事件监听器等机制实现。这些异步特性使 JavaScript 能够处理诸如获取数据、处理用户输入和执行 I/O 操作等任务,而不会阻塞主线程,使其适用于构建响应式和交互式的 Web 应用程序。
2. 解释 JavaScript 引擎的主要组件及其工作原理。
每个浏览器都有一个 JavaScript 引擎,用于执行 JavaScript 代码并将其转换为机器码。
当执行 JavaScript 代码时,解析器首先读取代码并生成 AST,然后将其存储在内存中。解释器接着处理这个 AST 并生成字节码或机器码,计算机执行它。
性能分析器是 JavaScript 引擎的一个组件,监视 代码的执行情况。
字节码与性能分析数据一起被优化编译器使用。 “优化编译器” 或即时 (JIT) 编译器根据性能分析数据做出某些假设,并生成高度优化的机器码。
有时候,“优化” 的假设是不正确的,然后它通过 “取消优化” 阶段(对我们来说实际上会产生开销)回到之前的版本。
JS 引擎通常会优化 “热门函数”,并使用内联缓存技术优化代码。
在此过程中,调用栈跟踪当前正在执行的函数,内存堆用于内存分配。
最后,垃圾收集器开始管理内存,从未使用的对象中回收内存。
Google Chrome 𝗩𝟴 引擎:
- 解释器称为 “Ignition”。
- 优化编译器称为 “TurboFan”。
- 除了解析器外,还有一个 “pre-parser” 用于检查语法和标记。
- 引入了 “Sparkplug”,位于 “Ignition” 和 “TurboFan” 之间,也称为 快速编译器。
3. 解释 JavaScript 中的事件循环。
事件循环是 JavaScript 运行时环境的核心组件。它负责调度和执行异步任务。事件循环通过持续监视两个队列来工作:调用栈和事件队列。
调用栈 是一个 堆栈(后进先出)数据结构,用于存储当前正在执行的函数(存储代码执行期间创建的执行上下文)。
Web API 是异步操作(setTimeout、fetch 请求、Promise)和它们的回调等待完成的地方。它从线程池中借用线程以在后台完成任务,而不会阻塞主线程。
作业队列(或微任务) 是一个 先进先出 结构,其中包含 准备执行的异步/等待、Promise、process.nextTick() 的回调。例如,已完成 Promise 的 resolve 或 reject 回调会排队在作业队列中。
任务队列(或宏任务) 是一个 先进先出 结构,其中包含 准备执行的异步操作的回调(类似于定时器 setInterval、setTimeout)。例如,已超时的 setTimeout()
的回调 - 准备执行 - 会排队在任务队列中。
事件循环持续监视 调用栈是否为空。如果调用栈为空,事件循环会查看作业队列或任务队列,并将准备执行的任何回调 出队到调用栈中执行。
4. JavaScript 中的不同数据类型
JavaScript 是一种动态且松散类型(也称鸭子类型)的语言。这意味着我们不需要指定变量的类型,因为 JavaScript 引擎会根据变量的值动态确定变量的数据类型。
JavaScript 中的原始数据类型是最基本的数据类型,表示单一值。它们是不可变的(无法更改),直接包含特定的值。
在 JavaScript 中,Symbol 是一种原始数据类型,在 ECMAScript 6 (ES6) 中引入,表示唯一且不可变的值。它通常用于作为对象属性的标识符,以避免命名冲突。
const mySymbol = Symbol('key');
const obj = {
[mySymbol]: 'value'
};
当 Symbol 被用作属性键时,它不会与其他属性键(包括字符串键)发生冲突。
5. 什么是回调函数和回调地狱?
在 JavaScript 中,回调通常用于处理异步操作。
回调函数 是一种作为参数传递给另一个函数并在特定任务完成或特定时间执行的函数。
function fetchData(url, callback) {
// 模拟从服务器获取数据
setTimeout(() => {
const data = 'Some data from the server';
callback(data);
}, 1000);
}
function processData(data) {
console.log('Processing data:', data);
}
fetchData('https://example.com/data', processData);
在这个例子中,fetchData
函数接受一个 URL 和一个回调函数作为参数。在从服务器获取数据后(使用 setTimeout
模拟),它调用回调函数并将检索到的数据传递给它。
回调地狱,也称为**“金字塔地狱”**,是指在 JavaScript 编程中,当多个嵌套回调用于异步函数时,会产生代码难以阅读和维护的情况。
“它发生在异步操作依赖于先前异步操作的结果时,导致深度嵌套且难以阅读的代码。”
回调地狱是一种反模式,包含多个嵌套回调,这使得处理异步逻辑的代码难以阅读和调试。
fs.readFile('file1.txt', 'utf8', function (err, data) {
if (err) {
console.error(err);
} else {
fs.readFile('file2.txt', 'utf8', function (err, data) {
if (err) {
console.error(err);
} else {
fs.readFile('file3.txt', 'utf8', function (err, data) {
if (err) {
console.error(err);
} else {
// 继续更多的嵌套回调...
}
});
}
});
}
});
在这个例子中,我们使用 fs.readFile
函数顺序读取三个文件,每个文件读取操作都是异步的。结果,我们不得不将回调嵌套在一起,形成一个回调金字塔。
为了避免回调地狱,现代 JavaScript 提供了像 Promise 和 async/await 这样的替代方案。 这里是使用 Promise 的相同代码:
const readFile = (file) => {
return new Promise((resolve, reject) => {
fs.readFile(file