Skip to main content

JavaScript 面试题库

一、变量声明

1.1 let、const、var 的区别

面试回答思路:

这是 JavaScript 面试中的高频题,需要从多个维度来对比这三个关键字。

核心区别:

特性varletconst
作用域函数作用域块级作用域块级作用域
变量提升会提升会提升但有暂时性死区会提升但有暂时性死区
重复声明允许不允许不允许
重新赋值允许允许不允许
初始化可选可选必须初始化
全局对象属性

1. 作用域(Scope)

var:函数作用域

javascript
1function testVar() {
2 if (true) {
3 var x = 10
4 }
5 console.log(x) // 10,可以访问
6}
7
8// 全局作用域
9var globalVar = 'global'
10console.log(window.globalVar) // 'global'(浏览器环境)

let 和 const:块级作用域

javascript
1function testLet() {
2 if (true) {
3 let x = 10
4 const y = 20
5 }
6 console.log(x) // ReferenceError: x is not defined
7 console.log(y) // ReferenceError: y is not defined
8}
9
10// 块级作用域示例
11{
12 let blockScoped = 'inside'
13 const alsoBlockScoped = 'inside'
14}
15console.log(blockScoped) // ReferenceError

为什么块级作用域重要:

  • 避免变量污染
  • 更好的代码组织
  • 减少意外的变量覆盖

2. 变量提升(Hoisting)

var:会提升并初始化为 undefined

javascript
1console.log(x) // undefined(不会报错)
2var x = 10
3console.log(x) // 10
4
5// 等价于:
6var x
7console.log(x) // undefined
8x = 10
9console.log(x) // 10

let 和 const:会提升但不初始化(暂时性死区)

javascript
1console.log(x) // ReferenceError: Cannot access 'x' before initialization
2let x = 10
3
4console.log(y) // ReferenceError: Cannot access 'y' before initialization
5const y = 20
6
7// 暂时性死区(Temporal Dead Zone, TDZ)
8{
9 // TDZ 开始
10 console.log(x) // ReferenceError
11 let x = 10 // TDZ 结束
12 console.log(x) // 10
13}

3. 重复声明

var:允许重复声明

javascript
1var x = 10
2var x = 20 // 不会报错
3console.log(x) // 20

let 和 const:不允许重复声明

javascript
1let x = 10
2let x = 20 // SyntaxError: Identifier 'x' has already been declared
3
4const y = 10
5const y = 20 // SyntaxError: Identifier 'y' has already been declared

4. 重新赋值

var 和 let:允许重新赋值

javascript
1var x = 10
2x = 20 // ✅ 可以
3
4let y = 10
5y = 20 // ✅ 可以

const:不允许重新赋值

javascript
1const z = 10
2z = 20 // ❌ TypeError: Assignment to constant variable
3
4// 但是对象和数组的内容可以修改
5const obj = { name: 'John' }
6obj.name = 'Jane' // ✅ 可以修改属性
7obj.age = 30 // ✅ 可以添加属性
8obj = {} // ❌ 不能重新赋值
9
10const arr = [1, 2, 3]
11arr.push(4) // ✅ 可以修改数组
12arr = [] // ❌ 不能重新赋值

5. 循环中的表现

var 的问题:

javascript
1// ❌ 经典问题:循环中的 var
2for (var i = 0; i < 3; i++) {
3 setTimeout(() => {
4 console.log(i) // 输出 3, 3, 3
5 }, 100)
6}
7// 原因:var 是函数作用域,所有回调共享同一个 i
8
9// 解决方案 1:使用 IIFE
10for (var i = 0; i < 3; i++) {
11 (function(j) {
12 setTimeout(() => {
13 console.log(j) // 输出 0, 1, 2
14 }, 100)
15 })(i)
16}

let 的优势:

javascript
1// ✅ 使用 let 自动解决
2for (let i = 0; i < 3; i++) {
3 setTimeout(() => {
4 console.log(i) // 输出 0, 1, 2
5 }, 100)
6}
7// 原因:每次迭代都会创建新的块级作用域

实际应用场景:

使用 var 的场景(几乎没有):

  • 兼容老代码
  • 需要函数作用域的特殊情况(极少)

使用 let 的场景:

javascript
1// 1. 需要重新赋值的变量
2let count = 0
3count++
4
5// 2. 循环计数器
6for (let i = 0; i < 10; i++) {
7 // ...
8}
9
10// 3. 条件赋值
11let message
12if (condition) {
13 message = 'Yes'
14} else {
15 message = 'No'
16}

使用 const 的场景(推荐优先使用):

javascript
1// 1. 常量
2const PI = 3.14159
3const API_URL = 'https://api.example.com'
4
5// 2. 不会重新赋值的变量
6const user = { name: 'John', age: 30 }
7const numbers = [1, 2, 3, 4, 5]
8
9// 3. 函数声明
10const add = (a, b) => a + b
11
12// 4. 导入的模块
13const React = require('react')
14import { useState } from 'react'

最佳实践:

  1. 优先使用 const

    • 默认使用 const
    • 让代码意图更明确
    • 防止意外修改
  2. 需要重新赋值时使用 let

    • 循环计数器
    • 需要更新的变量
  3. 避免使用 var

    • 现代 JavaScript 中几乎不需要 var
    • 使用 let 和 const 可以避免很多问题

常见面试追问:

Q: const 声明的对象为什么可以修改属性?

A: const 保证的是变量指向的内存地址不变,而不是值不变。对于对象和数组,变量存储的是引用(内存地址),修改属性不会改变引用。

javascript
1const obj = { name: 'John' }
2// obj 指向的内存地址:0x001
3
4obj.name = 'Jane'
5// obj 仍然指向:0x001(地址没变,只是内容变了)
6
7obj = { name: 'Bob' }
8// ❌ 试图让 obj 指向新地址:0x002(不允许)

Q: 如何让对象完全不可修改?

A: 使用 Object.freeze()

javascript
1const obj = Object.freeze({ name: 'John' })
2obj.name = 'Jane' // 严格模式下报错,非严格模式下静默失败
3console.log(obj.name) // 'John'
4
5// 深度冻结需要递归
6function deepFreeze(obj) {
7 Object.freeze(obj)
8 Object.values(obj).forEach(value => {
9 if (typeof value === 'object' && value !== null) {
10 deepFreeze(value)
11 }
12 })
13 return obj
14}

记忆技巧:

  • var:老旧(Vintage)、可变(Variable)、危险(Vulnerable)
  • let:让它改变(Let it change)
  • const:常量(Constant)、不变(Consistent)

二、数据类型

2.1 JavaScript 的数据类型有哪些?

面试回答思路:

JavaScript 有 8 种数据类型,分为基本类型和引用类型。

基本类型(Primitive Types)- 7 种:

  1. Number(数字)
javascript
1const num1 = 42
2const num2 = 3.14
3const num3 = NaN // Not a Number
4const num4 = Infinity
5const num5 = -Infinity
  1. String(字符串)
javascript
1const str1 = 'Hello'
2const str2 = "World"
3const str3 = `Template ${str1}` // 模板字符串
  1. Boolean(布尔值)
javascript
1const bool1 = true
2const bool2 = false
  1. Undefined(未定义)
javascript
1let x
2console.log(x) // undefined
3
4function test() {}
5console.log(test()) // undefined
  1. Null(空值)
javascript
1const empty = null
  1. Symbol(符号)- ES6
javascript
1const sym1 = Symbol('description')
2const sym2 = Symbol('description')
3console.log(sym1 === sym2) // false,每个 Symbol 都是唯一的
  1. BigInt(大整数)- ES2020
javascript
1const bigNum = 9007199254740991n
2const bigNum2 = BigInt(9007199254740991)

引用类型(Reference Types)- 1 种:

Object(对象)

javascript
1// 普通对象
2const obj = { name: 'John', age: 30 }
3
4// 数组
5const arr = [1, 2, 3]
6
7// 函数
8const func = function() {}
9
10// 日期
11const date = new Date()
12
13// 正则
14const regex = /pattern/
15
16// Map
17const map = new Map()
18
19// Set
20const set = new Set()

基本类型 vs 引用类型:

特性基本类型引用类型
存储位置栈内存堆内存(栈中存引用)
赋值方式值拷贝引用拷贝
比较方式值比较引用比较
可变性不可变可变
javascript
1// 基本类型:值拷贝
2let a = 10
3let b = a
4b = 20
5console.log(a) // 10(a 不受影响)
6
7// 引用类型:引用拷贝
8let obj1 = { name: 'John' }
9let obj2 = obj1
10obj2.name = 'Jane'
11console.log(obj1.name) // 'Jane'(obj1 受影响)

2.2 如何判断数据类型?

面试回答思路:

JavaScript 有多种判断数据类型的方法,每种方法都有其适用场景和局限性。

1. typeof 操作符

**适用场景:**判断基本类型(除了 null)

javascript
1typeof 42 // 'number'
2typeof 'hello' // 'string'
3typeof true // 'boolean'
4typeof undefined // 'undefined'
5typeof Symbol() // 'symbol'
6typeof 123n // 'bigint'
7
8// 函数
9typeof function() {} // 'function'
10
11// 对象和数组
12typeof {} // 'object'
13typeof [] // 'object'
14typeof null // 'object'(历史遗留问题)

局限性:

  • null 返回 'object'(这是一个历史遗留的 bug)
  • 无法区分数组、对象、null

2. instanceof 操作符

**适用场景:**判断对象的具体类型

javascript
1[] instanceof Array // true
2{} instanceof Object // true
3function() {} instanceof Function // true
4
5// 继承关系
6class Animal {}
7class Dog extends Animal {}
8const dog = new Dog()
9
10dog instanceof Dog // true
11dog instanceof Animal // true
12dog instanceof Object // true

局限性:

  • 只能判断对象类型
  • 无法判断基本类型
  • 跨 iframe 可能失效

3. Object.prototype.toString.call()

**适用场景:**最准确的类型判断(推荐)

javascript
1Object.prototype.toString.call(42) // '[object Number]'
2Object.prototype.toString.call('hello') // '[object String]'
3Object.prototype.toString.call(true) // '[object Boolean]'
4Object.prototype.toString.call(undefined) // '[object Undefined]'
5Object.prototype.toString.call(null) // '[object Null]'
6Object.prototype.toString.call({}) // '[object Object]'
7Object.prototype.toString.call([]) // '[object Array]'
8Object.prototype.toString.call(function() {}) // '[object Function]'
9Object.prototype.toString.call(new Date()) // '[object Date]'
10Object.prototype.toString.call(/regex/) // '[object RegExp]'
11Object.prototype.toString.call(Symbol()) // '[object Symbol]'
12Object.prototype.toString.call(123n) // '[object BigInt]'
13
14// 封装成工具函数
15function getType(value) {
16 return Object.prototype.toString.call(value).slice(8, -1).toLowerCase()
17}
18
19getType(42) // 'number'
20getType([]) // 'array'
21getType({}) // 'object'
22getType(null) // 'null'

4. Array.isArray()

**适用场景:**专门判断数组

javascript
1Array.isArray([]) // true
2Array.isArray({}) // false
3Array.isArray('array') // false

5. 其他特定判断

javascript
1// 判断 NaN
2Number.isNaN(NaN) // true
3Number.isNaN('hello') // false(推荐)
4
5isNaN(NaN) // true
6isNaN('hello') // true(不推荐,会先转换)
7
8// 判断有限数字
9Number.isFinite(42) // true
10Number.isFinite(Infinity) // false
11
12// 判断整数
13Number.isInteger(42) // true
14Number.isInteger(3.14) // false

完整的类型判断工具:

javascript
1const TypeChecker = {
2 // 基础类型判断
3 isNumber: (val) => typeof val === 'number' && !isNaN(val),
4 isString: (val) => typeof val === 'string',
5 isBoolean: (val) => typeof val === 'boolean',
6 isUndefined: (val) => typeof val === 'undefined',
7 isNull: (val) => val === null,
8 isSymbol: (val) => typeof val === 'symbol',
9 isBigInt: (val) => typeof val === 'bigint',
10
11 // 引用类型判断
12 isObject: (val) => val !== null && typeof val === 'object',
13 isArray: (val) => Array.isArray(val),
14 isFunction: (val) => typeof val === 'function',
15 isDate: (val) => val instanceof Date,
16 isRegExp: (val) => val instanceof RegExp,
17
18 // 特殊判断
19 isNaN: (val) => Number.isNaN(val),
20 isFinite: (val) => Number.isFinite(val),
21 isInteger: (val) => Number.isInteger(val),
22
23 // 空值判断
24 isEmpty: (val) => {
25 if (val === null || val === undefined) return true
26 if (typeof val === 'string') return val.length === 0
27 if (Array.isArray(val)) return val.length === 0
28 if (typeof val === 'object') return Object.keys(val).length === 0
29 return false
30 },
31
32 // 获取精确类型
33 getType: (val) => {
34 return Object.prototype.toString.call(val).slice(8, -1).toLowerCase()
35 }
36}
37
38// 使用示例
39console.log(TypeChecker.isNumber(42)) // true
40console.log(TypeChecker.isArray([])) // true
41console.log(TypeChecker.isEmpty('')) // true
42console.log(TypeChecker.getType(new Date())) // 'date'

选择建议:

  1. 判断基本类型:使用 typeof(注意 null)
  2. 判断数组:使用 Array.isArray()
  3. 判断对象类型:使用 instanceof 或 Object.prototype.toString.call()
  4. 通用判断:使用 Object.prototype.toString.call()(最可靠)

三、总结

这份 JavaScript 面试题库目前涵盖了:

  1. ✅ 变量声明(let、const、var 的详细对比)
  2. ✅ 数据类型(8 种数据类型详解)
  3. ✅ 类型判断(多种判断方法和最佳实践)

后续会继续补充更多内容,包括:

  • 作用域和闭包
  • this 指向
  • 原型和继承
  • 异步编程
  • ES6+ 新特性
  • 等等...

建议结合实际代码练习,深入理解每个知识点。

forum

评论区 / Comments