Skip to content
On This Page

面试题[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 操作符对于原始数据类型(如 numberstringboolean)返回的是它们的小写形式字符串(如 "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 数组的常用方法

javascript
// 判断变量是否是数组
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 继承

JavaScript实现继承

1.14 事件模型

事件冒泡(Event Bubbling)、事件捕获(Event Capturing)、事件委托(Event Delegation)。

1.15 事件循环

JavaScript事件循环机制

1.16 JavaScript 中的异步编程

1.17 JavaScript 中的严格模式

2. JavaScript 拓展知识

2.1 ECMA 标准从提案到发布有几个阶段?哪个阶段是具有里程碑意义的

ECMA标准从提案到发布经历了以下几个阶段:

  1. Stage 0 - Strawman(草案):这个阶段是最初的提案阶段,通常由个人或小组提出,并还没有经过正式的标准化流程。提案可能只是一个想法或初步的概念。
  2. Stage 1 - Proposal(提案):在这个阶段,提案开始进入正式的标准化流程。提案需要详细说明其功能、语法和语义,并且需要提供示例代码和使用案例。
  3. Stage 2 - Draft(草稿):在这个阶段,提案转化为一份详细的草稿,其中包含了具体的语法规范和语义定义。草稿需要经过审查和讨论,并且需要有多个独立实现的证明。
  4. Stage 3 - Candidate(候选):在这个阶段,提案已经足够成熟,可以被视为候选标准。这意味着提案已经通过了实际应用并经过广泛的测试和实现。
  5. Stage 4 - Finished(完成):在这个阶段,提案被接受为最终的标准,已经准备好发布。提案的规范细节已经完善,并且已经有多个独立实现通过了所有测试。

这些阶段代表了ECMA标准的不同发展阶段,其中最具里程碑意义的是Stage 4 - Finished(完成)阶段。在这个阶段,提案被接受为最终的标准,意味着它已经经过了广泛的实现、测试和审查,并被认为是稳定和可靠的。完成阶段的标准可以被广泛采用和应用于实际的编程环境中。

2.2 不创建新变量的前提下,如何交换两个变量的位置

  • 解构赋值:适用于数组、对象和其他可迭代的数据结构。
javascript
let a = 10;
let b = 20;
[a, b] = [b, a];
  • 位运算符 -- 位异或(^)运算符:只适用于数字类型的变量。
javascript
let a = 1;
let b = 2;
a = a ^ b;
b = a ^ b;
a = a ^ b;
  • 加法和减法、乘法和除法:只适用于数字类型的变量。
javascript
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 文件相互引用有什么问题

可能导致代码执行错误或者无法正常工作。下面是一些常见的问题及其解决方法:

  1. 循环依赖(Circular Dependency): 循环依赖指的是两个或多个模块相互依赖,直接或间接地引用对方,导致模块无法正确加载。解决循环依赖问题的方法包括:
    • 重构代码结构,将共享的逻辑抽离到单独的模块中,避免直接相互引用。
    • 使用异步加载模块的方式,如 import() 动态导入语法,可以延迟加载模块,避免循环依赖问题。
  2. 加载顺序错误: 当 JavaScript 文件相互引用时,确保它们之间的加载顺序是正确的非常重要。如果加载顺序错误,可能会导致某些模块在使用时还未被加载,从而出现错误。解决加载顺序错误的方法包括:
    • 明确定义模块之间的依赖关系,确保先加载依赖的模块,再加载依赖它们的模块。
    • 使用模块打包工具(如 Webpack、Rollup 等)来管理模块之间的依赖关系和加载顺序,确保打包后的文件能正确加载所有模块。
  3. 使用命名导出和默认导出: 在模块间引用时,可以使用命名导出和默认导出来更清晰地定义模块之间的关系。通过明确导出和导入需要的函数、变量等,可以减少不必要的引用问题。
  4. 使用事件订阅/发布模式: 如果存在模块之间需要通信的情况,可以考虑使用事件订阅/发布模式(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 数值精度问题的方法:

  1. toFixed() 方法
    • 当你需要将浮点数转换为字符串并保留一定的小数位数时,可以使用 toFixed() 方法。但请注意,toFixed() 返回的是一个字符串,而不是一个数字。
  2. Math.round(), Math.floor(), Math.ceil()
    • 这些函数可以帮助你四舍五入、向下取整或向上取整到最接近的整数。
  3. 使用第三方库
    • 有些第三方库,如 decimal.jsbignumber.js,提供了高精度的十进制数运算。这些库可以处理比JavaScript内置Number类型更大或更精确的数值。

在处理金融或需要高精度计算的场景时,使用第三方库(如 decimal.jsbignumber.js)通常是一个好主意,因为它们提供了比JavaScript内置Number类型更高的精度和可靠性。