Appearance
JavaScript 基础面试题
1. JavaScript 数据类型
问题:JavaScript 有哪些数据类型?如何判断数据类型?
答案:
JavaScript 有 8 种数据类型,包括 7 种基本类型和 1 种引用类型。基本类型包括 undefined 表示未定义,null 表示空值,boolean 表示布尔值,number 表示数字,string 表示字符串,symbol 表示符号(ES6),bigint 表示大整数(ES2020)。引用类型是 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 中的作用域类型包括全局作用域、函数作用域和块级作用域三种。全局作用域中的变量在整个程序中都可以访问,通常在函数外部声明的变量具有全局作用域。函数作用域是指在函数内部声明的变量只能在函数内部访问,函数外部无法访问。块级作用域是 ES6 引入的特性,使用 let 和 const 声明的变量具有块级作用域,只在声明的代码块内有效,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 中一个重要概念,指的是有权访问另一个函数作用域中变量的函数。闭包在实际开发中有四个主要应用场景:数据私有化利用闭包创建私有变量,外部无法直接访问,只能通过返回的接口方法操作,这在面向对象编程中非常有用;函数柯里化利用闭包实现多参数函数的转换,将接受多个参数的函数转换为接受单一参数的函数序列,便于函数复用和延迟执行;防抖和节流利用闭包保存定时器状态,分别实现函数执行频率控制,常用于优化事件处理和 API 调用;模块模式利用闭包创建私有作用域,实现模块化开发,隐藏内部实现细节,只暴露必要的接口。
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 中 this 的指向取决于函数的调用方式,共有五种绑定规则。默认绑定是指在严格模式下调用独立函数时,this 指向 undefined,非严格模式下指向全局对象;隐式绑定是指通过对象属性方法调用时,this 指向该对象;显式绑定是指使用 call、apply、bind 方法显式指定 this 的值;new 绑定是指使用 new 关键字调用构造函数时,this 指向新创建的对象;箭头函数的 this 继承外层作用域的 this,箭头函数本身没有 this。这五种绑定的优先级从高到低依次为 new 绑定 > 显式绑定 > 隐式绑定 > 默认绑定。
6. 继承
问题:JavaScript 中有哪些继承方式?
答案:
继承方式:JavaScript 中有四种常见的继承方式。原型链继承通过将子类的原型设置为父类的实例来实现继承,这是最基础的继承方式,但会导致父类实例属性变为原型属性,无法传参;构造函数继承通过在子类构造函数中调用父类构造函数来实现继承,可以传参但无法继承父类原型上的方法;组合继承结合了原型链继承和构造函数继承的优点,既能传参也能继承原型方法,是最常用的继承方式;ES6 类继承是现代 JavaScript 的标准继承方式,使用 class 关键字和 extends 关键字实现继承,语法更加清晰,是目前推荐使用的继承方式。 console.log(this.age); } }
const child = new Child('John', 20); child.sayName(); // 'John' child.sayAge(); // 20
## 7. 深拷贝和浅拷贝
**问题**:如何实现深拷贝和浅拷贝?
**答案**:
**浅拷贝**:
只复制对象的第一层属性,嵌套对象仍然是引用。
```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);
};