Skip to content

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

2. 作用域和作用域链

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

答案

作用域: 作用域是变量和函数的可访问范围,决定了代码中变量和函数的可见性。

作用域类型

  1. 全局作用域
javascript
var globalVar = 'global';

function test() {
  console.log(globalVar);  // 可以访问全局变量
}

test();  // 'global'
  1. 函数作用域
javascript
function test() {
  var functionVar = 'function';
  console.log(functionVar);  // 可以访问
}

console.log(functionVar);  // ReferenceError: functionVar is not defined
  1. 块级作用域(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

闭包应用场景

  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'
  1. 函数柯里化
javascript
function multiply(a) {
  return function(b) {
    return function(c) {
      return a * b * c;
    };
  };
}

const result = multiply(2)(3)(4);  // 24
  1. 防抖和节流
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);
    }
  };
}
  1. 模块模式
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 的指向

  1. 默认绑定
javascript
function test() {
  console.log(this);  // 全局对象(浏览器中是 window)
}

test();
  1. 隐式绑定
javascript
const obj = {
  name: 'John',
  sayName: function() {
    console.log(this.name);  // 'John'
  }
};

obj.sayName();
  1. 显式绑定
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'
  1. new 绑定
javascript
function Person(name) {
  this.name = name;
}

const person = new Person('John');
console.log(person.name);  // 'John'
  1. 箭头函数
javascript
const obj = {
  name: 'John',
  sayName: function() {
    const arrow = () => {
      console.log(this.name);  // 'John'(继承外层 this)
    };
    arrow();
  }
};

obj.sayName();

this 优先级: new 绑定 > 显式绑定 > 隐式绑定 > 默认绑定

6. 继承

问题:JavaScript 中有哪些继承方式?

答案

继承方式

  1. 原型链继承
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'
  1. 构造函数继承
javascript
function Parent(name) {
  this.name = name;
}

function Child(name) {
  Parent.call(this, name);
}

const child = new Child('child');
console.log(child.name);  // 'child'
  1. 组合继承
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'
  1. 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();   // 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);
};