安庆市上往网

50道JavaScript高频面试题及答案总结大全

2026-03-30 07:19:01 浏览次数:1
详细信息

50道JavaScript高频面试题及答案总结

基础概念与语法

1. JavaScript的数据类型有哪些?

答案:

typeof null // "object" (历史遗留问题)
typeof []   // "object"
typeof function() {} // "function"

2. let、const和var的区别?

答案: | 特性 | var | let | const | |------|-----|-----|-------| | 作用域 | 函数作用域 | 块级作用域 | 块级作用域 | | 变量提升 | 是(初始化为undefined) | 是(TDZ暂时性死区) | 是(TDZ暂时性死区) | | 重复声明 | 允许 | 不允许 | 不允许 | | 初始值 | 可选 | 可选 | 必须 | | 可重新赋值 | 是 | 是 | 否(对于原始类型) |

3. 什么是闭包?有什么作用?

答案: 闭包是指函数能够访问并记住其词法作用域,即使函数在其词法作用域之外执行。

function createCounter() {
  let count = 0;
  return function() {
    count++;
    return count;
  };
}
const counter = createCounter();
counter(); // 1
counter(); // 2

作用

创建私有变量和方法 实现函数工厂和柯里化 模块模式 事件处理和回调

4. 原型和原型链是什么?

答案:

5. call、apply、bind的区别?

答案:

func.call(thisArg, arg1, arg2, ...)
func.apply(thisArg, [argsArray])
func.bind(thisArg, arg1, arg2, ...)

区别

function greet(greeting, punctuation) {
  console.log(greeting + ', ' + this.name + punctuation);
}

const person = { name: 'Alice' };

greet.call(person, 'Hello', '!');       // Hello, Alice!
greet.apply(person, ['Hi', '!!']);      // Hi, Alice!!
const boundGreet = greet.bind(person, 'Hey');
boundGreet('!!!');                      // Hey, Alice!!!

6. 事件循环(Event Loop)机制?

答案: JavaScript是单线程的,事件循环是其异步编程的核心。

执行顺序

执行同步任务 执行微任务(Microtask):Promise.then/catch/finally、queueMicrotask、MutationObserver 执行宏任务(Macrotask):setTimeout、setInterval、setImmediate、I/O、UI渲染 重复上述过程
console.log('1'); // 同步

setTimeout(() => {
  console.log('2'); // 宏任务
}, 0);

Promise.resolve().then(() => {
  console.log('3'); // 微任务
});

console.log('4'); // 同步

// 输出:1 4 3 2

7. 什么是深拷贝和浅拷贝?如何实现?

答案:

实现方式

// 浅拷贝
const shallowCopy1 = Object.assign({}, obj);
const shallowCopy2 = {...obj};
const shallowCopy3 = [];
arr.forEach(item => shallowCopy3.push(item));

// 深拷贝
const deepCopy1 = JSON.parse(JSON.stringify(obj)); // 缺点:不能处理函数、undefined、循环引用
const deepCopy2 = structuredClone(obj); // 现代API(Node 17+,浏览器支持)

手动实现深拷贝

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 (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      clone[key] = deepClone(obj[key], map);
    }
  }
  return clone;
}

8. Promise是什么?有哪些状态?

答案: Promise是异步编程的解决方案,有三种状态:

pending:初始状态 fulfilled:操作成功完成 rejected:操作失败
const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    Math.random() > 0.5 ? resolve('Success') : reject('Error');
  }, 1000);
});

promise
  .then(result => console.log(result))
  .catch(error => console.error(error))
  .finally(() => console.log('Done'));

9. async/await的原理?

答案: async/await是Promise的语法糖,基于Generator函数实现。

async function fetchData() {
  try {
    const response = await fetch('/api/data');
    const data = await response.json();
    return data;
  } catch (error) {
    console.error('Error:', error);
  }
}

// 等同于
function fetchData() {
  return fetch('/api/data')
    .then(response => response.json())
    .catch(error => console.error('Error:', error));
}

10. 箭头函数和普通函数的区别?

答案: | 特性 | 箭头函数 | 普通函数 | |------|---------|---------| | this绑定 | 继承外层作用域的this | 动态绑定,取决于调用方式 | | arguments对象 | 没有 | 有 | | 构造函数 | 不能作为构造函数 | 可以 | | prototype | 没有 | 有 | | 重复命名参数 | 不允许 | 严格模式下不允许 | | yield关键字 | 不能用作生成器 | 可以 |

11. 防抖和节流的区别与实现?

答案:

// 防抖:n秒后执行,n秒内重复触发则重新计时
function debounce(fn, delay) {
  let timer;
  return function(...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
}

// 节流:n秒内只执行一次
function throttle(fn, delay) {
  let canRun = true;
  return function(...args) {
    if (!canRun) return;
    canRun = false;
    setTimeout(() => {
      fn.apply(this, args);
      canRun = true;
    }, delay);
  };
}

// 使用
window.addEventListener('resize', debounce(handleResize, 200));
window.addEventListener('scroll', throttle(handleScroll, 100));

12. 什么是事件委托?

答案: 利用事件冒泡,将子元素的事件监听委托给父元素。

// 传统方式
document.querySelectorAll('.item').forEach(item => {
  item.addEventListener('click', handleClick);
});

// 事件委托
document.querySelector('.container').addEventListener('click', event => {
  if (event.target.classList.contains('item')) {
    handleClick(event);
  }
});

// 动态添加元素也有效
document.querySelector('.container').insertAdjacentHTML('beforeend', '<div class="item">New</div>');

13. 跨域问题及解决方案?

答案: 同源策略:协议、域名、端口相同

解决方案

CORS(推荐)

// 服务端设置响应头
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET,POST,PUT
Access-Control-Allow-Headers: Content-Type

JSONP

function handleResponse(data) {
  console.log(data);
}

const script = document.createElement('script');
script.src = 'http://api.example.com/data?callback=handleResponse';
document.body.appendChild(script);

代理服务器

WebSocket

postMessage(iframe通信)

Nginx反向代理

14. 什么是柯里化(Currying)?

答案: 将多参数函数转换为一系列单参数函数。

// 传统函数
function add(a, b, c) {
  return a + b + c;
}

// 柯里化实现
function curry(fn) {
  return function curried(...args) {
    if (args.length >= fn.length) {
      return fn.apply(this, args);
    } else {
      return function(...args2) {
        return curried.apply(this, args.concat(args2));
      };
    }
  };
}

const curriedAdd = curry(add);
console.log(curriedAdd(1)(2)(3)); // 6
console.log(curriedAdd(1, 2)(3)); // 6

15. 模块化的几种方式?

答案:

IIFE(立即执行函数表达式)

(function(global) {
  const module = {};
  // 私有变量
  let privateVar = 'secret';

  // 公有方法
  module.publicMethod = function() {
    return privateVar;
  };

  global.myModule = module;
})(window);

CommonJS(Node.js)

// module.js
module.exports = { name: 'module' };

// app.js
const module = require('./module.js');

AMD(Require.js)

define(['dependency'], function(dependency) {
  return { name: 'module' };
});

ES6 Module(现代标准)

// module.js
export const name = 'module';
export default function() {};

// app.js
import { name } from './module.js';
import myFunc from './module.js';

16. 什么是尾调用优化?

答案: 函数最后一步调用另一个函数,无需保留当前函数的调用记录。

// 普通递归(会产生调用栈)
function factorial(n) {
  if (n === 1) return 1;
  return n * factorial(n - 1); // 需要等待返回值
}

// 尾递归(可优化)
function factorial(n, total = 1) {
  if (n === 1) return total;
  return factorial(n - 1, n * total); // 直接返回,无需等待
}

17. 内存泄漏的常见情况?

答案:

意外的全局变量

function leak() {
  leakVar = 'leak'; // 没有var/let/const
  this.globalVar = 'leak'; // this指向window
}

闭包使用不当

未被清理的定时器和事件监听

// 错误
setInterval(() => {}, 1000);

// 正确
const timer = setInterval(() => {}, 1000);
clearInterval(timer);

DOM引用未释放

const elements = {
  button: document.getElementById('button')
};

// 即使从DOM移除,仍然在内存中
document.body.removeChild(document.getElementById('button'));

Map/Set中的对象引用

18. 如何判断数据类型?

答案:

// 1. typeof - 原始类型
typeof 'str'      // 'string'
typeof 123        // 'number'
typeof true       // 'boolean'
typeof undefined  // 'undefined'
typeof null       // 'object' (bug)
typeof []         // 'object'
typeof {}         // 'object'
typeof function(){} // 'function'

// 2. instanceof - 引用类型
[] instanceof Array      // true
{} instanceof Object     // true
new Date() instanceof Date // true

// 3. Object.prototype.toString - 万能方法
Object.prototype.toString.call('str')     // '[object String]'
Object.prototype.toString.call([])        // '[object Array]'
Object.prototype.toString.call(null)      // '[object Null]'

// 4. Array.isArray
Array.isArray([])  // true
Array.isArray({})  // false

// 封装通用方法
function getType(value) {
  return Object.prototype.toString.call(value)
    .slice(8, -1)
    .toLowerCase();
}

19. 数组的常见方法有哪些?

答案: 改变原数组pushpopshiftunshiftsplicesortreverse 不改变原数组concatslicemapfilterreduceforEachfindfindIndexsomeevery

// 链式调用示例
const result = arr
  .filter(item => item > 0)
  .map(item => item * 2)
  .reduce((sum, item) => sum + item, 0);

// flat和flatMap
const arr = [1, [2, [3]]];
arr.flat(2); // [1, 2, 3]

// 数组去重
const unique = [...new Set(arr)];
const unique2 = arr.filter((item, index) => arr.indexOf(item) === index);

20. 什么是执行上下文?

答案: 代码执行时的环境,包含变量对象、作用域链、this值。

执行上下文类型

全局执行上下文:最外层环境 函数执行上下文:每次函数调用创建 eval执行上下文:eval代码执行时创建

执行栈:LIFO结构,管理执行上下文

21. 什么是暂时性死区(TDZ)?

答案: let/const声明的变量在声明前不可访问的区域。

console.log(a); // undefined(变量提升)
var a = 1;

console.log(b); // ReferenceError(暂时性死区)
let b = 2;

22. new操作符做了什么?

答案:

创建一个空对象 将对象的__proto__指向构造函数的prototype 将构造函数的this指向新对象 执行构造函数代码 如果构造函数返回对象,则返回该对象;否则返回新对象
function myNew(constructor, ...args) {
  // 1. 创建空对象
  const obj = {};

  // 2. 设置原型
  obj.__proto__ = constructor.prototype;

  // 3. 绑定this并执行
  const result = constructor.apply(obj, args);

  // 4. 返回结果
  return result instanceof Object ? result : obj;
}

23. 实现一个发布 订阅模式?

答案:

class EventEmitter {
  constructor() {
    this.events = {};
  }

  on(event, listener) {
    if (!this.events[event]) {
      this.events[event] = [];
    }
    this.events[event].push(listener);
  }

  off(event, listener) {
    if (!this.events[event]) return;
    this.events[event] = this.events[event].filter(l => l !== listener);
  }

  emit(event, ...args) {
    if (!this.events[event]) return;
    this.events[event].forEach(listener => listener(...args));
  }

  once(event, listener) {
    const onceWrapper = (...args) => {
      listener(...args);
      this.off(event, onceWrapper);
    };
    this.on(event, onceWrapper);
  }
}

// 使用
const emitter = new EventEmitter();
emitter.on('message', data => console.log(data));
emitter.emit('message', 'Hello!');

24. 函数式编程的特点?

答案:

纯函数:相同输入总是返回相同输出,无副作用 不可变性:数据不可变,创建新数据 函数是一等公民:函数可作为参数、返回值 高阶函数:接收函数作为参数或返回函数 柯里化和组合
// 纯函数
const add = (a, b) => a + b;

// 不可变性
const arr = [1, 2, 3];
const newArr = [...arr, 4]; // 不改变原数组

// 函数组合
const compose = (...fns) => x => fns.reduceRight((acc, fn) => fn(acc), x);
const add1 = x => x + 1;
const double = x => x * 2;
const add1ThenDouble = compose(double, add1);
add1ThenDouble(5); // 12

25. 什么是Web Workers?

答案: 在后台运行JavaScript的线程,不阻塞主线程。

// main.js
const worker = new Worker('worker.js');

worker.postMessage('Hello Worker');

worker.onmessage = event => {
  console.log('From worker:', event.data);
};

worker.onerror = error => {
  console.error('Worker error:', error);
};

// worker.js
self.onmessage = event => {
  const result = doHeavyTask(event.data);
  self.postMessage(result);
};

function doHeavyTask(data) {
  // 复杂计算
  return data.toUpperCase();
}

进阶概念

26. 什么是设计模式?常用的有哪些?

答案: 设计模式:解决特定问题的可重用方案。

常用模式

单例模式:一个类只有一个实例

class Singleton {
  static instance;

  constructor() {
    if (Singleton.instance) {
      return Singleton.instance;
    }
    Singleton.instance = this;
  }
}

工厂模式:创建对象而不暴露创建逻辑

观察者模式:对象间一对多依赖关系

策略模式:定义一系列算法并使其可互换

装饰器模式:动态添加职责

27. 什么是Web Components?

答案: 创建可重用定制元素的技术,包含:

Custom Elements:定义新HTML元素 Shadow DOM:封装样式和行为 HTML Templates:定义可复用的模板
class MyElement extends HTMLElement {
  constructor() {
    super();
    const shadow = this.attachShadow({mode: 'open'});
    shadow.innerHTML = `
      <style>
        :host { display: block; }
      </style>
      <slot></slot>
    `;
  }
}

customElements.define('my-element', MyElement);

28. 性能优化方法?

答案:

代码层面

网络层面

渲染层面

内存管理

29. 什么是Service Worker?

答案: 浏览器在后台运行的脚本,可用于离线缓存、推送通知、后台同步。

// 注册
if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/sw.js')
    .then(registration => console.log('SW registered'))
    .catch(error => console.log('SW registration failed'));
}

// sw.js - Service Worker文件
self.addEventListener('install', event => {
  event.waitUntil(
    caches.open('v1').then(cache => {
      return cache.addAll(['/', '/index.html']);
    })
  );
});

self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request).then(response => {
      return response || fetch(event.request);
    })
  );
});

30. 什么是Tree Shaking?

答案: 通过静态分析移除未使用的代码。

// math.js
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;

// app.js
import { add } from './math.js'; // 只有add被包含在bundle中

31. 什么是Babel?作用是什么?

答案: JavaScript编译器,将新语法转换为旧浏览器支持的语法。

主要功能

语法转换(ES6+ → ES5) Polyfill(通过@babel/polyfill) 代码转换(JSX、TypeScript) 插件系统

32. 什么是Webpack?核心概念?

答案: 静态模块打包工具。

核心概念

Entry:入口文件 Output:输出配置 Loader:处理非JavaScript文件 Plugin:扩展功能 Mode:开发/生产模式 Module:模块系统

33. 什么是虚拟DOM?

答案: 描述真实DOM的JavaScript对象,通过diff算法最小化DOM操作。

// 虚拟DOM示例
const vnode = {
  tag: 'div',
  props: { id: 'app', className: 'container' },
  children: [
    { tag: 'h1', props: {}, children: ['Hello'] },
    { tag: 'p', props: {}, children: ['World'] }
  ]
};

34. 什么是MVVM模式?

答案: Model-View-ViewModel架构模式,实现数据双向绑定。

// 简单实现
class MVVM {
  constructor(options) {
    this.$data = options.data;
    this.$el = document.querySelector(options.el);

    this.observe(this.$data);
    this.compile(this.$el);
  }

  observe(data) {
    // 数据劫持
    Object.keys(data).forEach(key => {
      let value = data[key];
      const dep = new Dep();

      Object.defineProperty(data, key, {
        get() {
          Dep.target && dep.addSub(Dep.target);
          return value;
        },
        set(newVal) {
          if (newVal !== value) {
            value = newVal;
            dep.notify();
          }
        }
      });
    });
  }

  compile(el) {
    // 模板编译
  }
}

35. 什么是SSR?优缺点?

答案: 服务端渲染(Server-Side Rendering)

优点

更好的SEO 更快的首屏加载 更好的用户体验

缺点

服务器压力大 开发复杂度高 TTFB可能变长

ES6+新特性

36. ES6有哪些新特性?

答案

let/const 箭头函数 模板字符串 解构赋值 扩展运算符 默认参数 Promise Class Module Set/Map

37. Map和Object的区别?

答案: | 特性 | Map | Object | |------|-----|--------| | 键类型 | 任意类型 | String/Symbol | | 键顺序 | 插入顺序 | 无序(但ES6后也有序) | | 大小 | size属性获取 | 手动计算 | | 迭代 | 可直接迭代 | 需要Object.keys等 | | 性能 | 频繁增删时更好 | 一般情况 | | 序列化 | 不能直接JSON序列化 | 可以 |

38. Set和Array的区别?

答案:

// Set:值唯一
const set = new Set([1, 2, 3, 3]); // {1, 2, 3}
set.add(4);
set.has(2); // true

// Array:可重复
const arr = [1, 2, 3, 3]; // [1, 2, 3, 3]

39. 什么是可选链操作符(?.)?

答案: 安全访问嵌套对象属性。

// 传统方式
const name = user && user.info && user.info.name;

// 可选链
const name = user?.info?.name;

// 配合空值合并运算符
const name = user?.info?.name ?? 'Default';

40. 什么是空值合并运算符(??)?

答案: 左侧为null或undefined时返回右侧值。

const value = null ?? 'default'; // 'default'
const value = 0 ?? 'default';    // 0(不同于||)
const value = '' ?? 'default';   // ''(不同于||)

41. 什么是BigInt?

答案: 表示大于2^53-1的整数。

const big = 9007199254740991n;
const big2 = BigInt('9007199254740991');
const result = big + 1n; // 9007199254740992n

42. 什么是Proxy和Reflect?

答案: Proxy用于定义基本操作的自定义行为,Reflect提供拦截JavaScript操作的方法。

const handler = {
  get(target, prop) {
    return prop in target ? target[prop] : 37;
  },
  set(target, prop, value) {
    if (prop === 'age' && value < 0) {
      throw new Error('Age cannot be negative');
    }
    target[prop] = value;
    return true;
  }
};

const obj = new Proxy({}, handler);
obj.age = 25;
console.log(obj.age); // 25
console.log(obj.name); // 37

43. 什么是Generator函数?

答案: 可以暂停执行的函数。

function* generator() {
  yield 1;
  yield 2;
  yield 3;
}

const gen = generator();
console.log(gen.next()); // {value: 1, done: false}
console.log(gen.next()); // {value: 2, done: false}
console.log(gen.next()); // {value: 3, done: false}
console.log(gen.next()); // {value: undefined, done: true}

// 异步应用
async function* asyncGenerator() {
  const data = await fetchData();
  yield data;
}

44. 什么是装饰器(Decorator)?

答案: 修改类或类成员的语法(目前Stage 3提案)。

// 类装饰器
@log
class MyClass {
  @readonly
  method() {}
}

function log(constructor) {
  return class extends constructor {
    constructor(...args) {
      console.log('Creating instance');
      super(...args);
    }
  };
}

function readonly(target, name, descriptor) {
  descriptor.writable = false;
  return descriptor;
}

45. ES2020+重要特性?

答案:

可选链(?.) 空值合并(??) Promise.allSettled 动态导入(import()) globalThis String.matchAll BigInt 顶层await

实践题

46. 实现数组扁平化?

// 方法1: flat
const flattened = arr.flat(Infinity);

// 方法2: 递归
function flatten(arr) {
  return arr.reduce((acc, val) => 
    acc.concat(Array.isArray(val) ? flatten(val) : val), 
  []);
}

// 方法3: 迭代
function flatten(arr) {
  const stack = [...arr];
  const result = [];
  while (stack.length) {
    const next = stack.pop();
    if (Array.isArray(next)) {
      stack.push(...next);
    } else {
      result.push(next);
    }
  }
  return result.reverse();
}

47. 实现数组去重?

// 方法1: Set
const unique = [...new Set(arr)];

// 方法2: filter + indexOf
const unique = arr.filter((item, index) => arr.indexOf(item) === index);

// 方法3: reduce
const unique = arr.reduce((acc, item) => 
  acc.includes(item) ? acc : [...acc, item], 
[]);

// 方法4: 对象键值(适用于非对象类型)
const unique = Object.keys(arr.reduce((acc, item) => {
  acc[item] = true;
  return acc;
}, {}));

48. 实现继承的几种方式?

// 1. 原型链继承
function Parent() {}
function Child() {}
Child.prototype = new Parent();

// 2. 构造函数继承
function Parent(name) { this.name = name; }
function Child(name) { Parent.call(this, name); }

// 3. 组合继承
function Parent(name) { this.name = name; }
Parent.prototype.say = function() {};
function Child(name) {
  Parent.call(this, name);
}
Child.prototype = new Parent();
Child.prototype.constructor = Child;

// 4. 寄生组合继承(最佳)
function inherit(Child, Parent) {
  const prototype = Object.create(Parent.prototype);
  prototype.constructor = Child;
  Child.prototype = prototype;
}

// 5. Class继承
class Parent {}
class Child extends Parent {}

49. 实现Promise相关方法?

// Promise.all
Promise.all = function(promises) {
  return new Promise((resolve, reject) => {
    const results = [];
    let count = 0;

    promises.forEach((promise, index) => {
      Promise.resolve(promise).then(
        value => {
          results[index] = value;
          count++;
          if (count === promises.length) resolve(results);
        },
        reject
      );
    });
  });
};

// Promise.race
Promise.race = function(promises) {
  return new Promise((resolve, reject) => {
    promises.forEach(promise => {
      Promise.resolve(promise).then(resolve, reject);
    });
  });
};

// Promise.allSettled
Promise.allSettled = function(promises) {
  return Promise.all(
    promises.map(promise => 
      Promise.resolve(promise)
        .then(value => ({ status: 'fulfilled', value }))
        .catch(reason => ({ status: 'rejected', reason }))
    )
  );
};

50. 实现一个简单的Vue响应式系统?

class Dep {
  constructor() {
    this.subscribers = new Set();
  }

  depend() {
    if (activeEffect) {
      this.subscribers.add(activeEffect);
    }
  }

  notify() {
    this.subscribers.forEach(effect => effect());
  }
}

let activeEffect = null;

function watchEffect(effect) {
  activeEffect = effect;
  effect();
  activeEffect = null;
}

const targetMap = new WeakMap();

function getDep(target, key) {
  let depsMap = targetMap.get(target);
  if (!depsMap) {
    depsMap = new Map();
    targetMap.set(target, depsMap);
  }

  let dep = depsMap.get(key);
  if (!dep) {
    dep = new Dep();
    depsMap.set(key, dep);
  }
  return dep;
}

function reactive(obj) {
  return new Proxy(obj, {
    get(target, key) {
      const dep = getDep(target, key);
      dep.depend();
      return target[key];
    },
    set(target, key, value) {
      target[key] = value;
      const dep = getDep(target, key);
      dep.notify();
      return true;
    }
  });
}

// 使用
const state = reactive({ count: 0 });

watchEffect(() => {
  console.log('Count:', state.count);
});

state.count++; // 自动触发watchEffect

这份总结涵盖了JavaScript面试中最常见和重要的50个问题,建议结合实际编码练习加深理解。每个问题都提供了核心概念解释和代码示例,方便学习和复习。

相关推荐