面试题[JavaScript]
1. JavaScript 基础知识
1.1 数据类型
JavaScript 中将数据类型分为基本数据类型和引用数据类型,它们其中有一个区别就是存储的位置不同。
基本数据类型:boolean、string、number、undefined、null、symbol、bigint。
引用数据类型:array、object。
1.2 typeof 和 instanceof 的区别
instanceof 原理手写
1.3 typeof null 的结果是什么?为什么?
在 JavaScript 中,typeof null
的结果是 "object"
。这是一个历史遗留问题,而不是出于设计上的考虑。
在 JavaScript 中,所有事物都是对象,但有一个例外:null
。然而,为了简化类型检测,typeof
操作符对于原始数据类型(如 number
、string
、boolean
)返回的是它们的小写形式字符串(如 "number"
、"string"
、"boolean"
),但对于复杂的数据类型(如对象和函数),它返回的是 "object"
。
原因可以追溯到 JavaScript 的早期版本,特别是 ECMAScript 3 和更早的版本。在 JavaScript 最初的实现中,JavaScript 中的值是由一个表示类型的标签和实际数据值表示的。对象的类型标签是 0。由于 null
代表的是空指针(大多数平台下值为 0x00),因此,null 的类型标签是 0,typeof null
也因此返回 "object"
。(参考来源 (opens new window))
曾有一个 ECMAScript 的修复提案(通过选择性加入的方式),但被拒绝了 (opens new window)。该提案会导致 typeof null === "null"
。
1.4 字符串的常用方法
1.5 数组的常用方法
// 判断变量是否是数组
Array.isArray(arr)
arr instanceof Array
Object.prototype.toString.call(arr) === '[object Array]'
1.6 对象的常用方法
1.7 变量提升
1.8 闭包和作用域
闭包定义
根据 JavaScript 中的词法作用域规则,内部函数总是可以访问其外部函数中声明的变量。当内部函数被返回到外部函数之外时,即使外部函数执行结束了,但是内部函数引用了外部函数的变量,这些变量仍然会被保存在内存中。这个现象称为闭包。
JavaScript 中常见的作用域包括全局作用域、函数作用域、块级作用域。
JavaScript 中自由变量的查找是在函数定义的地方,向上级作用域查找,不是在执行的地方。
闭包是作用域应用的特殊场景。常见的闭包使用有两种场景:一种是函数作为参数被传递;一种是函数作为返回值被返回。
怎么看待闭包的副作用?
1.9 原型和原型链
1.10 JavaScript 执行上下文、执行栈和闭包
1.11 对 this 的理解
1.12 new 操作符做了什么
1.13 继承
1.14 事件模型
事件冒泡(Event Bubbling)、事件捕获(Event Capturing)、事件委托(Event Delegation)。
1.15 事件循环
1.16 JavaScript 中的异步编程
1.17 JavaScript 中的严格模式
2. JavaScript 拓展知识
2.1 ECMA 标准从提案到发布有几个阶段?哪个阶段是具有里程碑意义的
ECMA标准从提案到发布经历了以下几个阶段:
- Stage 0 - Strawman(草案):这个阶段是最初的提案阶段,通常由个人或小组提出,并还没有经过正式的标准化流程。提案可能只是一个想法或初步的概念。
- Stage 1 - Proposal(提案):在这个阶段,提案开始进入正式的标准化流程。提案需要详细说明其功能、语法和语义,并且需要提供示例代码和使用案例。
- Stage 2 - Draft(草稿):在这个阶段,提案转化为一份详细的草稿,其中包含了具体的语法规范和语义定义。草稿需要经过审查和讨论,并且需要有多个独立实现的证明。
- Stage 3 - Candidate(候选):在这个阶段,提案已经足够成熟,可以被视为候选标准。这意味着提案已经通过了实际应用并经过广泛的测试和实现。
- Stage 4 - Finished(完成):在这个阶段,提案被接受为最终的标准,已经准备好发布。提案的规范细节已经完善,并且已经有多个独立实现通过了所有测试。
这些阶段代表了ECMA标准的不同发展阶段,其中最具里程碑意义的是Stage 4 - Finished(完成)阶段。在这个阶段,提案被接受为最终的标准,意味着它已经经过了广泛的实现、测试和审查,并被认为是稳定和可靠的。完成阶段的标准可以被广泛采用和应用于实际的编程环境中。
2.2 不创建新变量的前提下,如何交换两个变量的位置
- 解构赋值:适用于数组、对象和其他可迭代的数据结构。
let a = 10;
let b = 20;
[a, b] = [b, a];
- 位运算符 -- 位异或(^)运算符:只适用于数字类型的变量。
let a = 1;
let b = 2;
a = a ^ b;
b = a ^ b;
a = a ^ b;
- 加法和减法、乘法和除法:只适用于数字类型的变量。
let a = 1;
let b = 2;
a = a + b;
b = a - b;
a = a - b;
a = a * b;
b = a / b;
a = a / b;
2.3 JavaScript 文件相互引用有什么问题
可能导致代码执行错误或者无法正常工作。下面是一些常见的问题及其解决方法:
- 循环依赖(Circular Dependency): 循环依赖指的是两个或多个模块相互依赖,直接或间接地引用对方,导致模块无法正确加载。解决循环依赖问题的方法包括:
- 重构代码结构,将共享的逻辑抽离到单独的模块中,避免直接相互引用。
- 使用异步加载模块的方式,如
import()
动态导入语法,可以延迟加载模块,避免循环依赖问题。
- 加载顺序错误: 当 JavaScript 文件相互引用时,确保它们之间的加载顺序是正确的非常重要。如果加载顺序错误,可能会导致某些模块在使用时还未被加载,从而出现错误。解决加载顺序错误的方法包括:
- 明确定义模块之间的依赖关系,确保先加载依赖的模块,再加载依赖它们的模块。
- 使用模块打包工具(如 Webpack、Rollup 等)来管理模块之间的依赖关系和加载顺序,确保打包后的文件能正确加载所有模块。
- 使用命名导出和默认导出: 在模块间引用时,可以使用命名导出和默认导出来更清晰地定义模块之间的关系。通过明确导出和导入需要的函数、变量等,可以减少不必要的引用问题。
- 使用事件订阅/发布模式: 如果存在模块之间需要通信的情况,可以考虑使用事件订阅/发布模式(Event Emitter),模块之间通过事件进行通信,避免直接引用对方。
总的来说,避免 JavaScript 文件相互引用时的常见问题,需要注意模块之间的依赖关系、加载顺序以及合理设计模块之间的通信机制。通过合理的代码组织和模块化设计,可以有效减少相互引用带来的问题,并确保代码的可维护性和可扩展性。
2.4 CommonJS 和 ES Module 的区别
2.5 setTimeout 和 setInterval 的区别
2.6 for...in 和 for...of 的区别
2.7 深拷贝和浅拷贝的区别
2.8 Object.prototype.toString.call
2.9 JavaScript 数值精度问题
在 JavaScript中,由于浮点数的表示方式(基于IEEE 754 标准),我们有时会遇到数值精度问题。这些问题主要源于二进制浮点数的表示限制,导致无法精确表示所有的十进制小数。
以下是一些解决 JavaScript 数值精度问题的方法:
- toFixed() 方法:
- 当你需要将浮点数转换为字符串并保留一定的小数位数时,可以使用
toFixed()
方法。但请注意,toFixed()
返回的是一个字符串,而不是一个数字。
- 当你需要将浮点数转换为字符串并保留一定的小数位数时,可以使用
- Math.round(), Math.floor(), Math.ceil():
- 这些函数可以帮助你四舍五入、向下取整或向上取整到最接近的整数。
- 使用第三方库:
- 有些第三方库,如
decimal.js
或bignumber.js
,提供了高精度的十进制数运算。这些库可以处理比JavaScript内置Number类型更大或更精确的数值。
- 有些第三方库,如
在处理金融或需要高精度计算的场景时,使用第三方库(如 decimal.js
或 bignumber.js
)通常是一个好主意,因为它们提供了比JavaScript内置Number类型更高的精度和可靠性。