数据类型

1. JS 中有哪些基本数据类型?

答案:

  • 基本数据类型(Primitive Types)

    • Undefined:声明未赋值的变量
    • Null:空值
    • Booleantrue/false
    • Number:整数/浮点数(包括Infinity, NaN
    • String:字符串
    • BigInt:大整数(ES2020)
    • Symbol:唯一值(ES6)
  • 引用类型(Reference Type)

    • Object(包含Array, Function, Date等)

解析:

  • 基本类型存储在栈内存,按值访问。
  • 引用类型存储在堆内存,按引用访问。
  • typeof null 返回 "object"(历史遗留问题)。

2. 如何判断数据类型?

答案:

  • typeof variable:返回类型字符串,无法区分数组/对象。
  • variable instanceof Constructor:检测原型链。
  • Object.prototype.toString.call(variable):最准确方式。

示例:

 1 
 2console.log(Object.prototype.toString.call([])); 
 3console.log([] instanceof Array); 

变量与作用域

3. var、let、const 的区别?

答案:

特性varletconst
作用域函数级块级块级
变量提升否(TDZ)否(TDZ)
重复声明允许禁止禁止
重新赋值允许允许不允许

解析:

  • TDZ(暂时性死区) :声明前访问会报错。
  • const 必须初始化,但对象属性可修改。

闭包

4. 什么是闭包?应用场景?

答案: 闭包是能够访问其他函数内部变量的函数。常见场景:

  1. 封装私有变量
  2. 模块化开发
  3. 函数柯里化

示例:

 1 
 2function createCounter() {
 3  let count = 0;
 4  return function() {
 5    return ++count;
 6  };
 7}
 8const counter = createCounter();
 9console.log(counter()); 

解析: 闭包会导致外部函数的作用域无法释放,可能引发内存泄漏。


原型与继承

5. 描述原型链机制

答案:

  • 每个对象都有__proto__指向构造函数的prototype
  • 访问属性时,若对象本身没有,则沿原型链查找。
  • 终点是Object.prototype.__proto__null)。

示例:

 1 
 2function Person(name) { this.name = name; }
 3Person.prototype.sayName = function() { console.log(this.name); };
 4
 5const p = new Person('Alice');
 6p.sayName(); 

异步编程

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

答案:

  1. JS 是单线程,通过事件循环处理异步。
  2. 调用栈执行同步任务。
  3. 异步任务进入任务队列(宏任务:setTimeout,微任务:Promise.then)。
  4. 每次循环先清空微任务队列,再执行一个宏任务。

示例:

 1 
 2setTimeout(() => console.log('宏任务'), 0);
 3Promise.resolve().then(() => console.log('微任务'));
 4

ES6+ 新特性

7. 箭头函数与普通函数的区别?

答案:

  • 语法更简洁(() => {})。
  • 没有自己的this,继承外层。
  • 不能作为构造函数。
  • 没有arguments对象,可用剩余参数...args

其他重要概念

8. 深拷贝 vs 浅拷贝

答案:

  • 浅拷贝:复制一层属性(Object.assign, 展开运算符)。
  • 深拷贝:递归复制所有层级(JSON.parse(JSON.stringify(obj)),手动递归,lodash.cloneDeep)。

注意: JSON方法会忽略undefined和函数。


Promise 实现

9. 手写 Promise 的核心实现

答案:

 1 
 2class MyPromise {
 3  constructor(executor) {
 4    this.state = 'pending'; 
 5    this.value = undefined;
 6    this.onResolvedCallbacks = [];
 7    this.onRejectedCallbacks = [];
 8
 9    const resolve = (value) => {
10      if (this.state !== 'pending') return;
11      this.state = 'fulfilled';
12      this.value = value;
13      this.onResolvedCallbacks.forEach(fn => fn());
14    };
15
16    const reject = (reason) => {
17      if (this.state !== 'pending') return;
18      this.state = 'rejected';
19      this.value = reason;
20      this.onRejectedCallbacks.forEach(fn => fn());
21    };
22
23    try {
24      executor(resolve, reject);
25    } catch (err) {
26      reject(err);
27    }
28  }
29
30  then(onFulfilled, onRejected) {
31    return new MyPromise((resolve, reject) => {
32      const handleCallback = (callback, value) => {
33        try {
34          const result = callback?.(value);
35          result instanceof MyPromise ? 
36            result.then(resolve, reject) : 
37            resolve(result);
38        } catch (err) {
39          reject(err);
40        }
41      };
42
43      if (this.state === 'fulfilled') {
44        setTimeout(() => handleCallback(onFulfilled, this.value));
45      } else if (this.state === 'rejected') {
46        setTimeout(() => handleCallback(onRejected, this.value));
47      } else { 
48        this.onResolvedCallbacks.push(() => 
49          setTimeout(() => handleCallback(onFulfilled, this.value)));
50        this.onRejectedCallbacks.push(() => 
51          setTimeout(() => handleCallback(onRejected, this.value)));
52      }
53    });
54  }
55}

解析:

  • 状态不可逆:Promise 状态只能从 pending 变更为 fulfilledrejected
  • 链式调用.then 返回新 Promise,支持链式调用。
  • 异步执行:回调函数通过 setTimeout 模拟微任务队列(实际 Promise 使用微任务)。

防抖与节流

10. 防抖(Debounce)与节流(Throttle)的区别与实现

区别:

防抖节流
核心事件结束后触发固定时间间隔触发
场景搜索框输入联想滚动事件、窗口调整
实现每次触发重置定时器通过时间戳或定时器控制频率

防抖实现:

 1 
 2function debounce(fn, delay) {
 3  let timer = null;
 4  return function(...args) {
 5    clearTimeout(timer);
 6    timer = setTimeout(() => fn.apply(this, args), delay);
 7  };
 8}

节流实现(时间戳版):

 1 
 2function throttle(fn, interval) {
 3  let lastTime = 0;
 4  return function(...args) {
 5    const now = Date.now();
 6    if (now - lastTime >= interval) {
 7      fn.apply(this, args);
 8      lastTime = now;
 9    }
10  };
11}

this 指向

11. 如何确定函数中的 this

规则优先级:

  1. new 绑定new Foo()this 指向新对象。
  2. 显式绑定call/apply/bind → 指向指定对象。
  3. 隐式绑定obj.fn() → 指向调用者 obj
  4. 默认绑定:全局对象(严格模式下为 undefined)。
  5. 箭头函数:继承外层作用域的 this

示例:

 1 
 2const obj = {
 3  name: 'Alice',
 4  sayName: function() { console.log(this.name) },
 5  sayNameArrow: () => console.log(this.name)
 6};
 7obj.sayName();       
 8obj.sayNameArrow();  

模块化

12. CommonJS 与 ES Module 的区别

特性CommonJSES Module
加载方式同步加载(动态导入)异步加载(静态解析)
输出module.exportsexport/export default
导入require()import
执行顺序运行时解析编译时解析(静态)
适用场景Node.js 环境浏览器/现代前端框架

CommonJS 示例:

 1 
 2
 3module.exports = { add: (a, b) => a + b };
 4
 5const math = require('./math.js');
 6math.add(2, 3); 

ES Module 示例:

 1 
 2
 3export const add = (a, b) => a + b;
 4
 5import { add } from './math.js';
 6add(2, 3); 

POST 与 GET 请求的区别

13. 核心区别

特性GETPOST
参数位置URL 查询字符串(?key=value请求体(Body)
安全性参数暴露在 URL 中参数在请求体中,相对安全
缓存可被缓存默认不缓存
幂等性幂等(多次请求结果相同)非幂等(可能修改数据)
长度限制受浏览器 URL 长度限制无限制

使用场景:

  • GET:获取数据(如搜索、分页)。
  • POST:提交数据(如登录、表单提交)。

重绘(Repaint)与重排(Reflow)的区别

14. 浏览器渲染机制中的关键概念

重绘(Repaint)重排(Reflow)
定义元素外观变化(颜色、背景等)布局或几何属性变化(尺寸、位置等)
触发color, visibility, backgroundwidth, height, margin, display
性能开销较小开销较大(可能引发整个文档重排)

优化建议:

  1. 避免频繁操作 DOM,使用 DocumentFragment 或虚拟 DOM。
  2. 使用 CSS3 动画(transformopacity)代替直接修改布局属性。
  3. 批量修改样式(通过 class 切换而非逐行修改)。


深入核心概念

15. 异步编程的常见实现方式?

答案:

  1. 回调函数:传统异步方案,但易导致“回调地狱”。
  2. Promise:链式调用,解决嵌套问题。
  3. async/await:基于 Promise 的语法糖,以同步方式写异步代码。
  4. Generator 函数:通过 yield 暂停执行(需配合执行器如 co 库)。
  5. 事件监听:如 EventEmitter 模式。

示例(async/await):

 1 
 2async function fetchData() {
 3  try {
 4    const res = await fetch('https://api.example.com/data');
 5    const data = await res.json();
 6    console.log(data);
 7  } catch (err) {
 8    console.error('请求失败:', err);
 9  }
10}

16. 原型链的实际应用场景?

答案:

  • 继承机制:通过 prototype 实现对象间属性和方法的共享。
  • 内置方法扩展:如为 Array 添加自定义方法。
  • 性能优化:共享方法减少内存占用。

示例(方法扩展):

 1 
 2Array.prototype.last = function() {
 3  return this[this.length - 1];
 4};
 5console.log([1, 2, 3].last()); 

17. 闭包的内存管理问题?

答案:

  • 内存泄漏风险:闭包会长期持有外部函数变量的引用,导致无法被垃圾回收。

  • 解决方案

    1. 及时解除引用:fn = null
    2. 避免循环引用
    3. 使用弱引用(如 WeakMap

JS 延迟加载方式

18. 如何实现 JS 延迟加载?

答案:

方法说明
defer 属性异步下载,HTML 解析完成后按顺序执行(适用于依赖 DOM 的脚本)
async 属性异步下载,下载完成后立即执行(适用于独立无依赖的脚本)
动态脚本插入通过 document.createElement('script') 动态加载
setTimeout 延迟延迟执行代码(不推荐,可能阻塞渲染)

最佳实践:

 1 
 2<script defer src="app.js"></script>
 3<script async src="analytics.js"></script>

call、apply、bind 的区别

19. 三者核心区别与使用场景?

答案:

方法传参方式执行时机使用场景
call参数逐个传递 (a, b)立即执行明确参数数量时
apply参数数组传递 ([a, b])立即执行参数数量不确定(如数组处理)
bind参数逐个传递返回绑定后的函数需要延迟执行或作为回调

示例:

 1 
 2function greet(message) {
 3  console.log(`${message}, ${this.name}`);
 4}
 5const user = { name: 'Alice' };
 6
 7greet.call(user, 'Hello');      
 8greet.apply(user, ['Hi']);      
 9const boundGreet = greet.bind(user, 'Hey');
10boundGreet();                   

new 操作符

20. new 操作符的底层实现步骤?

答案:

  1. 创建空对象const obj = {}
  2. 链接原型obj.__proto__ = Constructor.prototype
  3. 绑定 this:执行构造函数,this 指向新对象
  4. 返回对象:若构造函数返回对象则使用该对象,否则返回 obj

手动实现 new

 1 
 2function myNew(Constructor, ...args) {
 3  const obj = Object.create(Constructor.prototype);
 4  const result = Constructor.apply(obj, args);
 5  return result instanceof Object ? result : obj;
 6}

“use strict” 严格模式

21. 严格模式的主要作用?

答案:

  1. 消除静默错误:未声明变量赋值会报错(a = 10ReferenceError
  2. 禁止删除不可删除属性delete Object.prototype → 报错
  3. 函数参数不能重名function(a, a) {} → 报错
  4. this 默认不指向全局:未绑定的 thisundefined
  5. 禁用 with 语句:避免作用域混乱

示例:

 1 
 2'use strict';
 3function test() {
 4  console.log(this); 
 5}
 6test();

事件冒泡与事件委托

22. 事件传播机制与委托优化

答案:

  • 事件冒泡:事件从目标元素向上传播到根元素(<div> → <body> → <html>)。
  • 事件捕获:从根元素向下传播到目标元素(与冒泡相反)。
  • 事件委托:利用冒泡机制,将事件监听器绑定到父元素,通过 event.target 处理子元素事件。

示例(委托优化列表点击):

 1 
 2document.getElementById('list').addEventListener('click', function(e) {
 3  if (e.target.tagName === 'LI') {
 4    console.log('点击了:', e.target.textContent);
 5  }
 6});

优点:

  • 减少内存消耗(无需为每个子元素绑定监听器)
  • 动态新增元素无需重新绑定

JavaScript 作用域链

23. 作用域链的查找规则?

答案:

  1. 词法作用域:函数定义时确定作用域链,与调用位置无关。
  2. 链式结构:当前作用域 → 外层作用域 → … → 全局作用域。
  3. 变量查找:沿作用域链逐级向上查找,未找到则报 ReferenceError

示例:

 1 
 2const globalVar = 'global';
 3function outer() {
 4  const outerVar = 'outer';
 5  function inner() {
 6    console.log(outerVar);  
 7    console.log(globalVar); 
 8  }
 9  inner();
10}
11outer();

堆(Heap)与栈(Stack)的区别

24. 内存管理中的堆与栈

答案:

栈(Stack)堆(Heap)
存储内容基本类型值、函数调用上下文(指针)引用类型对象(复杂数据结构)
分配方式系统自动分配/释放(FILO)手动分配(开发者申请,GC回收)
访问速度快(内存连续)慢(内存碎片化)
空间限制固定大小(可能栈溢出)灵活(受物理内存限制)

示例:

 1 
 2let a = 10;          
 3let b = { name: 'Bob' }; 

25. 原型链继承的实现方式与优缺点

答案: 实现方式:

  1. 原型链继承:子类原型指向父类实例

     1 
     2function Parent() { this.name = 'Parent'; }
     3Parent.prototype.say = function() { console.log(this.name); };
     4
     5function Child() {}
     6Child.prototype = new Parent(); 
     7const child = new Child();
     8child.say(); 
    
    • 缺点:所有子类实例共享父类引用属性(如数组)。
  2. 构造函数继承:在子类中调用父类构造函数

     1 
     2function Child() {
     3  Parent.call(this); 
     4}
    
    • 缺点:无法继承父类原型方法。
  3. 组合继承(原型链 + 构造函数):

     1 
     2function Child() {
     3  Parent.call(this); 
     4}
     5Child.prototype = new Parent(); 
     6Child.prototype.constructor = Child;
    
    • 缺点:父类构造函数被调用两次。
  4. ES6 Class 继承

     1 
     2class Parent {
     3  constructor() { this.name = 'Parent'; }
     4  say() { console.log(this.name); }
     5}
     6class Child extends Parent {
     7  constructor() { super(); }
     8}
     9const child = new Child();
    10child.say(); 
    

总结:ES6 class 语法糖最简洁,底层基于原型链。


26. 闭包内存管理的实际案例与解决方案

案例

 1 
 2function createHeavyObject() {
 3  const bigData = new Array(1000000).fill('data');
 4  return function() { console.log(bigData[0]); };
 5}
 6const closure = createHeavyObject(); 

解决方案

  1. 手动释放引用

     1 
     2closure = null; 
    
  2. 使用 WeakMap(弱引用):

     1 
     2const wm = new WeakMap();
     3function createSafeClosure() {
     4  const bigData = new Array(1000000).fill('data');
     5  wm.set(this, bigData);
     6  return () => console.log(wm.get(this)?.[0]);
     7}
    

27. 事件循环(Event Loop)的深入解析

执行顺序规则

  1. 同步任务:直接进入调用栈执行。
  2. 微任务Promise.then, MutationObserver):在每一个宏任务结束后立即执行。
  3. 宏任务setTimeout, setInterval, I/O):等待下一个事件循环。

示例:

 1 
 2console.log('1'); 
 3setTimeout(() => console.log('2'), 0); 
 4Promise.resolve().then(() => console.log('3')); 
 5console.log('4'); 
 6

28. Promise 链式调用的错误处理

链式调用规则

  • 每个 .then 返回新 Promise。
  • 使用 .catch 捕获链中任意位置的错误。

示例:

 1 
 2fetch('url')
 3  .then(res => res.json())
 4  .then(data => processData(data))
 5  .catch(err => console.error('链中任何错误:', err));

async/await 优化:

 1 
 2async function fetchData() {
 3  try {
 4    const res = await fetch('url');
 5    const data = await res.json();
 6    return processData(data);
 7  } catch (err) {
 8    console.error('错误捕获:', err);
 9  }
10}

29. 渐进增强(Progressive Enhancement)与优雅降级(Graceful Degradation)

概念核心思想适用场景
渐进增强从基础功能开始,逐步增加高级功能(优先保证基础体验)面向未来、兼容旧浏览器
优雅降级先实现完整功能,再处理兼容性问题(优先高级浏览器,降级适配低版本)快速开发、内部系统

示例

  • 渐进增强:先实现 <input type="text">,再通过 JavaScript 增强为日期选择器。
  • 优雅降级:使用 CSS3 动画,低版本浏览器回退为 JavaScript 动画。

30. Web Worker 与 WebSocket 的区别

特性Web WorkerWebSocket
用途多线程计算,避免阻塞主线程实时双向通信(如聊天室、股票行情)
通信方式通过 postMessage 传递消息基于 TCP 的全双工通信(ws://协议)
生命周期需手动终止 (worker.terminate())长连接,持续通信
DOM 访问无法直接操作 DOM通常由主线程处理数据

31. JavaScript 垃圾回收机制

主要算法

  1. 标记清除(Mark-Sweep)

    • 从根对象(全局变量、活动函数)出发,标记所有可达对象。
    • 清除未标记对象。
  2. 引用计数(Reference Counting)

    • 记录每个对象的引用次数,次数为0时回收。
    • 缺陷:循环引用无法回收(现代浏览器已弃用)。

内存泄漏场景

  • 未清理的定时器:setInterval
  • 未解除的 DOM 引用:const element = document.getElementById('id')
  • 闭包未释放

32. WeakSet 与 WeakMap 的特性

特性WeakSet/WeakMapSet/Map
键类型只接受对象作为键任意类型
弱引用键是弱引用,不计入垃圾回收机制强引用,阻止垃圾回收
可迭代性不可迭代支持 for...of 遍历
用例存储 DOM 节点(自动释放)通用数据存储

示例(WeakMap 缓存):

 1 
 2const cache = new WeakMap();
 3function getData(obj) {
 4  if (cache.has(obj)) return cache.get(obj);
 5  const data = ;
 6  cache.set(obj, data);
 7  return data;
 8}

33. Shadow DOM 的概念与作用

定义:Shadow DOM 是 Web Components 的核心技术之一,用于创建封装的 DOM 子树,与主文档 DOM 隔离。

特点

  • 样式封装:内部样式不影响外部,外部样式默认不影响内部。
  • DOM 隔离:通过 shadowRoot 访问内部 DOM。
  • 自定义元素:结合 customElements.define 创建可重用组件。

示例

 1 
 2class MyComponent extends HTMLElement {
 3  constructor() {
 4    super();
 5    const shadow = this.attachShadow({ mode: 'open' });
 6    shadow.innerHTML = `
 7      <style>p { color: red; }</style>
 8      <p>Shadow DOM 内容</p>
 9    `;
10  }
11}
12customElements.define('my-component', MyComponent);

34. 递归(Recursion)的应用与注意事项

定义:函数直接或间接调用自身,通过分解问题为更小的同类子问题来求解。

示例(阶乘):

 1 
 2function factorial(n) {
 3  if (n <= 1) return 1;
 4  return n * factorial(n - 1); 
 5}
 6console.log(factorial(5)); 

注意事项

  1. 终止条件:必须存在明确的递归结束条件。
  2. 性能问题:深度递归可能导致栈溢出(可用尾递归优化或循环替代)。

35. 纯函数、高阶函数与睡眠函数

纯函数(Pure Function)

  • 相同输入始终返回相同输出。

  • 无副作用(不修改外部状态)。

  • 示例

     1 
     2function add(a, b) { return a + b; }
    

高阶函数(Higher-Order Function)

  • 接收函数作为参数或返回函数。

  • 示例

     1 
     2function map(arr, fn) { return arr.map(fn); }
    

睡眠函数(Sleep Function)

  • 模拟延迟执行,基于 Promise 实现。

  • 示例

     1 
     2function sleep(ms) {
     3  return new Promise(resolve => setTimeout(resolve, ms));
     4}
     5async function demo() {
     6  await sleep(2000);
     7  console.log('2秒后输出');
     8}
    

36. 实现数组随机排序(Fisher-Yates 算法)

问题:编写 randomSort 函数,实现均匀随机排序。

正确实现(避免 arr.sort(() => Math.random() - 0.5) 的不均匀问题)

 1 
 2function randomSort(arr) {
 3  const result = [...arr];
 4  for (let i = result.length - 1; i > 0; i--) {
 5    const j = Math.floor(Math.random() * (i + 1));
 6    [result[i], result[j]] = [result[j], result[i]]; 
 7  }
 8  return result;
 9}
10
11
12console.log(randomSort([1, 2, 3, 4, 5])); 

解析

  • Fisher-Yates 算法:从后向前遍历,保证每个位置的概率均匀。
  • 时间复杂度:O(n),效率优于排序算法。
个人笔记记录 2021 ~ 2025