Skip to content

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'); // false

2. 作用域和作用域链

问题:什么是作用域?什么是作用域链?

答案

作用域:作用域是变量和函数的可访问范围,决定了代码中变量和函数的可见性。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 是单线程的,事件循环用于协调异步操作的执行。

执行顺序

  1. 执行同步代码
  2. 执行微任务
  3. 执行宏任务
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);
};