36道我命由我不由天的JavaScript 基础面试题详解

大家好,我是鱼樱!!!

关注公众号【鱼樱AI实验室】持续每天分享更多前端和AI辅助前端编码新知识~~

一个城市淘汰的自由职业-农村前端程序员(虽然不靠代码挣钱,写文章就是为爱发电),兼职远程上班目前!!!热心坚持分享36道有用的JS面试题~~~

数据类型

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):最准确方式。

示例:

js 复制代码
 
console.log(Object.prototype.toString.call([])); // [object Array]
console.log([] instanceof Array); // true

变量与作用域

3. var、let、const 的区别?

答案:

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

解析:

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

闭包

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

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

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

示例:

js 复制代码
 
function createCounter() {
  let count = 0;
  return function() {
    return ++count;
  };
}
const counter = createCounter();
console.log(counter()); // 1

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


原型与继承

5. 描述原型链机制

答案:

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

示例:

js 复制代码
 
function Person(name) { this.name = name; }
Person.prototype.sayName = function() { console.log(this.name); };

const p = new Person('Alice');
p.sayName(); // 通过原型链调用

异步编程

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

答案:

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

示例:

js 复制代码
 
setTimeout(() => console.log('宏任务'), 0);
Promise.resolve().then(() => console.log('微任务'));
// 输出顺序:微任务 → 宏任务

ES6+ 新特性

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

答案:

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

其他重要概念

8. 深拷贝 vs 浅拷贝

答案:

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

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


Promise 实现

9. 手写 Promise 的核心实现

答案:

js 复制代码
 
class MyPromise {
  constructor(executor) {
    this.state = 'pending'; // pending/fulfilled/rejected
    this.value = undefined;
    this.onResolvedCallbacks = [];
    this.onRejectedCallbacks = [];

    const resolve = (value) => {
      if (this.state !== 'pending') return;
      this.state = 'fulfilled';
      this.value = value;
      this.onResolvedCallbacks.forEach(fn => fn());
    };

    const reject = (reason) => {
      if (this.state !== 'pending') return;
      this.state = 'rejected';
      this.value = reason;
      this.onRejectedCallbacks.forEach(fn => fn());
    };

    try {
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }

  then(onFulfilled, onRejected) {
    return new MyPromise((resolve, reject) => {
      const handleCallback = (callback, value) => {
        try {
          const result = callback?.(value);
          result instanceof MyPromise ? 
            result.then(resolve, reject) : 
            resolve(result);
        } catch (err) {
          reject(err);
        }
      };

      if (this.state === 'fulfilled') {
        setTimeout(() => handleCallback(onFulfilled, this.value));
      } else if (this.state === 'rejected') {
        setTimeout(() => handleCallback(onRejected, this.value));
      } else { // pending
        this.onResolvedCallbacks.push(() => 
          setTimeout(() => handleCallback(onFulfilled, this.value)));
        this.onRejectedCallbacks.push(() => 
          setTimeout(() => handleCallback(onRejected, this.value)));
      }
    });
  }
}

解析:

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

防抖与节流

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

区别:

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

防抖实现:

js 复制代码
 
function debounce(fn, delay) {
  let timer = null;
  return function(...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
}

节流实现(时间戳版):

js 复制代码
 
function throttle(fn, interval) {
  let lastTime = 0;
  return function(...args) {
    const now = Date.now();
    if (now - lastTime >= interval) {
      fn.apply(this, args);
      lastTime = now;
    }
  };
}

this 指向

11. 如何确定函数中的 this

规则优先级:

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

示例:

js 复制代码
 
const obj = {
  name: 'Alice',
  sayName: function() { console.log(this.name) },
  sayNameArrow: () => console.log(this.name)
};
obj.sayName();       // 'Alice'(隐式绑定)
obj.sayNameArrow();  // undefined(箭头函数继承全局 this)

模块化

12. CommonJS 与 ES Module 的区别

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

CommonJS 示例:

js 复制代码
 
// math.js
module.exports = { add: (a, b) => a + b };
// app.js
const math = require('./math.js');
math.add(2, 3); // 5

ES Module 示例:

js 复制代码
 
// math.js
export const add = (a, b) => a + b;
// app.js
import { add } from './math.js';
add(2, 3); // 5

POST 与 GET 请求的区别

13. 核心区别

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

使用场景:

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

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

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

重绘(Repaint) 重排(Reflow)
定义 元素外观变化(颜色、背景等) 布局或几何属性变化(尺寸、位置等)
触发 color, visibility, background width, 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):

js 复制代码
 
async function fetchData() {
  try {
    const res = await fetch('https://api.example.com/data');
    const data = await res.json();
    console.log(data);
  } catch (err) {
    console.error('请求失败:', err);
  }
}

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

答案:

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

示例(方法扩展):

js 复制代码
 
Array.prototype.last = function() {
  return this[this.length - 1];
};
console.log([1, 2, 3].last()); // 3

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

答案:

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

  • 解决方案

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

JS 延迟加载方式

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

答案:

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

最佳实践:

js 复制代码
 
<script defer src="app.js"></script>
<script async src="analytics.js"></script>

call、apply、bind 的区别

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

答案:

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

示例:

js 复制代码
 
function greet(message) {
  console.log(`${message}, ${this.name}`);
}
const user = { name: 'Alice' };

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

new 操作符

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

答案:

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

手动实现 new

js 复制代码
 
function myNew(Constructor, ...args) {
  const obj = Object.create(Constructor.prototype);
  const result = Constructor.apply(obj, args);
  return result instanceof Object ? result : obj;
}

"use strict" 严格模式

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

答案:

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

示例:

js 复制代码
 
'use strict';
function test() {
  console.log(this); // undefined(非严格模式为 window)
}
test();

事件冒泡与事件委托

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

答案:

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

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

js 复制代码
 
document.getElementById('list').addEventListener('click', function(e) {
  if (e.target.tagName === 'LI') {
    console.log('点击了:', e.target.textContent);
  }
});

优点:

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

JavaScript 作用域链

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

答案:

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

示例:

js 复制代码
 
const globalVar = 'global';
function outer() {
  const outerVar = 'outer';
  function inner() {
    console.log(outerVar);  // 'outer'
    console.log(globalVar); // 'global'
  }
  inner();
}
outer();

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

24. 内存管理中的堆与栈

答案:

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

示例:

js 复制代码
 
let a = 10;          // 栈内存
let b = { name: 'Bob' }; // 对象存储在堆,栈中存储指针

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

答案: 实现方式:

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

    js 复制代码
     
    function Parent() { this.name = 'Parent'; }
    Parent.prototype.say = function() { console.log(this.name); };
    
    function Child() {}
    Child.prototype = new Parent(); // 继承
    const child = new Child();
    child.say(); // 'Parent'
    • 缺点:所有子类实例共享父类引用属性(如数组)。
  2. 构造函数继承:在子类中调用父类构造函数

    js 复制代码
     
    function Child() {
      Parent.call(this); // 继承实例属性
    }
    • 缺点:无法继承父类原型方法。
  3. 组合继承(原型链 + 构造函数):

    js 复制代码
     
    function Child() {
      Parent.call(this); // 实例属性
    }
    Child.prototype = new Parent(); // 原型方法
    Child.prototype.constructor = Child;
    • 缺点:父类构造函数被调用两次。
  4. ES6 Class 继承

    js 复制代码
     
    class Parent {
      constructor() { this.name = 'Parent'; }
      say() { console.log(this.name); }
    }
    class Child extends Parent {
      constructor() { super(); }
    }
    const child = new Child();
    child.say(); // 'Parent'

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


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

案例

js 复制代码
 
function createHeavyObject() {
  const bigData = new Array(1000000).fill('data');
  return function() { console.log(bigData[0]); };
}
const closure = createHeavyObject(); // bigData 无法释放

解决方案

  1. 手动释放引用

    js 复制代码
     
    closure = null; // 解除引用,触发垃圾回收
  2. 使用 WeakMap(弱引用):

    js 复制代码
     
    const wm = new WeakMap();
    function createSafeClosure() {
      const bigData = new Array(1000000).fill('data');
      wm.set(this, bigData);
      return () => console.log(wm.get(this)?.[0]);
    }

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

执行顺序规则

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

示例:

js 复制代码
 
console.log('1'); // 同步
setTimeout(() => console.log('2'), 0); // 宏任务
Promise.resolve().then(() => console.log('3')); // 微任务
console.log('4'); // 同步
// 输出顺序:1 → 4 → 3 → 2

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

链式调用规则

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

示例:

js 复制代码
 
fetch('url')
  .then(res => res.json())
  .then(data => processData(data))
  .catch(err => console.error('链中任何错误:', err));

async/await 优化:

js 复制代码
 
async function fetchData() {
  try {
    const res = await fetch('url');
    const data = await res.json();
    return processData(data);
  } catch (err) {
    console.error('错误捕获:', err);
  }
}

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

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

示例

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

30. Web Worker 与 WebSocket 的区别

特性 Web Worker WebSocket
用途 多线程计算,避免阻塞主线程 实时双向通信(如聊天室、股票行情)
通信方式 通过 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/WeakMap Set/Map
键类型 只接受对象作为键 任意类型
弱引用 键是弱引用,不计入垃圾回收机制 强引用,阻止垃圾回收
可迭代性 不可迭代 支持 for...of 遍历
用例 存储 DOM 节点(自动释放) 通用数据存储

示例(WeakMap 缓存):

js 复制代码
 
const cache = new WeakMap();
function getData(obj) {
  if (cache.has(obj)) return cache.get(obj);
  const data = /* 计算 */;
  cache.set(obj, data);
  return data;
}

33. Shadow DOM 的概念与作用

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

特点

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

示例

js 复制代码
 
class MyComponent extends HTMLElement {
  constructor() {
    super();
    const shadow = this.attachShadow({ mode: 'open' });
    shadow.innerHTML = `
      <style>p { color: red; }</style>
      <p>Shadow DOM 内容</p>
    `;
  }
}
customElements.define('my-component', MyComponent);

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

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

示例(阶乘):

js 复制代码
 
function factorial(n) {
  if (n <= 1) return 1;
  return n * factorial(n - 1); // 递归调用
}
console.log(factorial(5)); // 120

注意事项

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

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

纯函数(Pure Function)

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

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

  • 示例

    js 复制代码
     
    function add(a, b) { return a + b; }

高阶函数(Higher-Order Function)

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

  • 示例

    js 复制代码
     
    function map(arr, fn) { return arr.map(fn); }

睡眠函数(Sleep Function)

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

  • 示例

    js 复制代码
     
    function sleep(ms) {
      return new Promise(resolve => setTimeout(resolve, ms));
    }
    async function demo() {
      await sleep(2000);
      console.log('2秒后输出');
    }

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

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

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

js 复制代码
 
function randomSort(arr) {
  const result = [...arr];
  for (let i = result.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [result[i], result[j]] = [result[j], result[i]]; // 交换
  }
  return result;
}

// 示例
console.log(randomSort([1, 2, 3, 4, 5])); // 随机顺序如 [3,1,5,2,4]

解析

  • Fisher-Yates 算法:从后向前遍历,保证每个位置的概率均匀。
  • 时间复杂度:O(n),效率优于排序算法。
相关推荐
GDAL32 分钟前
better-sqlite3之exec方法
javascript·sqlite
匹马夕阳1 小时前
基于Canvas和和原生JS实现俄罗斯方块小游戏
javascript·canva可画
m0_616188491 小时前
Vue3 中 Computed 用法
前端·javascript·vue.js
六个点1 小时前
图片懒加载与预加载的实现
前端·javascript·面试
weixin_460783871 小时前
Flutter解决TabBar顶部页面切换导致页面重载问题
android·javascript·flutter
程序员清风1 小时前
什么时候会考虑用联合索引?如果只有一个条件查就没有建联合索引的必要了么?
java·后端·面试
Patrick_Wilson2 小时前
🔥【全网首篇】30分钟带你从0到1搭建基于Lynx的跨端开发环境
前端·react.js·前端框架
逍遥客.2 小时前
uniapp对接打印机和电子秤
javascript·vue.js·uni-app
小沙盒2 小时前
godot在_process()函数实现非阻塞延时触发逻辑
javascript·游戏引擎·godot
Moment2 小时前
前端 社招 面筋分享:前端两年都问些啥 ❓️❓️❓️
前端·javascript·面试