JavaScript 面试题库
一、变量声明
1.1 let、const、var 的区别
面试回答思路:
这是 JavaScript 面试中的高频题,需要从多个维度来对比这三个关键字。
核心区别:
| 特性 | var | let | const |
|---|---|---|---|
| 作用域 | 函数作用域 | 块级作用域 | 块级作用域 |
| 变量提升 | 会提升 | 会提升但有暂时性死区 | 会提升但有暂时性死区 |
| 重复声明 | 允许 | 不允许 | 不允许 |
| 重新赋值 | 允许 | 允许 | 不允许 |
| 初始化 | 可选 | 可选 | 必须初始化 |
| 全局对象属性 | 是 | 否 | 否 |
1. 作用域(Scope)
var:函数作用域
1function testVar() {2 if (true) {3 var x = 104 }5 console.log(x) // 10,可以访问6}78// 全局作用域9var globalVar = 'global'10console.log(window.globalVar) // 'global'(浏览器环境)let 和 const:块级作用域
1function testLet() {2 if (true) {3 let x = 104 const y = 205 }6 console.log(x) // ReferenceError: x is not defined7 console.log(y) // ReferenceError: y is not defined8}910// 块级作用域示例11{12 let blockScoped = 'inside'13 const alsoBlockScoped = 'inside'14}15console.log(blockScoped) // ReferenceError为什么块级作用域重要:
- 避免变量污染
- 更好的代码组织
- 减少意外的变量覆盖
2. 变量提升(Hoisting)
var:会提升并初始化为 undefined
1console.log(x) // undefined(不会报错)2var x = 103console.log(x) // 1045// 等价于:6var x7console.log(x) // undefined8x = 109console.log(x) // 10let 和 const:会提升但不初始化(暂时性死区)
1console.log(x) // ReferenceError: Cannot access 'x' before initialization2let x = 1034console.log(y) // ReferenceError: Cannot access 'y' before initialization5const y = 2067// 暂时性死区(Temporal Dead Zone, TDZ)8{9 // TDZ 开始10 console.log(x) // ReferenceError11 let x = 10 // TDZ 结束12 console.log(x) // 1013}3. 重复声明
var:允许重复声明
1var x = 102var x = 20 // 不会报错3console.log(x) // 20let 和 const:不允许重复声明
1let x = 102let x = 20 // SyntaxError: Identifier 'x' has already been declared34const y = 105const y = 20 // SyntaxError: Identifier 'y' has already been declared4. 重新赋值
var 和 let:允许重新赋值
1var x = 102x = 20 // ✅ 可以34let y = 105y = 20 // ✅ 可以const:不允许重新赋值
1const z = 102z = 20 // ❌ TypeError: Assignment to constant variable34// 但是对象和数组的内容可以修改5const obj = { name: 'John' }6obj.name = 'Jane' // ✅ 可以修改属性7obj.age = 30 // ✅ 可以添加属性8obj = {} // ❌ 不能重新赋值910const arr = [1, 2, 3]11arr.push(4) // ✅ 可以修改数组12arr = [] // ❌ 不能重新赋值5. 循环中的表现
var 的问题:
1// ❌ 经典问题:循环中的 var2for (var i = 0; i < 3; i++) {3 setTimeout(() => {4 console.log(i) // 输出 3, 3, 35 }, 100)6}7// 原因:var 是函数作用域,所有回调共享同一个 i89// 解决方案 1:使用 IIFE10for (var i = 0; i < 3; i++) {11 (function(j) {12 setTimeout(() => {13 console.log(j) // 输出 0, 1, 214 }, 100)15 })(i)16}let 的优势:
1// ✅ 使用 let 自动解决2for (let i = 0; i < 3; i++) {3 setTimeout(() => {4 console.log(i) // 输出 0, 1, 25 }, 100)6}7// 原因:每次迭代都会创建新的块级作用域实际应用场景:
使用 var 的场景(几乎没有):
- 兼容老代码
- 需要函数作用域的特殊情况(极少)
使用 let 的场景:
1// 1. 需要重新赋值的变量2let count = 03count++45// 2. 循环计数器6for (let i = 0; i < 10; i++) {7 // ...8}910// 3. 条件赋值11let message12if (condition) {13 message = 'Yes'14} else {15 message = 'No'16}使用 const 的场景(推荐优先使用):
1// 1. 常量2const PI = 3.141593const API_URL = 'https://api.example.com'45// 2. 不会重新赋值的变量6const user = { name: 'John', age: 30 }7const numbers = [1, 2, 3, 4, 5]89// 3. 函数声明10const add = (a, b) => a + b1112// 4. 导入的模块13const React = require('react')14import { useState } from 'react'最佳实践:
-
优先使用 const
- 默认使用 const
- 让代码意图更明确
- 防止意外修改
-
需要重新赋值时使用 let
- 循环计数器
- 需要更新的变量
-
避免使用 var
- 现代 JavaScript 中几乎不需要 var
- 使用 let 和 const 可以避免很多问题
常见面试追问:
Q: const 声明的对象为什么可以修改属性?
A: const 保证的是变量指向的内存地址不变,而不是值不变。对于对象和数组,变量存储的是引用(内存地址),修改属性不会改变引用。
1const obj = { name: 'John' }2// obj 指向的内存地址:0x00134obj.name = 'Jane'5// obj 仍然指向:0x001(地址没变,只是内容变了)67obj = { name: 'Bob' }8// ❌ 试图让 obj 指向新地址:0x002(不允许)Q: 如何让对象完全不可修改?
A: 使用 Object.freeze()
1const obj = Object.freeze({ name: 'John' })2obj.name = 'Jane' // 严格模式下报错,非严格模式下静默失败3console.log(obj.name) // 'John'45// 深度冻结需要递归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 obj14}记忆技巧:
- var:老旧(Vintage)、可变(Variable)、危险(Vulnerable)
- let:让它改变(Let it change)
- const:常量(Constant)、不变(Consistent)
二、数据类型
2.1 JavaScript 的数据类型有哪些?
面试回答思路:
JavaScript 有 8 种数据类型,分为基本类型和引用类型。
基本类型(Primitive Types)- 7 种:
- Number(数字)
1const num1 = 422const num2 = 3.143const num3 = NaN // Not a Number4const num4 = Infinity5const num5 = -Infinity- String(字符串)
1const str1 = 'Hello'2const str2 = "World"3const str3 = `Template ${str1}` // 模板字符串- Boolean(布尔值)
1const bool1 = true2const bool2 = false- Undefined(未定义)
1let x2console.log(x) // undefined34function test() {}5console.log(test()) // undefined- Null(空值)
1const empty = null- Symbol(符号)- ES6
1const sym1 = Symbol('description')2const sym2 = Symbol('description')3console.log(sym1 === sym2) // false,每个 Symbol 都是唯一的- BigInt(大整数)- ES2020
1const bigNum = 9007199254740991n2const bigNum2 = BigInt(9007199254740991)引用类型(Reference Types)- 1 种:
Object(对象)
1// 普通对象2const obj = { name: 'John', age: 30 }34// 数组5const arr = [1, 2, 3]67// 函数8const func = function() {}910// 日期11const date = new Date()1213// 正则14const regex = /pattern/1516// Map17const map = new Map()1819// Set20const set = new Set()基本类型 vs 引用类型:
| 特性 | 基本类型 | 引用类型 |
|---|---|---|
| 存储位置 | 栈内存 | 堆内存(栈中存引用) |
| 赋值方式 | 值拷贝 | 引用拷贝 |
| 比较方式 | 值比较 | 引用比较 |
| 可变性 | 不可变 | 可变 |
1// 基本类型:值拷贝2let a = 103let b = a4b = 205console.log(a) // 10(a 不受影响)67// 引用类型:引用拷贝8let obj1 = { name: 'John' }9let obj2 = obj110obj2.name = 'Jane'11console.log(obj1.name) // 'Jane'(obj1 受影响)2.2 如何判断数据类型?
面试回答思路:
JavaScript 有多种判断数据类型的方法,每种方法都有其适用场景和局限性。
1. typeof 操作符
**适用场景:**判断基本类型(除了 null)
1typeof 42 // 'number'2typeof 'hello' // 'string'3typeof true // 'boolean'4typeof undefined // 'undefined'5typeof Symbol() // 'symbol'6typeof 123n // 'bigint'78// 函数9typeof function() {} // 'function'1011// 对象和数组12typeof {} // 'object'13typeof [] // 'object'14typeof null // 'object'(历史遗留问题)局限性:
- null 返回 'object'(这是一个历史遗留的 bug)
- 无法区分数组、对象、null
2. instanceof 操作符
**适用场景:**判断对象的具体类型
1[] instanceof Array // true2{} instanceof Object // true3function() {} instanceof Function // true45// 继承关系6class Animal {}7class Dog extends Animal {}8const dog = new Dog()910dog instanceof Dog // true11dog instanceof Animal // true12dog instanceof Object // true局限性:
- 只能判断对象类型
- 无法判断基本类型
- 跨 iframe 可能失效
3. Object.prototype.toString.call()
**适用场景:**最准确的类型判断(推荐)
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]'1314// 封装成工具函数15function getType(value) {16 return Object.prototype.toString.call(value).slice(8, -1).toLowerCase()17}1819getType(42) // 'number'20getType([]) // 'array'21getType({}) // 'object'22getType(null) // 'null'4. Array.isArray()
**适用场景:**专门判断数组
1Array.isArray([]) // true2Array.isArray({}) // false3Array.isArray('array') // false5. 其他特定判断
1// 判断 NaN2Number.isNaN(NaN) // true3Number.isNaN('hello') // false(推荐)45isNaN(NaN) // true6isNaN('hello') // true(不推荐,会先转换)78// 判断有限数字9Number.isFinite(42) // true10Number.isFinite(Infinity) // false1112// 判断整数13Number.isInteger(42) // true14Number.isInteger(3.14) // false完整的类型判断工具:
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 true26 if (typeof val === 'string') return val.length === 027 if (Array.isArray(val)) return val.length === 028 if (typeof val === 'object') return Object.keys(val).length === 029 return false30 },31 32 // 获取精确类型33 getType: (val) => {34 return Object.prototype.toString.call(val).slice(8, -1).toLowerCase()35 }36}3738// 使用示例39console.log(TypeChecker.isNumber(42)) // true40console.log(TypeChecker.isArray([])) // true41console.log(TypeChecker.isEmpty('')) // true42console.log(TypeChecker.getType(new Date())) // 'date'选择建议:
- 判断基本类型:使用 typeof(注意 null)
- 判断数组:使用 Array.isArray()
- 判断对象类型:使用 instanceof 或 Object.prototype.toString.call()
- 通用判断:使用 Object.prototype.toString.call()(最可靠)
三、总结
这份 JavaScript 面试题库目前涵盖了:
- ✅ 变量声明(let、const、var 的详细对比)
- ✅ 数据类型(8 种数据类型详解)
- ✅ 类型判断(多种判断方法和最佳实践)
后续会继续补充更多内容,包括:
- 作用域和闭包
- this 指向
- 原型和继承
- 异步编程
- ES6+ 新特性
- 等等...
建议结合实际代码练习,深入理解每个知识点。
评论区 / Comments