Appearance
JavaScript 基础面试题
1. JavaScript 数据类型
问题:JavaScript 有哪些数据类型?如何判断数据类型?
答案:
JavaScript 有 8 种数据类型:
基本类型(7种):
undefined- 未定义null- 空值boolean- 布尔值number- 数字string- 字符串symbol- 符号(ES6)bigint- 大整数(ES2020)
引用类型(1种):
object- 对象(包括 Array、Function、Date 等)
判断数据类型:
javascript
// typeof - 判断基本类型
typeof undefined; // 'undefined'
typeof null; // 'object'(历史遗留问题)
typeof true; // 'boolean'
typeof 42; // 'number'
typeof 'hello'; // 'string'
typeof Symbol(); // 'symbol'
typeof 123n; // 'bigint'
typeof {}; // 'object'
typeof []; // 'object'
typeof function(){}; // 'function'
// instanceof - 判断对象类型
[] instanceof Array; // true
{} instanceof Object; // true
new Date() instanceof Date; // true
// Object.prototype.toString() - 最准确的方式
function getType(obj) {
return Object.prototype.toString.call(obj).slice(8, -1).toLowerCase();
}
getType(null); // 'null'
getType([]); // 'array'
getType(new Date()); // 'date'
getType(function(){}); // 'function'
getType(/test/); // 'regexp'
// Array.isArray() - 判断数组
Array.isArray([]); // true
Array.isArray({}); // false
// isNaN() - 判断 NaN
isNaN(NaN); // true
isNaN('abc'); // true
// Number.isNaN() - 更准确的 NaN 判断
Number.isNaN(NaN); // true
Number.isNaN('abc'); // false2. 作用域和作用域链
问题:什么是作用域?什么是作用域链?
答案:
作用域: 作用域是变量和函数的可访问范围,决定了代码中变量和函数的可见性。
作用域类型:
- 全局作用域:
javascript
var globalVar = 'global';
function test() {
console.log(globalVar); // 可以访问全局变量
}
test(); // 'global'- 函数作用域:
javascript
function test() {
var functionVar = 'function';
console.log(functionVar); // 可以访问
}
console.log(functionVar); // ReferenceError: functionVar is not defined- 块级作用域(ES6):
javascript
// let 和 const 具有块级作用域
if (true) {
let blockVar = 'block';
console.log(blockVar); // 可以访问
}
console.log(blockVar); // ReferenceError: blockVar is not defined
// var 没有块级作用域
if (true) {
var varVar = 'var';
}
console.log(varVar); // 可以访问作用域链: 作用域链是变量查找机制,当访问一个变量时,会从当前作用域开始查找,如果没有找到,就向上一级作用域查找,直到全局作用域。
javascript
var globalVar = 'global';
function outer() {
var outerVar = 'outer';
function inner() {
var innerVar = 'inner';
console.log(innerVar); // 当前作用域
console.log(outerVar); // 外层作用域
console.log(globalVar); // 全局作用域
}
inner();
}
outer();3. 闭包
问题:什么是闭包?闭包有什么应用场景?
答案:
闭包定义: 闭包是指函数能够访问其词法作用域外的变量,即使该函数在其词法作用域之外执行。
闭包示例:
javascript
function createCounter() {
let count = 0;
return {
increment: function() {
count++;
return count;
},
decrement: function() {
count--;
return count;
},
getCount: function() {
return count;
}
};
}
const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.getCount()); // 2
console.log(counter.decrement()); // 1闭包应用场景:
- 数据私有化:
javascript
function createPerson(name) {
let _name = name;
return {
getName: function() {
return _name;
},
setName: function(newName) {
_name = newName;
}
};
}
const person = createPerson('John');
console.log(person.getName()); // 'John'
person.setName('Jane');
console.log(person.getName()); // 'Jane'- 函数柯里化:
javascript
function multiply(a) {
return function(b) {
return function(c) {
return a * b * c;
};
};
}
const result = multiply(2)(3)(4); // 24- 防抖和节流:
javascript
// 防抖
function debounce(func, delay) {
let timer;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => func.apply(this, args), delay);
};
}
// 节流
function throttle(func, delay) {
let lastCall = 0;
return function(...args) {
const now = Date.now();
if (now - lastCall >= delay) {
lastCall = now;
func.apply(this, args);
}
};
}- 模块模式:
javascript
const module = (function() {
let privateVar = 'private';
function privateFunc() {
console.log('private function');
}
return {
publicVar: 'public',
publicFunc: function() {
privateFunc();
return privateVar;
}
};
})();
console.log(module.publicFunc()); // 'private function', 'private'4. 原型和原型链
问题:什么是原型?什么是原型链?
答案:
原型: 每个 JavaScript 对象都有一个内部属性 [[Prototype]](通过 __proto__ 访问),指向其原型对象。
javascript
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function() {
console.log('Hello, ' + this.name);
};
const person = new Person('John');
person.sayHello(); // 'Hello, John'
console.log(person.__proto__ === Person.prototype); // true原型链: 当访问对象的属性时,如果对象本身没有该属性,就会沿着原型链向上查找,直到找到该属性或到达原型链顶端。
javascript
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function() {
console.log('Hello, ' + this.name);
};
const person = new Person('John');
// 属性查找顺序
console.log(person.name); // 1. 查找对象本身
console.log(person.sayHello); // 2. 查找原型对象
console.log(person.toString); // 3. 查找 Object.prototype
// 原型链
person.__proto__ === Person.prototype; // true
Person.prototype.__proto__ === Object.prototype; // true
Object.prototype.__proto__ === null; // true原型继承:
javascript
// 原型继承
function Animal(name) {
this.name = name;
}
Animal.prototype.eat = function() {
console.log(this.name + ' is eating');
};
function Dog(name, breed) {
Animal.call(this, name);
this.breed = breed;
}
// 设置原型链
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.bark = function() {
console.log(this.name + ' is barking');
};
const dog = new Dog('Buddy', 'Golden Retriever');
dog.eat(); // 'Buddy is eating'
dog.bark(); // 'Buddy is barking'5. this 关键字
问题:JavaScript 中 this 指向什么?
答案:
this 的指向:
- 默认绑定:
javascript
function test() {
console.log(this); // 全局对象(浏览器中是 window)
}
test();- 隐式绑定:
javascript
const obj = {
name: 'John',
sayName: function() {
console.log(this.name); // 'John'
}
};
obj.sayName();- 显式绑定:
javascript
function sayName() {
console.log(this.name);
}
const obj = { name: 'John' };
// call
sayName.call(obj); // 'John'
// apply
sayName.apply(obj); // 'John'
// bind
const boundSayName = sayName.bind(obj);
boundSayName(); // 'John'- new 绑定:
javascript
function Person(name) {
this.name = name;
}
const person = new Person('John');
console.log(person.name); // 'John'- 箭头函数:
javascript
const obj = {
name: 'John',
sayName: function() {
const arrow = () => {
console.log(this.name); // 'John'(继承外层 this)
};
arrow();
}
};
obj.sayName();this 优先级: new 绑定 > 显式绑定 > 隐式绑定 > 默认绑定
6. 继承
问题:JavaScript 中有哪些继承方式?
答案:
继承方式:
- 原型链继承:
javascript
function Parent() {
this.name = 'parent';
}
Parent.prototype.sayName = function() {
console.log(this.name);
};
function Child() {}
Child.prototype = new Parent();
const child = new Child();
child.sayName(); // 'parent'- 构造函数继承:
javascript
function Parent(name) {
this.name = name;
}
function Child(name) {
Parent.call(this, name);
}
const child = new Child('child');
console.log(child.name); // 'child'- 组合继承:
javascript
function Parent(name) {
this.name = name;
}
Parent.prototype.sayName = function() {
console.log(this.name);
};
function Child(name) {
Parent.call(this, name);
}
Child.prototype = new Parent();
Child.prototype.constructor = Child;
const child = new Child('child');
child.sayName(); // 'child'- ES6 类继承:
javascript
class Parent {
constructor(name) {
this.name = name;
}
sayName() {
console.log(this.name);
}
}
class Child extends Parent {
constructor(name, age) {
super(name);
this.age = age;
}
sayAge() {
console.log(this.age);
}
}
const child = new Child('John', 20);
child.sayName(); // 'John'
child.sayAge(); // 207. 深拷贝和浅拷贝
问题:如何实现深拷贝和浅拷贝?
答案:
浅拷贝: 只复制对象的第一层属性,嵌套对象仍然是引用。
javascript
// Object.assign()
const obj1 = { a: 1, b: { c: 2 } };
const obj2 = Object.assign({}, obj1);
obj2.b.c = 3;
console.log(obj1.b.c); // 3(受影响)
// 展开运算符
const obj3 = { ...obj1 };
obj3.b.c = 4;
console.log(obj1.b.c); // 4(受影响)
// Array.slice()
const arr1 = [1, 2, [3, 4]];
const arr2 = arr1.slice();
arr2[2][0] = 5;
console.log(arr1[2][0]); // 5(受影响)深拷贝: 递归复制所有层级的属性。
javascript
// JSON.parse(JSON.stringify())
const obj1 = { a: 1, b: { c: 2 } };
const obj2 = JSON.parse(JSON.stringify(obj1));
obj2.b.c = 3;
console.log(obj1.b.c); // 2(不受影响)
// 限制:无法复制函数、undefined、Symbol、循环引用
// 手动实现深拷贝
function deepClone(obj, map = new WeakMap()) {
if (obj === null || typeof obj !== 'object') {
return obj;
}
if (map.has(obj)) {
return map.get(obj);
}
const clone = Array.isArray(obj) ? [] : {};
map.set(obj, clone);
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
clone[key] = deepClone(obj[key], map);
}
}
return clone;
}
// 使用 structuredClone()(现代浏览器)
const obj3 = structuredClone(obj1);8. 事件循环
问题:JavaScript 的事件循环是如何工作的?
答案:
事件循环: JavaScript 是单线程的,事件循环用于协调异步操作的执行。
执行顺序:
- 执行同步代码
- 执行微任务
- 执行宏任务
javascript
console.log('1');
setTimeout(() => {
console.log('2');
}, 0);
Promise.resolve().then(() => {
console.log('3');
});
console.log('4');
// 输出:1, 4, 3, 2宏任务和微任务:
javascript
// 宏任务
setTimeout(() => {}, 0);
setInterval(() => {}, 1000);
setImmediate(() => {});
I/O 操作;
// 微任务
Promise.then();
process.nextTick();
queueMicrotask();9. 数组方法
问题:常用的数组方法有哪些?
答案:
改变原数组:
javascript
// push - 添加元素到末尾
const arr = [1, 2, 3];
arr.push(4); // [1, 2, 3, 4]
// pop - 删除末尾元素
arr.pop(); // [1, 2, 3]
// shift - 删除开头元素
arr.shift(); // [2, 3]
// unshift - 添加元素到开头
arr.unshift(1); // [1, 2, 3]
// splice - 删除/插入元素
arr.splice(1, 1); // [1, 3]
arr.splice(1, 0, 2); // [1, 2, 3]
// sort - 排序
arr.sort((a, b) => a - b);
// reverse - 反转
arr.reverse(); // [3, 2, 1]不改变原数组:
javascript
// map - 映射
const doubled = arr.map(x => x * 2); // [2, 4, 6]
// filter - 过滤
const evens = arr.filter(x => x % 2 === 0); // [2]
// reduce - 归约
const sum = arr.reduce((acc, x) => acc + x, 0); // 6
// find - 查找
const found = arr.find(x => x > 1); // 2
// some - 是否存在
const hasEven = arr.some(x => x % 2 === 0); // true
// every - 是否全部满足
const allPositive = arr.every(x => x > 0); // true
// forEach - 遍历
arr.forEach(x => console.log(x));10. 异常处理
问题:JavaScript 中如何处理异常?
答案:
try-catch-finally:
javascript
try {
// 可能出错的代码
const result = JSON.parse('invalid json');
} catch (error) {
// 捕获错误
console.error('Error:', error.message);
} finally {
// 无论是否出错都会执行
console.log('Cleanup');
}Error 对象:
javascript
// 创建错误
const error = new Error('Something went wrong');
error.name = 'CustomError';
error.message = 'Detailed error message';
error.stack = 'Error stack trace';
// 抛出错误
throw error;Promise 错误处理:
javascript
// catch 方法
fetch('/api/data')
.then(response => response.json())
.catch(error => {
console.error('Fetch error:', error);
});
// async/await
async function fetchData() {
try {
const response = await fetch('/api/data');
const data = await response.json();
return data;
} catch (error) {
console.error('Fetch error:', error);
throw error;
}
}全局错误处理:
javascript
// 捕获未处理的错误
window.onerror = function(message, source, lineno, colno, error) {
console.error('Global error:', error);
};
// 捕获未处理的 Promise 拒绝
window.onunhandledrejection = function(event) {
console.error('Unhandled rejection:', event.reason);
};