JavaScript 面试题

#前端#JavaScript

前端面试题 - JavaScript篇

JavaScript是前端开发的核心语言,在面试中占据重要地位。本文档整理了169道JavaScript面试题,涵盖13个核心知识点,帮助你系统复习和准备面试。


文档概览

项目内容
题目总数169道
覆盖分类13个核心知识点
难度分布基础(31道) / 中级(83道) / 高级(55道)
更新时间2025年

难度标识说明

  • ⭐ 基础题:必须掌握的核心概念
  • ⭐⭐ 中级题:深入理解和实际应用
  • ⭐⭐⭐ 高级题:原理分析和手写实现

目录


一、ES6+新特性

1. let/const与var的区别

难度:⭐⭐(基础) 标签#ES6 #变量声明 #作用域

问题描述: 请解释ES6中let/const与var的区别?

参考答案要点

  • 作用域:let/const是块级作用域,var是函数作用域
  • 变量提升:var会提升变量,let/const不会(有暂时性死区TDZ)
  • 重复声明:var允许重复声明,let/const不允许
  • const特性:const声明必须初始化且不能重新赋值(对象属性可修改)

2. 箭头函数的特点

难度:⭐⭐(基础) 标签#ES6 #箭头函数 #this

问题描述: 箭头函数与普通函数有什么区别?

参考答案要点

  • 语法更简洁,可以省略return和花括号
  • 没有自己的this,继承外层this值(词法作用域绑定)
  • 不能作为构造函数使用(不能用new)
  • 没有arguments对象,可用rest参数替代
  • 没有prototype属性

3. Promise的三种状态

难度:⭐⭐(基础) 标签#ES6 #Promise #异步

问题描述: Promise有哪些状态?状态之间如何转换?

参考答案要点

  • pending(进行中):初始状态
  • fulfilled(已成功):执行resolve后
  • rejected(已失败):执行reject后
  • 状态一旦改变就不可再次改变(从pending到fulfilled或rejected)

4. Promise.all/Promise.race/Promise.allSettled的区别

难度:⭐⭐⭐(中级) 标签#ES6 #Promise #异步

问题描述: Promise.all、Promise.race、Promise.allSettled、Promise.any有什么区别?

参考答案要点

  • Promise.all:所有promise都成功时返回结果数组;任何一个失败立即reject
  • Promise.race:第一个settled的promise决定结果(无论成功失败)
  • Promise.allSettled:等待所有promise完成(无论成功失败),返回每个的结果状态数组
  • Promise.any:取第一个成功的Promise,若全部失败则返回AggregateError

5. async/await的实现原理

难度:⭐⭐⭐(高级) 标签#ES6 #async/await #Generator

问题描述: 请解释async/await的实现原理?

参考答案要点

  • async/await本质上是Generator函数的语法糖
  • async函数返回Promise对象
  • await将异步代码改造成同步写法,底层通过Generator和Promise实现
  • await后的表达式会被包装成Promise
  • 通过状态机和自动执行器实现"异步转同步"的效果

6. 解构赋值的应用

难度:⭐⭐(基础) 标签#ES6 #解构赋值

问题描述: 什么是解构赋值?数组和对象的解构有什么区别?

参考答案要点

  • 从数组或对象中提取值并赋值给变量
  • 数组解构按位置匹配:const [a, b] = [1, 2]
  • 对象解构按属性名匹配:const {name, age} = person
  • 可以设置默认值、嵌套解构、重命名

7. 扩展运算符(...)的应用场景

难度:⭐⭐(基础) 标签#ES6 #扩展运算符

问题描述: 扩展运算符有哪些常见用法?

参考答案要点

  • 数组展开:const arr2 = [...arr1, 4, 5]
  • 对象展开:const obj2 = {...obj1, newProp: 'value'}
  • 函数参数:rest参数收集剩余参数function fn(...args)
  • 数组/对象浅拷贝
  • 数组拼接替代concat

8. 可选链操作符(?.)和空值合并运算符(??)

难度:⭐⭐(基础) 标签#ES2020 #可选链 #空值合并

问题描述: 可选链操作符和空值合并运算符的作用是什么?

参考答案要点

  • 可选链?.:安全访问嵌套对象属性,避免undefined报错
    • const street = user?.address?.street
  • 空值合并??:只有null/undefined时使用默认值(区别于||)
    • const displayName = user.nickname ?? '匿名用户'
  • ??优先级低于?.,常与括号配合使用

9. ES6模块化与CommonJS的区别

难度:⭐⭐⭐(中级) 标签#ES6 #模块化 #CommonJS

问题描述: ES6 Module与CommonJS有什么区别?

参考答案要点

  • 语法:ESM使用import/export,CommonJS使用require/module.exports
  • 加载时机:ESM编译时静态加载,CommonJS运行时动态加载
  • 值拷贝vs引用:CommonJS输出值的拷贝,ESM输出值的引用
  • this指向:CommonJS中this指向当前模块,ESM中this指向undefined
  • 循环加载处理:ESM有更好的循环加载处理机制

10. Symbol类型的特点和用途

难度:⭐⭐⭐(中级) 标签#ES6 #Symbol #数据类型

问题描述: Symbol是什么?有什么应用场景?

参考答案要点

  • ES6新增的基本数据类型
  • 每个Symbol值都是唯一的,即使描述相同也不相等
  • 常用于对象属性名,避免属性名冲突
  • 可用于实现私有属性、常量枚举、消除魔术字符串
  • Symbol.for()可以创建/获取全局Symbol

11. Map与Object的区别

难度:⭐⭐(基础) 标签#ES6 #Map #Object

问题描述: Map和普通对象有什么区别?

参考答案要点

  • 键类型:Map键可以是任意类型,Object键只能是String/Symbol
  • 顺序性:Map按插入顺序遍历,Object不保证顺序
  • 大小获取:Map有size属性,Object需要Object.keys().length
  • 迭代:Map可直接迭代,Object需要转换
  • 性能:频繁增删键值对时Map性能更好

12. Set的去重原理

难度:⭐⭐(基础) 标签#ES6 #Set #数组去重

问题描述: 如何使用Set进行数组去重?

参考答案要点

  • const uniqueArr = [...new Set(arr)]
  • Set存储唯一值,重复值会被自动忽略
  • 对于引用类型,比较的是引用地址
  • 结合Array.from:Array.from(new Set(arr))

13. 模板字符串的高级用法

难度:⭐⭐(基础) 标签#ES6 #模板字符串

问题描述: 模板字符串相比普通字符串有哪些优势?

参考答案要点

  • 支持多行字符串,无需转义换行符
  • 支持字符串插值:`Hello ${name}`
  • 支持标签模板字符串:tag`Hello ${name}`
  • 可以在字符串中嵌入表达式和函数调用

14. Proxy与Object.defineProperty的区别

难度:⭐⭐⭐(高级) 标签#ES6 #Proxy #响应式 #Vue3

问题描述: Proxy与Object.defineProperty有什么区别?Vue3为什么选用Proxy?

参考答案要点

  • 拦截能力:Proxy可以拦截13种操作,Object.defineProperty只能拦截get/set
  • 新增属性:Proxy可以监听新增属性,Object.defineProperty需要额外处理
  • 数组:Proxy可以监听数组索引和length变化
  • 性能:Proxy初始化性能更好,但兼容性较差
  • Vue3选用Proxy是因为更强大的拦截能力和更好的数组支持

15. BigInt的使用场景

难度:⭐⭐(基础) 标签#ES2020 #BigInt #数据类型

问题描述: BigInt是什么?什么时候需要使用?

参考答案要点

  • ES2020引入,用于表示任意精度的整数
  • 超过Number.MAX_SAFE_INTEGER(2^53-1)的数字计算
  • 语法:在数字后加n,如12345678901234567890n
  • 不能与普通Number混合运算
  • typeof返回"bigint"

二、原型链和继承

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

难度:⭐⭐⭐(中级) 标签#原型链 #继承 #核心概念

问题描述: 请解释JavaScript中的原型和原型链?

参考答案要点

  • 原型:每个函数都有prototype属性,指向原型对象
  • **proto**:每个对象都有proto属性,指向其构造函数的prototype
  • 原型链:对象查找属性时,先在自身查找,找不到沿proto向上查找,直到Object.prototype.proto为null
  • 原型链实现了属性和方法的共享与继承

2. new操作符具体做了什么?

难度:⭐⭐⭐(中级) 标签#new #原型链 #手写题

问题描述: new操作符的执行过程是什么?

参考答案要点

  1. 创建一个空对象obj
  2. 将obj的proto指向构造函数的prototype
  3. 将构造函数的this指向obj
  4. 执行构造函数代码
  5. 如果构造函数返回对象则返回该对象,否则返回obj

代码示例

JAVASCRIPT
function myNew(constructor, ...args) {
  const obj = Object.create(constructor.prototype)
  const result = constructor.apply(obj, args)
  return result instanceof Object ? result : obj
}

3. JS实现继承的方式有哪些?

难度:⭐⭐⭐(中级) 标签#继承 #原型链 #设计模式

问题描述: JavaScript中有哪些继承方式?各自的优缺点?

参考答案要点

继承方式优点缺点
原型链继承写法简洁不能传参,引用类型共享
构造函数继承可以传参方法无法复用,不能继承原型方法
组合继承结合两者优点调用两次父类构造函数
原型式继承不需要构造函数引用类型共享
寄生式继承写法简单函数难以重用
寄生组合式继承效率高,只调用一次父构造函数代码复杂
ES6 Class extends语法简洁本质是语法糖

4. 原型链继承的问题

难度:⭐⭐⭐(中级) 标签#继承 #原型链

问题描述: 原型链继承有什么问题?如何解决?

参考答案要点

  • 问题1:不能向父类构造函数传递参数
  • 问题2:引用类型属性被所有实例共享
  • 解决:使用组合继承或寄生组合式继承
  • 组合继承 = 借用构造函数(解决传参)+ 原型链继承(继承原型方法)

5. ES6 Class与ES5构造函数的区别

难度:⭐⭐⭐(中级) 标签#ES6 #Class #继承

问题描述: ES6 Class和ES5构造函数实现继承有什么区别?

参考答案要点

  • ES5:先创建子类实例this,再将父类方法添加到this上(Parent.apply(this))
  • ES6:先将父类实例对象的属性和方法加到this上(必须先调用super()),再用子类构造函数修改this
  • Class语法更简洁,但本质是原型的语法糖
  • Class内部默认严格模式
  • Class的方法不可枚举

6. instanceof的原理

难度:⭐⭐⭐(中级) 标签#instanceof #原型链 #类型判断

问题描述: instanceof是如何判断的?

参考答案要点

  • 检查右边构造函数的prototype是否在左边对象的原型链上
  • 原理:left.__proto__.__proto__... === right.prototype
  • 不能判断基本数据类型
  • 可以自定义Symbol.hasInstance方法来改变行为

7. 如何实现一个完美的寄生组合式继承?

难度:⭐⭐⭐(高级) 标签#继承 #手写题 #寄生组合式继承

问题描述: 请手写寄生组合式继承的实现?

代码示例

JAVASCRIPT
function inheritPrototype(subType, superType) {
  // 创建父类原型的副本
  var prototype = Object.create(superType.prototype)
  // 修正constructor指向
  prototype.constructor = subType
  // 赋值给子类原型
  subType.prototype = prototype
}

function SuperType(name) {
  this.name = name
}
SuperType.prototype.sayName = function () {
  console.log(this.name)
}

function SubType(name, age) {
  SuperType.call(this, name) // 继承实例属性
  this.age = age
}
inheritPrototype(SubType, SuperType) // 继承原型方法

8. Object.create的作用

难度:⭐⭐(基础) 标签#Object.create #原型链

问题描述: Object.create()方法的作用是什么?

参考答案要点

  • 创建一个新对象,使用现有对象作为新对象的原型
  • Object.create(proto, propertiesObject)
  • 可以实现原型式继承
  • 创建空对象时传入null:Object.create(null)

9. hasOwnProperty的作用

难度:⭐⭐(基础) 标签#hasOwnProperty #原型链 #属性检测

问题描述: hasOwnProperty方法有什么作用?

参考答案要点

  • 判断属性是否为对象自身的属性(非继承属性)
  • 返回布尔值
  • 不会检查原型链上的属性
  • 常用在for...in循环中过滤继承属性

10. constructor属性的作用

难度:⭐⭐(基础) 标签#constructor #原型链

问题描述: constructor属性是什么?什么时候需要修正?

参考答案要点

  • 指向创建该实例的构造函数
  • 修改prototype后需要修正constructor指向
  • SubType.prototype.constructor = SubType
  • 用于判断对象的类型

11. 原型链的终点是什么?

难度:⭐⭐(基础) 标签#原型链 #Object.prototype

问题描述: 原型链的终点是什么?

参考答案要点

  • 原型链的终点是null
  • Object.prototype.__proto__ === null
  • Object.prototype是所有对象的最终原型

12. class中的static关键字

难度:⭐⭐(基础) 标签#ES6 #Class #static

问题描述: class中的static关键字有什么作用?

参考答案要点

  • 定义静态方法和静态属性
  • 静态方法/属性属于类本身,不属于实例
  • 通过类名直接调用:ClassName.staticMethod()
  • 静态方法中的this指向类本身

13. super关键字的作用

难度:⭐⭐⭐(中级) 标签#ES6 #Class #super

问题描述: class中的super关键字有哪些用法?

参考答案要点

  • 作为函数调用super(),调用父类构造函数,必须在子类构造函数第一行
  • 作为对象使用super.method(),调用父类原型上的方法
  • 静态方法中使用super指向父类本身
  • 实例方法中使用super指向父类原型

14. 如何实现多重继承?

难度:⭐⭐⭐(高级) 标签#继承 #Mixin #多重继承

问题描述: JavaScript如何实现多重继承?

参考答案要点

  • JavaScript原生不支持多重继承
  • 可以通过Mixin模式实现
  • 使用Object.assign()合并多个对象的方法
  • 或者使用组合优于继承的设计思想

15. 原型链的性能影响

难度:⭐⭐⭐(中级) 标签#原型链 #性能优化

问题描述: 原型链过长会有什么影响?

参考答案要点

  • 属性查找需要遍历原型链,链越长性能越差
  • 应避免过深的继承层次
  • 可以使用hasOwnProperty提前判断
  • 对于频繁访问的属性,可以缓存到本地变量

三、闭包和作用域

1. 什么是闭包?

难度:⭐⭐⭐(中级) 标签#闭包 #作用域 #核心概念

问题描述: 请解释JavaScript中的闭包?

参考答案要点

  • 闭包是指有权访问另一个函数作用域中变量的函数
  • 由函数和其创建时的词法环境组合而成
  • 即使外部函数执行完毕,内部函数仍能访问外部变量
  • 闭包可以"记住"它被创建时的环境

2. 闭包的应用场景

难度:⭐⭐⭐(中级) 标签#闭包 #应用场景

问题描述: 闭包有哪些常见的应用场景?

参考答案要点

  • 数据私有化/封装:创建私有变量和方法
  • 函数柯里化:参数复用
  • 防抖节流:保存定时器引用
  • 迭代器/生成器:维护内部状态
  • 模块化:IIFE创建私有作用域
  • 缓存/memoization:保存计算结果

3. 闭包导致的内存泄漏

难度:⭐⭐⭐(中级) 标签#闭包 #内存泄漏 #性能优化

问题描述: 闭包为什么会导致内存泄漏?如何避免?

参考答案要点

  • 闭包引用的外部变量不会被垃圾回收
  • 常见场景:事件监听器、定时器、DOM引用
  • 避免方法
    • 及时移除事件监听器
    • 清除定时器
    • 不再使用时将闭包设为null
    • 使用WeakMap/WeakSet

4. 词法作用域vs动态作用域

难度:⭐⭐⭐(中级) 标签#作用域 #词法作用域 #动态作用域

问题描述: 什么是词法作用域?与动态作用域有什么区别?

参考答案要点

  • 词法作用域(静态作用域):作用域在函数定义时确定
  • 动态作用域:作用域在函数调用时确定
  • JavaScript使用词法作用域
  • 函数的作用域链在定义时就已经确定,与调用位置无关

5. 作用域链的查找过程

难度:⭐⭐⭐(中级) 标签#作用域链 #变量查找

问题描述: 请解释作用域链的查找过程?

参考答案要点

  • 当访问变量时,JS引擎从当前作用域开始查找
  • 找不到则沿作用域链向上查找
  • 查找顺序:当前作用域 → 外层函数作用域 → ... → 全局作用域
  • 直到找到变量或到达全局作用域为止
  • 如果全局也找不到,则报错ReferenceError

6. 变量提升与函数提升

难度:⭐⭐⭐(中级) 标签#变量提升 #函数提升 #执行上下文

问题描述: 变量提升和函数提升有什么区别?

参考答案要点

  • 函数提升:函数声明整体提升,优先级高于变量提升
  • 变量提升:var声明的变量提升,初始化为undefined
  • let/const:有暂时性死区(TDZ),不会提升
  • 函数表达式不会被提升,只有函数声明会

7. 执行上下文的生命周期

难度:⭐⭐⭐(高级) 标签#执行上下文 #生命周期 #底层原理

问题描述: 执行上下文的生命周期是怎样的?

参考答案要点

  1. 创建阶段
    • 创建变量对象(VO)/活动对象(AO)
    • 建立作用域链
    • 确定this指向
  2. 执行阶段
    • 变量赋值
    • 函数引用
    • 执行代码

8. let/const的暂时性死区(TDZ)

难度:⭐⭐⭐(中级) 标签#ES6 #let #const #TDZ

问题描述: 什么是暂时性死区?

参考答案要点

  • 从块级作用域开始到let/const声明语句之间的区域
  • 在TDZ内访问变量会报错ReferenceError
  • 即使外部有同名变量,TDZ内也无法访问
  • typeof在TDZ内也会报错

9. 块级作用域的理解

难度:⭐⭐(基础) 标签#ES6 #块级作用域 #let #const

问题描述: ES6的块级作用域是什么?

参考答案要点

  • 使用let/const在{}内声明的变量具有块级作用域
  • 包括if/for/while等语句块
  • 块级作用域内的变量在块外不可访问
  • for循环的()部分和{}部分是两个作用域

10. 闭包经典面试题分析

难度:⭐⭐⭐(高级) 标签#闭包 #经典题目 #var #let

问题描述: 以下代码输出什么?为什么?

代码示例

JAVASCRIPT
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 0)
}

参考答案要点

  • 输出:3, 3, 3
  • 原因:var没有块级作用域,三个定时器共享同一个i
  • 解决方式1:使用let创建块级作用域
  • 解决方式2:使用IIFE创建闭包
  • 解决方式3:使用forEach

11. 立即执行函数(IIFE)的作用

难度:⭐⭐(基础) 标签#IIFE #闭包 #模块化

问题描述: IIFE有什么作用?

参考答案要点

  • 创建独立的作用域,避免变量污染全局
  • 实现模块化,封装私有变量
  • 保存循环中的索引值
  • 执行一次性初始化逻辑

12. 闭包与作用域链的关系

难度:⭐⭐⭐(高级) 标签#闭包 #作用域链 #底层原理

问题描述: 闭包和作用域链有什么关系?

参考答案要点

  • 闭包依赖于作用域链来维持对外部变量的访问
  • 作用域链是闭包能够跨作用域访问变量的桥梁
  • 闭包保留了对其词法环境中作用域链的引用
  • 即使外部函数执行完毕,作用域链也不会被销毁

13. 如何检测内存泄漏?

难度:⭐⭐⭐(高级) 标签#内存泄漏 #性能优化 #调试技巧

问题描述: 如何检测和排查闭包导致的内存泄漏?

参考答案要点

  • 使用Chrome DevTools的Memory面板
  • 拍摄堆快照(Heap Snapshot)对比分析
  • 使用Performance面板监控内存趋势
  • 使用Allocation Timeline追踪内存分配
  • 关注Detached DOM tree

14. with语句的作用域问题

难度:⭐⭐⭐(高级) 标签#with #作用域 #严格模式

问题描述: with语句有什么问题?为什么不推荐使用?

参考答案要点

  • with语句会动态改变作用域链
  • 导致性能下降(无法优化变量查找)
  • 代码难以预测和调试
  • 严格模式下禁止使用
  • 在ES6模块中不可用

15. 函数作用域与块级作用域的区别

难度:⭐⭐(基础) 标签#作用域 #函数作用域 #块级作用域

问题描述: 函数作用域和块级作用域有什么区别?

参考答案要点

  • 函数作用域:var声明的变量在函数内有效
  • 块级作用域:let/const声明的变量在{}块内有效
  • 块级作用域更精细,更符合其他语言的约定
  • 块级作用域有助于减少变量污染

四、事件循环

1. 什么是事件循环?

难度:⭐⭐⭐(中级) 标签#事件循环 #EventLoop #异步

问题描述: 请解释JavaScript的事件循环机制?

参考答案要点

  • JavaScript是单线程语言,事件循环是处理异步任务的机制
  • 事件循环不断检查调用栈是否为空
  • 如果为空,从任务队列中取出任务执行
  • 确保非阻塞的任务执行,保持流畅的用户体验

2. 宏任务与微任务的区别

难度:⭐⭐⭐(中级) 标签#宏任务 #微任务 #事件循环

问题描述: 宏任务和微任务有什么区别?执行顺序如何?

参考答案要点

特性宏任务(Macro Task)微任务(Micro Task)
常见类型setTimeout、setInterval、I/O、UI渲染Promise.then、MutationObserver、queueMicrotask
执行时机每个事件循环周期执行一次当前宏任务结束后立即执行
优先级

执行顺序:同步代码 → 微任务队列清空 → 宏任务 → 微任务队列清空 → 下一个宏任务...


3. Promise.then的执行时机

难度:⭐⭐⭐(中级) 标签#Promise #微任务 #事件循环

问题描述: Promise.then是宏任务还是微任务?

参考答案要点

  • Promise.then/catch/finally是微任务
  • Promise本身是同步执行的
  • then回调放入微任务队列
  • 微任务在当前宏任务结束后立即执行

4. async/await与事件循环

难度:⭐⭐⭐(高级) 标签#async/await #事件循环 #微任务

问题描述: async/await在事件循环中如何执行?

参考答案要点

  • async函数返回Promise
  • await会暂停async函数执行,等待Promise解决
  • await后面的代码相当于放在Promise.then中(微任务)
  • async函数内部的代码是同步执行的,遇到await后让出主线程

5. 经典输出题分析

难度:⭐⭐⭐(高级) 标签#事件循环 #经典题目 #输出顺序

问题描述: 以下代码输出顺序是什么?

代码示例

JAVASCRIPT
console.log("1")
setTimeout(() => console.log("2"), 0)
Promise.resolve().then(() => console.log("3"))
console.log("4")

参考答案要点

  • 输出顺序:1 → 4 → 3 → 2
  • 1和4是同步代码,先执行
  • Promise.then是微任务,在同步代码后执行
  • setTimeout是宏任务,在微任务后执行

6. Node.js与浏览器的事件循环差异

难度:⭐⭐⭐(高级) 标签#Node.js #事件循环 #浏览器差异

问题描述: Node.js和浏览器的事件循环有什么区别?

参考答案要点

  • 浏览器:宏任务队列 + 微任务队列
  • Node.js:多个阶段(timers、I/O callbacks、idle/prepare、poll、check、close callbacks)
  • Node.js有process.nextTick,优先级高于微任务
  • Node.js 11之后,微任务在每个宏任务后执行(与浏览器一致)

7. requestAnimationFrame的执行时机

难度:⭐⭐⭐(中级) 标签#requestAnimationFrame #动画 #渲染

问题描述: requestAnimationFrame属于宏任务还是微任务?

参考答案要点

  • requestAnimationFrame不属于宏任务也不属于微任务
  • 它在浏览器重绘之前执行
  • 优先级比宏任务高,比微任务低
  • 适合用于动画更新

8. 复杂异步代码分析

难度:⭐⭐⭐(高级) 标签#事件循环 #async/await #经典题目

问题描述: 以下代码输出顺序?

代码示例

JAVASCRIPT
async function async1() {
  console.log("A")
  await async2()
  console.log("B")
}
async function async2() {
  console.log("C")
}
console.log("D")
setTimeout(() => console.log("E"), 0)
async1()
new Promise((resolve) => {
  console.log("F")
  resolve()
}).then(() => console.log("G"))
console.log("H")

参考答案要点

  • 输出:D → A → C → F → H → B → G → E
  • D是同步代码
  • async1()执行,输出A,await async2()输出C
  • Promise构造函数同步执行,输出F
  • H是同步代码
  • 微任务队列:B(await后续)、G(Promise.then)
  • 宏任务:E

9. 为什么setTimeout会有延迟?

难度:⭐⭐⭐(中级) 标签#setTimeout #事件循环 #宏任务

问题描述: setTimeout(fn, 0)真的能立即执行吗?

参考答案要点

  • 不能立即执行,最小延迟约为4ms(HTML5规范)
  • 即使设为0,也会放入宏任务队列等待
  • 需要等待当前执行栈和微任务队列清空
  • 浏览器可能有最小延迟限制

10. MutationObserver的使用

难度:⭐⭐⭐(中级) 标签#MutationObserver #DOM #微任务

问题描述: MutationObserver是什么?有什么用?

参考答案要点

  • 用于监听DOM变化的API
  • 是微任务,在DOM变化后异步执行
  • 可以监听子节点变化、属性变化、文本变化
  • 相比Mutation Event性能更好

11. queueMicrotask的作用

难度:⭐⭐⭐(中级) 标签#queueMicrotask #微任务 #API

问题描述: queueMicrotask是什么?

参考答案要点

  • 用于将一个函数放入微任务队列
  • 比Promise.then更直接的微任务创建方式
  • queueMicrotask(() => console.log('microtask'))
  • 确保在当前任务结束后立即执行

12. 事件循环与页面渲染的关系

难度:⭐⭐⭐(高级) 标签#事件循环 #渲染 #性能优化

问题描述: 事件循环如何影响页面渲染?

参考答案要点

  • 浏览器通常以60fps(约16.7ms/帧)渲染
  • 每个事件循环周期可能包含渲染步骤
  • 长时间运行的JavaScript会阻塞渲染
  • 使用requestAnimationFrame配合渲染
  • 将大任务拆分为小任务,避免阻塞

五、异步编程

1. 回调地狱及解决方案

难度:⭐⭐(基础) 标签#回调地狱 #Promise #异步

问题描述: 什么是回调地狱?如何解决?

参考答案要点

  • 回调地狱:多层嵌套的回调函数,代码难以阅读和维护
  • 解决方案
    • Promise链式调用
    • async/await
    • 使用async库等工具
    • 将回调函数抽离为命名函数

2. Promise.all的实现原理

难度:⭐⭐⭐(高级) 标签#Promise #手写题 #并发

问题描述: 请手写实现Promise.all?

代码示例

JAVASCRIPT
Promise.myAll = function (promises) {
  return new Promise((resolve, reject) => {
    if (!Array.isArray(promises)) {
      return reject(new TypeError("Argument must be an array"))
    }
    const results = []
    let completedCount = 0

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

    if (promises.length === 0) {
      resolve(results)
    }
  })
}

3. Promise.race的应用场景

难度:⭐⭐⭐(中级) 标签#Promise #race #超时处理

问题描述: Promise.race有什么应用场景?

参考答案要点

  • 设置请求超时:Promise.race([fetch(url), timeout(5000)])
  • 多个数据源取最快返回的
  • 竞态条件处理
  • 取消操作(配合AbortController)

4. async/await的错误处理

难度:⭐⭐⭐(中级) 标签#async/await #错误处理 #try/catch

问题描述: async/await如何进行错误处理?

参考答案要点

  • try/catch:在async函数内部使用
  • .catch():在async函数调用后链式调用
  • 多个await可以共用一个try/catch
  • 可以使用Promise.all配合try/catch

5. 并发控制(限制请求数量)

难度:⭐⭐⭐(高级) 标签#并发控制 #手写题 #异步

问题描述: 如何实现并发请求数量限制?

代码示例

JAVASCRIPT
async function asyncPool(poolLimit, array, iteratorFn) {
  const ret = []
  const executing = []
  for (const item of array) {
    const p = Promise.resolve().then(() => iteratorFn(item))
    ret.push(p)
    if (poolLimit <= array.length) {
      const e = p.then(() => executing.splice(executing.indexOf(e), 1))
      executing.push(e)
      if (executing.length >= poolLimit) {
        await Promise.race(executing)
      }
    }
  }
  return Promise.all(ret)
}

6. 异步迭代器(Async Iterator)

难度:⭐⭐⭐(高级) 标签#ES2018 #异步迭代器 #for await of

问题描述: 什么是异步迭代器?如何使用?

参考答案要点

  • 用于遍历异步数据源的接口
  • 使用for await...of遍历
  • 对象需要实现[Symbol.asyncIterator]方法
  • 适用于流式数据处理

7. Promise与async/await的区别

难度:⭐⭐⭐(中级) 标签#Promise #async/await #对比

问题描述: Promise和async/await有什么区别?

参考答案要点

  • async/await是Promise的语法糖
  • async/await代码更易读,更像同步代码
  • async/await更易于调试(可以设置断点)
  • Promise更适合处理并发(Promise.all等)
  • async/await的错误处理更直观(try/catch)

8. 串行执行vs并行执行

难度:⭐⭐⭐(中级) 标签#异步 #串行 #并行

问题描述: 如何让多个异步操作串行执行或并行执行?

参考答案要点

  • 串行执行:使用for循环配合await,或reduce链式调用
  • 并行执行:使用Promise.all
  • 部分并行:根据依赖关系分组
  • 注意:无依赖的异步操作应该并行执行以提高性能

9. 手写Promise(简易版)

难度:⭐⭐⭐(高级) 标签#Promise #手写题 #核心原理

问题描述: 请手写一个简易的Promise实现?

代码示例

JAVASCRIPT
class MyPromise {
  constructor(executor) {
    this.state = "pending"
    this.value = undefined
    this.reason = undefined
    this.onFulfilledCallbacks = []
    this.onRejectedCallbacks = []

    const resolve = (value) => {
      if (this.state === "pending") {
        this.state = "fulfilled"
        this.value = value
        this.onFulfilledCallbacks.forEach((fn) => fn())
      }
    }

    const reject = (reason) => {
      if (this.state === "pending") {
        this.state = "rejected"
        this.reason = reason
        this.onRejectedCallbacks.forEach((fn) => fn())
      }
    }

    try {
      executor(resolve, reject)
    } catch (e) {
      reject(e)
    }
  }

  then(onFulfilled, onRejected) {
    if (this.state === "fulfilled") {
      onFulfilled(this.value)
    }
    if (this.state === "rejected") {
      onRejected(this.reason)
    }
    if (this.state === "pending") {
      this.onFulfilledCallbacks.push(() => onFulfilled(this.value))
      this.onRejectedCallbacks.push(() => onRejected(this.reason))
    }
  }
}

10. 取消一个Promise

难度:⭐⭐⭐(高级) 标签#Promise #取消 #AbortController

问题描述: 如何取消一个正在执行的Promise?

参考答案要点

  • Promise本身不可取消
  • 可以使用AbortController(fetch API支持)
  • 包装Promise,添加取消标记
  • 使用第三方库如p-cancelable

11. Promise.finally的作用

难度:⭐⭐(基础) 标签#Promise #finally #资源清理

问题描述: Promise.finally有什么作用?

参考答案要点

  • 无论Promise成功还是失败都会执行
  • 用于清理资源
  • 不接受任何参数
  • 返回一个新的Promise

12. 异步任务的优先级控制

难度:⭐⭐⭐(高级) 标签#异步 #优先级 #性能优化

问题描述: 如何控制异步任务的优先级?

参考答案要点

  • 使用微任务(Promise)优先于宏任务
  • 使用queueMicrotask创建高优先级任务
  • 自定义任务队列管理优先级
  • 使用requestIdleCallback处理低优先级任务

六、this指向和bind/call/apply

1. this的四种绑定规则

难度:⭐⭐⭐(中级) 标签#this #绑定规则 #核心概念

问题描述: this的绑定规则有哪些?优先级如何?

参考答案要点

绑定规则this指向示例
默认绑定全局对象(严格模式undefined)foo()
隐式绑定调用上下文对象obj.foo()
显式绑定指定对象foo.call(obj)
new绑定新创建的实例new Foo()
箭头函数继承外层this() => {}

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


2. call、apply、bind的区别

难度:⭐⭐⭐(中级) 标签#call #apply #bind #this

问题描述: call、apply、bind有什么区别?

参考答案要点

方法参数形式执行时机返回值
call参数列表立即执行函数返回值
apply参数数组立即执行函数返回值
bind参数列表返回新函数,不立即执行绑定this的新函数

3. 手写call方法

难度:⭐⭐⭐(高级) 标签#call #手写题 #this

问题描述: 请手写实现Function.prototype.call?

代码示例

JAVASCRIPT
Function.prototype.myCall = function (context, ...args) {
  context = context || window
  const fn = Symbol("fn")
  context[fn] = this
  const result = context[fn](...args)
  delete context[fn]
  return result
}

4. 手写apply方法

难度:⭐⭐⭐(高级) 标签#apply #手写题 #this

问题描述: 请手写实现Function.prototype.apply?

代码示例

JAVASCRIPT
Function.prototype.myApply = function (context, args) {
  context = context || window
  const fn = Symbol("fn")
  context[fn] = this
  const result = args ? context[fn](...args) : context[fn]()
  delete context[fn]
  return result
}

5. 手写bind方法

难度:⭐⭐⭐(高级) 标签#bind #手写题 #this #柯里化

问题描述: 请手写实现Function.prototype.bind?

代码示例

JAVASCRIPT
Function.prototype.myBind = function (context, ...args1) {
  const fn = this
  return function (...args2) {
    return fn.apply(context, [...args1, ...args2])
  }
}

6. 箭头函数的this特性

难度:⭐⭐⭐(中级) 标签#箭头函数 #this #ES6

问题描述: 箭头函数的this有什么特点?

参考答案要点

  • 箭头函数没有自己的this
  • this继承自外层作用域(定义时的上下文)
  • 不能用call/apply/bind改变this指向
  • 不能作为构造函数使用
  • 适合用于回调函数,避免this指向问题

7. 隐式绑定的丢失

难度:⭐⭐⭐(中级) 标签#this #隐式绑定 #经典问题

问题描述: 什么是隐式绑定的丢失?举例说明?

代码示例

JAVASCRIPT
const obj = {
  name: "obj",
  getName: function () {
    console.log(this.name)
  },
}
const fn = obj.getName
fn() // undefined,this指向全局

// 回调函数中的丢失
setTimeout(obj.getName, 0) // undefined

参考答案要点

  • 当函数被赋值给变量或作为回调时,this会丢失

8. 严格模式下的this

难度:⭐⭐(基础) 标签#this #严格模式

问题描述: 严格模式下this有什么不同?

参考答案要点

  • 全局作用域中this是undefined(非严格模式是window)
  • 普通函数调用this是undefined
  • 不影响call/apply/bind的显式绑定
  • 不影响new绑定
  • 不影响箭头函数

9. DOM事件中的this

难度:⭐⭐(基础) 标签#this #DOM #事件

问题描述: DOM事件处理函数中的this指向什么?

参考答案要点

  • 普通函数:指向触发事件的DOM元素
  • 箭头函数:继承外层this
  • addEventListener的回调:指向绑定事件的元素
  • 内联事件:指向window

10. 类中的this指向

难度:⭐⭐⭐(中级) 标签#this #Class #ES6

问题描述: class中的this有什么特点?

参考答案要点

  • 类的方法中的this指向实例
  • 但提取方法单独调用时会丢失this
  • 可以在constructor中绑定:this.method = this.method.bind(this)
  • 或使用箭头函数定义方法
  • 静态方法中的this指向类本身

11. call/apply的实际应用场景

难度:⭐⭐⭐(中级) 标签#call #apply #应用场景

问题描述: call/apply有哪些实际应用场景?

参考答案要点

  • 类型判断Object.prototype.toString.call(obj)
  • 借用方法Array.prototype.slice.call(arguments)
  • 继承实现:在子类构造函数中调用父类构造函数
  • Math方法Math.max.apply(null, arr)
  • 数组扁平化Array.prototype.concat.apply([], arr)

12. bind的柯里化应用

难度:⭐⭐⭐(高级) 标签#bind #柯里化 #函数式编程

问题描述: 如何利用bind实现函数柯里化?

代码示例

JAVASCRIPT
function add(a, b, c) {
  return a + b + c
}
const add5 = add.bind(null, 5)
console.log(add5(10, 15)) // 30

// 结合占位符实现部分应用
const _ = {}
function partial(fn, ...presetArgs) {
  return function (...laterArgs) {
    let argIndex = 0
    const finalArgs = presetArgs.map((arg) =>
      arg === _ ? laterArgs[argIndex++] : arg
    )
    return fn(...finalArgs, ...laterArgs.slice(argIndex))
  }
}

七、深拷贝和浅拷贝

1. 深拷贝与浅拷贝的区别

难度:⭐⭐(基础) 标签#深拷贝 #浅拷贝 #核心概念

问题描述: 深拷贝和浅拷贝有什么区别?

参考答案要点

特性浅拷贝深拷贝
复制层级只复制第一层递归复制所有层级
引用类型共享引用创建新引用
修改影响影响原对象不影响原对象
性能

2. 浅拷贝的实现方式

难度:⭐⭐(基础) 标签#浅拷贝 #Object.assign #扩展运算符

问题描述: 有哪些实现浅拷贝的方法?

参考答案要点

  • Object.assign(target, source)
  • 扩展运算符:{...obj}[...arr]
  • Array.prototype.slice()
  • Array.prototype.concat()
  • Array.from()

3. JSON.parse(JSON.stringify())的局限

难度:⭐⭐⭐(中级) 标签#深拷贝 #JSON #局限性

问题描述: 使用JSON方法深拷贝有什么局限性?

参考答案要点

  • 无法复制函数
  • 无法复制undefined(会被忽略或转为null)
  • 无法复制Symbol
  • 无法处理循环引用(会报错)
  • 无法复制Date对象(会变成字符串)
  • 无法复制RegExp、Error等特殊对象
  • 无法复制Map、Set

4. 手写深拷贝(基础版)

难度:⭐⭐⭐(中级) 标签#深拷贝 #手写题 #递归

问题描述: 请手写一个深拷贝函数?

代码示例

JAVASCRIPT
function deepClone(obj) {
  if (obj === null || typeof obj !== "object") {
    return obj
  }

  const clone = Array.isArray(obj) ? [] : {}

  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      clone[key] = deepClone(obj[key])
    }
  }

  return clone
}

5. 手写深拷贝(完整版)

难度:⭐⭐⭐(高级) 标签#深拷贝 #手写题 #循环引用

问题描述: 请实现一个支持循环引用、Date、RegExp的深拷贝?

代码示例

JAVASCRIPT
function deepClone(obj, map = new WeakMap()) {
  if (obj === null || typeof obj !== "object") {
    return obj
  }

  // 处理Date
  if (obj instanceof Date) {
    return new Date(obj.getTime())
  }

  // 处理RegExp
  if (obj instanceof RegExp) {
    return new RegExp(obj)
  }

  // 处理循环引用
  if (map.has(obj)) {
    return map.get(obj)
  }

  const clone = Array.isArray(obj) ? [] : {}
  map.set(obj, clone)

  // 处理Symbol属性
  const symKeys = Object.getOwnPropertySymbols(obj)
  for (const symKey of symKeys) {
    clone[symKey] = deepClone(obj[symKey], map)
  }

  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      clone[key] = deepClone(obj[key], map)
    }
  }

  return clone
}

6. 循环引用的处理

难度:⭐⭐⭐(高级) 标签#深拷贝 #循环引用 #WeakMap

问题描述: 深拷贝中如何处理循环引用?

参考答案要点

  • 使用WeakMap存储已拷贝的对象
  • 拷贝前检查WeakMap中是否已存在
  • 如果存在,直接返回已拷贝的对象
  • 避免无限递归和栈溢出

7. 深拷贝的性能优化

难度:⭐⭐⭐(高级) 标签#深拷贝 #性能优化

问题描述: 如何优化深拷贝的性能?

参考答案要点

  • 使用WeakMap避免重复拷贝
  • 对于简单对象,优先使用JSON方法
  • 使用迭代代替递归(避免栈溢出)
  • 使用MessageChannel进行异步深拷贝
  • 使用第三方库如lodash的cloneDeep

8. 结构化克隆算法

难度:⭐⭐⭐(高级) 标签#深拷贝 #结构化克隆 #MessageChannel

问题描述: 什么是结构化克隆算法?

参考答案要点

  • 浏览器内部使用的深拷贝算法
  • 通过postMessage、IndexedDB等API暴露
  • 支持更多类型:File、Blob、ArrayBuffer等

代码示例

JAVASCRIPT
function structuredClone(obj) {
  return new Promise((resolve) => {
    const { port1, port2 } = new MessageChannel()
    port2.onmessage = (ev) => resolve(ev.data)
    port1.postMessage(obj)
  })
}

9. 深拷贝的应用场景

难度:⭐⭐(基础) 标签#深拷贝 #应用场景

问题描述: 什么时候需要使用深拷贝?

参考答案要点

  • 需要完全独立的副本时
  • 避免修改原对象(函数式编程)
  • Redux等状态管理中
  • 表单数据的备份和重置
  • 复杂对象的比较

10. 函数拷贝的特殊处理

难度:⭐⭐⭐(高级) 标签#深拷贝 #函数 #特殊情况

问题描述: 深拷贝中如何处理函数?

参考答案要点

  • 函数通常不需要深拷贝(引用即可)
  • 如果需要拷贝,可以使用eval或new Function
  • 但会丢失闭包和原型链信息
  • 一般不建议深拷贝函数

11. Map和Set的深拷贝

难度:⭐⭐⭐(中级) 标签#深拷贝 #Map #Set

问题描述: 如何深拷贝Map和Set?

代码示例

JAVASCRIPT
function deepClone(obj) {
  if (obj instanceof Map) {
    const clone = new Map()
    obj.forEach((value, key) => {
      clone.set(deepClone(key), deepClone(value))
    })
    return clone
  }

  if (obj instanceof Set) {
    const clone = new Set()
    obj.forEach((value) => {
      clone.add(deepClone(value))
    })
    return clone
  }

  // ...其他处理
}

12. 深拷贝与不可变数据

难度:⭐⭐⭐(中级) 标签#深拷贝 #不可变数据 #函数式编程

问题描述: 深拷贝与不可变数据有什么关系?

参考答案要点

  • 深拷贝是实现不可变数据的一种方式
  • 不可变数据要求修改时返回新对象
  • 可以使用Immutable.js等库
  • 也可以使用Object.freeze()冻结对象
  • 函数式编程中常用

八、类型判断和类型转换

1. typeof的返回值

难度:⭐⭐(基础) 标签#typeof #类型判断

问题描述: typeof操作符能返回哪些值?有什么局限?

参考答案要点

类型typeof返回值
number"number"
string"string"
boolean"boolean"
undefined"undefined"
symbol"symbol"
bigint"bigint"
function"function"
object"object"
null"object"(历史bug)
array"object"(无法区分)

局限:无法区分数组、null、普通对象


2. instanceof的原理和局限

难度:⭐⭐⭐(中级) 标签#instanceof #原型链 #类型判断

问题描述: instanceof是如何工作的?有什么局限?

参考答案要点

  • 检查右边构造函数的prototype是否在左边对象的原型链上
  • 可以判断对象的类型
  • 局限
    • 不能判断基本数据类型
    • 跨iframe/window会失效(不同全局环境)
    • 原型链被修改后可能不准确

3. Object.prototype.toString的判断

难度:⭐⭐⭐(中级) 标签#类型判断 #toString #最可靠方法

问题描述: 为什么Object.prototype.toString可以准确判断类型?

参考答案要点

  • 返回"[object Type]"格式的字符串
  • 可以判断所有内置类型
  • Object.prototype.toString.call([]) → "[object Array]"
  • Object.prototype.toString.call(null) → "[object Null]"
  • 是最可靠的类型判断方法

4. 手写类型判断函数

难度:⭐⭐⭐(中级) 标签#类型判断 #手写题 #工具函数

问题描述: 请实现一个通用的类型判断函数?

代码示例

JAVASCRIPT
function getType(obj) {
  const type = typeof obj
  if (type !== "object") {
    return type
  }
  return Object.prototype.toString
    .call(obj)
    .replace(/^\[object (\S+)\]$/, "$1")
    .toLowerCase()
}

// 使用
getType([]) // 'array'
getType(null) // 'null'
getType(new Date()) // 'date'

5. == 与 === 的区别

难度:⭐⭐(基础) 标签#相等判断 #类型转换 #严格相等

问题描述: == 和 === 有什么区别?

参考答案要点

  • ===:严格相等,值和类型都相同
  • ==:宽松相等,会进行类型转换后再比较
  • 推荐使用===,避免隐式类型转换的意外行为
  • ==的转换规则复杂,容易出错

6. 隐式类型转换规则

难度:⭐⭐⭐(中级) 标签#类型转换 #隐式转换 #面试陷阱

问题描述: JavaScript有哪些隐式类型转换规则?

参考答案要点

场景转换规则
字符串 + 其他其他转为字符串
数字 + 其他(非字符串)其他转为数字
== 比较转为数字或字符串比较
if/while条件转为布尔值
+单目运算符转为数字
!运算符转为布尔值再取反

7. 对象转原始类型

难度:⭐⭐⭐(高级) 标签#类型转换 #对象 #toPrimitive

问题描述: 对象如何转换为原始类型?

参考答案要点

  • 调用对象的[Symbol.toPrimitive]方法(如果存在)
  • 否则,如果是字符串上下文,调用toString()
  • 如果是数字上下文,调用valueOf()
  • 如果valueOf返回非原始值,再调用toString()

8. 经典类型转换面试题

难度:⭐⭐⭐(中级) 标签#类型转换 #经典题目 #面试陷阱

问题描述: 以下表达式结果是什么?

代码示例

JAVASCRIPT
[] + []
[] + {}
{} + []
{} + {}
[] == ![]

参考答案要点

  • [] + [] → ""(空字符串)
  • [] + {} → "[object Object]"
  • {} + [] → 0({}被解析为代码块)
  • {} + {} → "[object Object][object Object]"(非严格模式)
  • [] == ![] → true([]转数字为0,![]为false也转0)

9. Array.isArray的实现

难度:⭐⭐⭐(中级) 标签#Array.isArray #类型判断 #数组

问题描述: Array.isArray是如何实现的?

代码示例

JAVASCRIPT
// 简易实现
function isArray(obj) {
  return Object.prototype.toString.call(obj) === "[object Array]"
}

// 或者使用instanceof(有跨frame问题)
function isArray(obj) {
  return obj instanceof Array
}

// 或者使用Array.isArray(最可靠)
Array.isArray([]) // true

10. NaN的判断

难度:⭐⭐(基础) 标签#NaN #类型判断 #特殊值

问题描述: 如何判断一个值是否为NaN?

参考答案要点

  • isNaN()会先尝试转换为数字,再判断
  • Number.isNaN()更严格,不会转换类型
  • Object.is(NaN, NaN) → true
  • NaN是唯一不等于自身的值:NaN !== NaN

11. null和undefined的区别

难度:⭐⭐(基础) 标签#null #undefined #区别

问题描述: null和undefined有什么区别?

参考答案要点

  • undefined:变量声明但未赋值;函数无返回值;对象无该属性
  • null:表示空值,需要显式赋值
  • typeof null → "object"(历史bug)
  • typeof undefined → "undefined"
  • 建议用===判断,不要用==

12. 包装对象

难度:⭐⭐(基础) 标签#包装对象 #基本类型 #自动装箱

问题描述: 什么是包装对象?

参考答案要点

  • 基本类型在调用方法时会临时转为包装对象
  • String、Number、Boolean
  • 操作完成后立即销毁
  • 'abc'.length实际执行:(new String('abc')).length
  • 不建议显式创建包装对象

九、数组和对象方法

1. 数组去重的方法

难度:⭐⭐(基础) 标签#数组去重 #Set #算法

问题描述: 数组去重有哪些方法?

参考答案要点

  • Set[...new Set(arr)](最简洁)
  • filter + indexOfarr.filter((item, i) => arr.indexOf(item) === i)
  • reducearr.reduce((acc, cur) => acc.includes(cur) ? acc : [...acc, cur], [])
  • 对象键值对:适用于基本类型

2. 数组扁平化

难度:⭐⭐⭐(中级) 标签#数组扁平化 #递归 #手写题

问题描述: 如何实现数组扁平化?

代码示例

JAVASCRIPT
// ES6 flat
arr.flat(Infinity)

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

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

3. 高阶函数的理解

难度:⭐⭐(基础) 标签#高阶函数 #函数式编程 #核心概念

问题描述: 什么是高阶函数?

参考答案要点

  • 接收函数作为参数的函数
  • 返回函数的函数
  • 常见高阶函数:map、filter、reduce、forEach、sort等
  • 是函数式编程的核心概念

4. map、filter、reduce的区别

难度:⭐⭐(基础) 标签#map #filter #reduce #数组方法

问题描述: map、filter、reduce有什么区别?

参考答案要点

方法作用返回值是否改变原数组
map映射,每个元素转换新数组
filter过滤,保留符合条件的新数组
reduce归约,汇总为单个值任意类型

5. 手写map方法

难度:⭐⭐⭐(中级) 标签#map #手写题 #数组方法

问题描述: 请手写实现Array.prototype.map?

代码示例

JAVASCRIPT
Array.prototype.myMap = function (callback, thisArg) {
  if (this == null) {
    throw new TypeError("this is null or not defined")
  }
  const result = []
  for (let i = 0; i < this.length; i++) {
    if (i in this) {
      result[i] = callback.call(thisArg, this[i], i, this)
    }
  }
  return result
}

6. 手写filter方法

难度:⭐⭐⭐(中级) 标签#filter #手写题 #数组方法

问题描述: 请手写实现Array.prototype.filter?

代码示例

JAVASCRIPT
Array.prototype.myFilter = function (callback, thisArg) {
  if (this == null) {
    throw new TypeError("this is null or not defined")
  }
  const result = []
  for (let i = 0; i < this.length; i++) {
    if (i in this && callback.call(thisArg, this[i], i, this)) {
      result.push(this[i])
    }
  }
  return result
}

7. 手写reduce方法

难度:⭐⭐⭐(中级) 标签#reduce #手写题 #数组方法

问题描述: 请手写实现Array.prototype.reduce?

代码示例

JAVASCRIPT
Array.prototype.myReduce = function (callback, initialValue) {
  if (this == null) {
    throw new TypeError("this is null or not defined")
  }
  let accumulator = initialValue
  let startIndex = 0

  if (arguments.length < 2) {
    accumulator = this[0]
    startIndex = 1
  }

  for (let i = startIndex; i < this.length; i++) {
    if (i in this) {
      accumulator = callback(accumulator, this[i], i, this)
    }
  }

  return accumulator
}

8. sort方法的注意事项

难度:⭐⭐⭐(中级) 标签#sort #数组排序 #注意事项

问题描述: Array.prototype.sort有什么需要注意的?

参考答案要点

  • 默认按字符串Unicode码点排序
  • 数字排序需要传入比较函数:(a, b) => a - b
  • 会改变原数组
  • 排序不稳定(不同浏览器实现可能不同)
  • 空位会被移到末尾

9. splice与slice的区别

难度:⭐⭐(基础) 标签#splice #slice #数组方法

问题描述: splice和slice有什么区别?

参考答案要点

特性spliceslice
作用删除/添加元素截取部分数组
改变原数组
返回值被删除的元素数组新数组
参数起始位置、删除个数、新增元素起始位置、结束位置

10. 类数组转数组的方法

难度:⭐⭐(基础) 标签#类数组 #Array.from #转换

问题描述: 类数组如何转换为真正的数组?

参考答案要点

  • Array.from(arrayLike)
  • Array.prototype.slice.call(arrayLike)
  • [...arrayLike](需要有迭代器)
  • Array.apply(null, arrayLike)

11. Object.keys/values/entries

难度:⭐⭐(基础) 标签#Object.keys #Object.values #Object.entries

问题描述: Object.keys、Object.values、Object.entries的作用?

参考答案要点

  • Object.keys:返回对象自身可枚举属性的键数组
  • Object.values:返回对象自身可枚举属性的值数组
  • Object.entries:返回[key, value]数组的数组
  • 都不包含继承的属性
  • 不包含Symbol属性

12. Object.assign的局限

难度:⭐⭐⭐(中级) 标签#Object.assign #浅拷贝 #局限性

问题描述: Object.assign有什么局限?

参考答案要点

  • 是浅拷贝,不是深拷贝
  • 不能拷贝不可枚举属性
  • 不能拷贝Symbol属性
  • 不能拷贝getter/setter,会执行并拷贝返回值
  • 会触发setter

13. Object.create的用法

难度:⭐⭐(基础) 标签#Object.create #原型链 #继承

问题描述: Object.create有哪些常见用法?

参考答案要点

  • 创建以指定对象为原型的对象
  • 实现原型式继承
  • 创建纯净对象(无原型):Object.create(null)
  • 创建具有指定属性的对象(第二个参数)

14. 数组的迭代器方法

难度:⭐⭐⭐(中级) 标签#迭代器 #keys #values #entries

问题描述: 数组有哪些迭代器方法?

参考答案要点

  • keys():返回索引迭代器
  • values():返回值迭代器
  • entries():返回[index, value]迭代器
  • @@iterator:默认迭代器,等同于values()
  • 可以用for...of遍历

15. find与findIndex的区别

难度:⭐⭐(基础) 标签#find #findIndex #数组方法

问题描述: find和findIndex有什么区别?

参考答案要点

  • find:返回第一个符合条件的元素,找不到返回undefined
  • findIndex:返回第一个符合条件的索引,找不到返回-1
  • 都接收回调函数和thisArg参数
  • 都会短路,找到后立即返回

十、函数式编程

1. 纯函数的理解

难度:⭐⭐(基础) 标签#纯函数 #函数式编程 #核心概念

问题描述: 什么是纯函数?

参考答案要点

  • 相同的输入总是返回相同的输出
  • 不产生副作用(不修改外部状态)
  • 不依赖外部状态
  • 易于测试和调试
  • 支持缓存和并行处理

2. 函数柯里化

难度:⭐⭐⭐(中级) 标签#柯里化 #函数式编程 #手写题

问题描述: 什么是函数柯里化?如何实现?

参考答案要点

  • 将多参数函数转换为一系列单参数函数
  • 每次调用返回一个新函数,接收剩余参数
  • 直到所有参数收集完毕才执行

代码示例

JAVASCRIPT
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))
      }
    }
  }
}

3. 函数组合(compose)

难度:⭐⭐⭐(中级) 标签#函数组合 #compose #函数式编程

问题描述: 什么是函数组合?如何实现?

参考答案要点

  • 将多个函数组合成一个函数
  • 从右到左执行:compose(f, g, h)(x) = f(g(h(x)))

代码示例

JAVASCRIPT
const compose =
  (...fns) =>
  (x) =>
    fns.reduceRight((v, f) => f(v), x)

// 管道(从左到右)
const pipe =
  (...fns) =>
  (x) =>
    fns.reduce((v, f) => f(v), x)

4. 不可变数据

难度:⭐⭐⭐(中级) 标签#不可变数据 #函数式编程 #Immutable

问题描述: 什么是不可变数据?如何实现?

参考答案要点

  • 数据创建后不能被修改
  • 修改时返回新数据
  • 实现方式:
    • Object.assign或扩展运算符
    • 深拷贝
    • Object.freeze()
    • Immutable.js等库
  • 好处:可预测、易追踪、支持时间旅行调试

5. 副作用的理解

难度:⭐⭐(基础) 标签#副作用 #纯函数 #函数式编程

问题描述: 什么是副作用?如何避免?

参考答案要点

  • 副作用:函数对外部状态产生的影响
  • 常见副作用:修改全局变量、DOM操作、I/O操作、API请求
  • 避免方法:
    • 使用纯函数
    • 将副作用集中到特定层
    • 使用函数式副作用管理(如IO Monad)

6. 高阶组件(HOC)模式

难度:⭐⭐⭐(中级) 标签#高阶组件 #HOC #React #设计模式

问题描述: 什么是高阶组件?与函数式编程有什么关系?

参考答案要点

  • 接收组件作为参数,返回新组件的函数
  • 是高阶函数在React中的应用
  • 用于代码复用、逻辑抽象
  • 注意:不要修改原组件,而是返回新组件

7. 偏函数(Partial Application)

难度:⭐⭐⭐(中级) 标签#偏函数 #柯里化 #函数式编程

问题描述: 偏函数与柯里化有什么区别?

参考答案要点

特性柯里化偏函数
参数传递逐次传递单个参数一次传递多个参数
返回接收剩余参数的函数接收剩余参数的函数
调用方式fn(a)(b)(c)fn(a, b)(c)fn.bind(null, a, b)

8. 尾递归优化

难度:⭐⭐⭐(高级) 标签#尾递归 #优化 #递归

问题描述: 什么是尾递归优化?

参考答案要点

  • 尾调用:函数的最后操作是调用另一个函数
  • 尾递归:尾调用自身
  • ES6规定尾调用优化,但实际支持有限
  • 可以将递归改写为循环避免栈溢出

9. 函子(Functor)概念

难度:⭐⭐⭐(高级) 标签#函子 #Functor #函数式编程

问题描述: 什么是函子?

参考答案要点

  • 实现了map方法的容器
  • map方法接收一个函数,将其应用到容器中的值
  • 遵守函子定律:
    • 同一律:functor.map(x => x) == functor
    • 结合律:functor.map(f).map(g) == functor.map(x => g(f(x)))
  • 常见函子:Maybe、Either、Promise

10. 函数式编程的优势

难度:⭐⭐(基础) 标签#函数式编程 #优势 #编程范式

问题描述: 函数式编程有什么优势?

参考答案要点

  • 代码更易测试(纯函数)
  • 可预测性强,易于推理
  • 支持并发和并行
  • 便于代码复用(高阶函数)
  • 减少bug(不可变数据)
  • 声明式编程,代码更简洁

十一、设计模式

1. 单例模式

难度:⭐⭐⭐(中级) 标签#单例模式 #创建型模式 #手写题

问题描述: 什么是单例模式?如何实现?

参考答案要点

  • 确保一个类只有一个实例,并提供全局访问点
  • 应用场景:全局配置、缓存、日志对象

代码示例

JAVASCRIPT
// ES6 Class实现
class Singleton {
  constructor() {
    if (!Singleton.instance) {
      Singleton.instance = this
    }
    return Singleton.instance
  }
}

// 闭包实现
const Singleton = (function () {
  let instance
  return function () {
    if (!instance) {
      instance = this
    }
    return instance
  }
})()

2. 发布订阅模式

难度:⭐⭐⭐(中级) 标签#发布订阅 #观察者模式 #事件系统

问题描述: 什么是发布订阅模式?与观察者模式有什么区别?

参考答案要点

特性观察者模式发布订阅模式
耦合度松耦合完全解耦
中间层有事件中心/经纪人
角色观察者+被观察者发布者+订阅者+事件中心
使用场景单个应用内部跨应用/消息中间件

代码示例

JAVASCRIPT
class EventEmitter {
  constructor() {
    this.events = {}
  }
  on(event, callback) {
    ;(this.events[event] || (this.events[event] = [])).push(callback)
  }
  emit(event, data) {
    ;(this.events[event] || []).forEach((cb) => cb(data))
  }
  off(event, callback) {
    this.events[event] = (this.events[event] || []).filter(
      (cb) => cb !== callback
    )
  }
}

3. 工厂模式

难度:⭐⭐⭐(中级) 标签#工厂模式 #创建型模式 #设计模式

问题描述: 什么是工厂模式?有什么应用场景?

参考答案要点

  • 定义创建对象的接口,让子类决定实例化哪个类
  • 将对象创建逻辑封装起来
  • 应用场景:
    • 创建复杂对象
    • 根据不同条件创建不同对象
    • 解耦对象创建和使用

代码示例

JAVASCRIPT
class Factory {
  create(type) {
    switch (type) {
      case "A":
        return new ProductA()
      case "B":
        return new ProductB()
      default:
        throw new Error("Unknown type")
    }
  }
}

4. 策略模式

难度:⭐⭐⭐(中级) 标签#策略模式 #行为型模式 #设计模式

问题描述: 什么是策略模式?

参考答案要点

  • 定义一系列算法,将它们封装起来,并且使它们可以互相替换
  • 消除大量if-else/switch语句
  • 应用场景:表单验证、支付方式选择、排序算法选择

代码示例

JAVASCRIPT
const strategies = {
  isNonEmpty: (value) => value !== "",
  isNumber: (value) => !isNaN(parseFloat(value)),
  isEmail: (value) => /^\w+@\w+\.\w+$/.test(value),
}

5. 装饰器模式

难度:⭐⭐⭐(中级) 标签#装饰器模式 #结构型模式 #ES7

问题描述: 什么是装饰器模式?

参考答案要点

  • 动态地给对象添加额外的职责
  • 比继承更灵活
  • ES7 Decorator语法支持

代码示例

JAVASCRIPT
function readonly(target, key, descriptor) {
  descriptor.writable = false
  return descriptor
}

class Person {
  @readonly
  name = "John"
}

6. 代理模式

难度:⭐⭐⭐(中级) 标签#代理模式 #Proxy #结构型模式

问题描述: 什么是代理模式?与装饰器模式有什么区别?

参考答案要点

  • 为对象提供一个代理,控制对原对象的访问
  • 应用场景:懒加载、访问控制、缓存、日志记录
  • 与装饰器区别:代理控制访问,装饰器增强功能
  • ES6 Proxy实现

7. 观察者模式

难度:⭐⭐⭐(中级) 标签#观察者模式 #行为型模式 #设计模式

问题描述: 什么是观察者模式?

参考答案要点

  • 定义对象间的一对多依赖关系
  • 当一个对象状态改变时,所有依赖者都会收到通知
  • 被观察者维护观察者列表
  • 应用场景:事件系统、数据绑定、消息推送

8. 模块模式

难度:⭐⭐(基础) 标签#模块模式 #闭包 #IIFE

问题描述: 什么是模块模式?

参考答案要点

  • 使用闭包封装私有变量和方法
  • 暴露公共API
  • IIFE实现

代码示例

JAVASCRIPT
const Module = (function () {
  let privateVar = 0

  function privateMethod() {
    console.log(privateVar)
  }

  return {
    publicMethod: function () {
      privateVar++
      privateMethod()
    },
  }
})()

9. 适配器模式

难度:⭐⭐⭐(中级) 标签#适配器模式 #结构型模式 #设计模式

问题描述: 什么是适配器模式?

参考答案要点

  • 将一个类的接口转换成客户希望的另一个接口
  • 使不兼容的接口能够一起工作
  • 应用场景:API适配、数据格式转换

10. 命令模式

难度:⭐⭐⭐(中级) 标签#命令模式 #行为型模式 #设计模式

问题描述: 什么是命令模式?

参考答案要点

  • 将请求封装为对象
  • 可以参数化客户端、队列请求、记录日志、支持撤销
  • 应用场景:撤销/重做功能、任务队列、宏命令

11. 设计模式分类

难度:⭐⭐(基础) 标签#设计模式 #分类 #架构

问题描述: 常见的设计模式有哪些分类?

参考答案要点

  • 创建型(5种):单例、工厂、抽象工厂、建造者、原型
  • 结构型(7种):适配器、装饰器、代理、外观、桥接、组合、享元
  • 行为型(11种):策略、模板方法、观察者、迭代器、责任链、命令、备忘录、状态、访问者、中介者、解释器

12. MVC/MVVM模式

难度:⭐⭐⭐(中级) 标签#MVC #MVVM #架构模式

问题描述: MVC和MVVM有什么区别?

参考答案要点

特性MVCMVVM
通信View和Model直接通信View和ViewModel双向绑定
更新手动更新自动同步
控制器Controller处理逻辑ViewModel处理逻辑
代表框架BackboneVue、Angular

十二、性能优化

1. 防抖(Debounce)

难度:⭐⭐⭐(中级) 标签#防抖 #性能优化 #手写题

问题描述: 什么是防抖?如何实现?

参考答案要点

  • 在事件触发后延迟执行,如果期间再次触发则重新计时
  • 应用场景:搜索框输入、窗口resize、表单验证

代码示例

JAVASCRIPT
function debounce(fn, delay) {
  let timer = null
  return function (...args) {
    clearTimeout(timer)
    timer = setTimeout(() => fn.apply(this, args), delay)
  }
}

// 立即执行版本
function debounce(fn, delay, immediate) {
  let timer = null
  return function (...args) {
    if (timer) clearTimeout(timer)
    if (immediate && !timer) {
      fn.apply(this, args)
    }
    timer = setTimeout(() => {
      if (!immediate) fn.apply(this, args)
      timer = null
    }, delay)
  }
}

2. 节流(Throttle)

难度:⭐⭐⭐(中级) 标签#节流 #性能优化 #手写题

问题描述: 什么是节流?如何实现?

参考答案要点

  • 在规定时间内只执行一次
  • 应用场景:滚动加载、按钮点击、mousemove事件

代码示例

JAVASCRIPT
// 时间戳版本
function throttle(fn, delay) {
  let last = 0
  return function (...args) {
    const now = Date.now()
    if (now - last >= delay) {
      fn.apply(this, args)
      last = now
    }
  }
}

// 定时器版本
function throttle(fn, delay) {
  let timer = null
  return function (...args) {
    if (!timer) {
      timer = setTimeout(() => {
        fn.apply(this, args)
        timer = null
      }, delay)
    }
  }
}

3. 防抖与节流的区别

难度:⭐⭐⭐(中级) 标签#防抖 #节流 #对比

问题描述: 防抖和节流有什么区别?

参考答案要点

特性防抖(Debounce)节流(Throttle)
执行时机停止触发后执行固定间隔执行
类比电梯关门水龙头滴水
应用场景搜索、验证滚动、resize
连续触发只执行最后一次按间隔执行

4. 图片懒加载

难度:⭐⭐⭐(中级) 标签#懒加载 #图片优化 #IntersectionObserver

问题描述: 如何实现图片懒加载?

代码示例

JAVASCRIPT
// IntersectionObserver实现
const lazyImages = document.querySelectorAll("img[data-src]")

const observer = new IntersectionObserver((entries) => {
  entries.forEach((entry) => {
    if (entry.isIntersecting) {
      const img = entry.target
      img.src = img.dataset.src
      observer.unobserve(img)
    }
  })
})

lazyImages.forEach((img) => observer.observe(img))

// scroll实现(兼容性更好)
function lazyLoad() {
  const images = document.querySelectorAll("img[data-src]")
  images.forEach((img) => {
    if (img.getBoundingClientRect().top < window.innerHeight) {
      img.src = img.dataset.src
      img.removeAttribute("data-src")
    }
  })
}
window.addEventListener("scroll", throttle(lazyLoad, 200))

5. 虚拟列表

难度:⭐⭐⭐(高级) 标签#虚拟列表 #大数据 #性能优化

问题描述: 什么是虚拟列表?如何实现?

参考答案要点

  • 只渲染可视区域的列表项
  • 通过计算滚动位置动态更新渲染内容
  • 需要设置容器高度和滚动条
  • 可以配合requestAnimationFrame优化

6. 重绘(Repaint)与回流(Reflow)

难度:⭐⭐⭐(中级) 标签#重绘 #回流 #渲染优化

问题描述: 什么是重绘和回流?如何减少?

参考答案要点

  • 回流(Reflow):几何属性变化,重新计算布局
  • 重绘(Repaint):外观变化,不触发布局
  • 减少方法
    • 批量修改样式(使用class或cssText)
    • 离线操作(cloneNode、display:none)
    • 避免频繁读取布局属性(offsetWidth等会强制回流)
    • 使用transform和opacity(触发GPU加速)
    • 使用requestAnimationFrame

7. 浏览器缓存策略

难度:⭐⭐⭐(中级) 标签#缓存 #性能优化 #HTTP

问题描述: 浏览器缓存有哪些类型?

参考答案要点

  • 强缓存
    • Expires(HTTP/1.0)
    • Cache-Control(HTTP/1.1):max-age、no-cache、no-store
  • 协商缓存
    • Last-Modified / If-Modified-Since
    • ETag / If-None-Match
  • 优先级:强缓存 > 协商缓存

8. 代码分割(Code Splitting)

难度:⭐⭐⭐(中级) 标签#代码分割 #懒加载 #Webpack

问题描述: 什么是代码分割?如何实现?

参考答案要点

  • 将代码分割成多个小块,按需加载
  • 实现方式:
    • Webpack动态导入:import()
    • React.lazy + Suspense
    • Vue异步组件
  • 好处:减少首屏加载时间

9. Tree Shaking

难度:⭐⭐⭐(中级) 标签#TreeShaking #Webpack #优化

问题描述: 什么是Tree Shaking?原理是什么?

参考答案要点

  • 消除未使用的代码
  • 基于ES6模块的静态结构
  • 需要:
    • 使用ES6模块(import/export)
    • 配置webpack的optimization.usedExports
    • 使用支持Tree Shaking的库

10. 性能监控指标

难度:⭐⭐⭐(中级) 标签#性能指标 #监控 #CoreWebVitals

问题描述: 前端性能监控有哪些指标?

参考答案要点

  • FP(First Paint):首次绘制
  • FCP(First Contentful Paint):首次内容绘制
  • LCP(Largest Contentful Paint):最大内容绘制
  • FID(First Input Delay):首次输入延迟
  • CLS(Cumulative Layout Shift):累积布局偏移
  • TTFB(Time To First Byte):首字节时间

11. Web Workers的使用

难度:⭐⭐⭐(中级) 标签#WebWorkers #多线程 #性能优化

问题描述: Web Workers有什么作用?

参考答案要点

  • 在后台线程运行JavaScript
  • 不阻塞主线程
  • 适用于CPU密集型任务
  • 与主线程通过postMessage通信
  • 无法直接操作DOM

12. 内存泄漏的常见场景

难度:⭐⭐⭐(中级) 标签#内存泄漏 #性能优化 #调试

问题描述: JavaScript中常见的内存泄漏场景有哪些?

参考答案要点

  1. 全局变量:意外创建的全局变量
  2. 闭包:未释放的闭包引用
  3. 定时器:未清除的setInterval/setTimeout
  4. DOM引用:已移除DOM但JS仍有引用
  5. 事件监听:未移除的事件监听器
  6. Map/Set:未清理的键值对
  7. 循环引用:现代浏览器已处理,但需注意

排查方法

  • Chrome DevTools Memory面板
  • Heap Snapshot堆快照对比
  • Performance面板监控内存趋势

十三、TypeScript

1. TypeScript与JavaScript的区别

难度:⭐⭐(基础) 标签#TypeScript #JavaScript #类型系统

问题描述: TypeScript与JavaScript有什么区别?

参考答案要点

  • TypeScript是JavaScript的超集
  • 添加了静态类型系统
  • 支持ES6+新特性
  • 需要编译为JavaScript才能运行
  • 更好的IDE支持和代码提示
  • 更容易进行重构

2. 接口(Interface)与类型别名(Type)的区别

难度:⭐⭐⭐(中级) 标签#TypeScript #Interface #Type

问题描述: interface和type有什么区别?

参考答案要点

特性InterfaceType
扩展extends&交叉类型
合并自动合并(声明合并)不能合并
实现可以被类实现不能
联合类型不支持支持
映射类型不支持支持

3. 泛型的理解和使用

难度:⭐⭐⭐(中级) 标签#TypeScript #泛型 #Generic

问题描述: 什么是泛型?有什么作用?

参考答案要点

  • 在定义时不指定具体类型,使用时再指定
  • 提高代码复用性和类型安全

代码示例

TYPESCRIPT
function identity<T>(arg: T): T {
  return arg;
}

// 泛型约束
function loggingIdentity<T extends { length: number }>(arg: T): T {
  console.log(arg.length);
  return arg;
}

4. 类型推断与类型断言

难度:⭐⭐(基础) 标签#TypeScript #类型推断 #类型断言

问题描述: 类型推断和类型断言有什么区别?

参考答案要点

  • 类型推断:TypeScript自动推断类型
  • 类型断言:开发者告诉编译器变量的类型
  • 类型断言语法:<Type>valuevalue as Type
  • 类型断言只在编译时有效,不影响运行时

5. 类型守卫(Type Guards)

难度:⭐⭐⭐(中级) 标签#TypeScript #类型守卫 #类型收窄

问题描述: 什么是类型守卫?有哪些实现方式?

参考答案要点

  • 在运行时检查类型,缩小类型范围
  • 实现方式:
    • typeof:判断基本类型
    • instanceof:判断类实例
    • in:判断属性是否存在
    • 自定义类型守卫函数:value is Type

代码示例

TYPESCRIPT
function isString(value: unknown): value is string {
  return typeof value === 'string';
}

6. 映射类型(Mapped Types)

难度:⭐⭐⭐(高级) 标签#TypeScript #映射类型 #高级类型

问题描述: 什么是映射类型?

参考答案要点

  • 基于旧类型创建新类型
  • 可以批量修改属性特性

代码示例

TYPESCRIPT
type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};

type Partial<T> = {
  [P in keyof T]?: T[P];
};

type Pick<T, K extends keyof T> = {
  [P in K]: T[P];
};

7. 条件类型(Conditional Types)

难度:⭐⭐⭐(高级) 标签#TypeScript #条件类型 #高级类型

问题描述: 什么是条件类型?

参考答案要点

  • 根据条件选择类型
  • 语法:T extends U ? X : Y
  • 可以嵌套使用

代码示例

TYPESCRIPT
type Exclude<T, U> = T extends U ? never : T;
type Extract<T, U> = T extends U ? T : never;
type NonNullable<T> = T extends null | undefined ? never : T;

8. 工具类型(Utility Types)

难度:⭐⭐⭐(中级) 标签#TypeScript #工具类型 #内置类型

问题描述: TypeScript有哪些常用的工具类型?

参考答案要点

工具类型作用
Partial所有属性可选
Required所有属性必选
Readonly所有属性只读
Pick<T, K>选取部分属性
Omit<T, K>省略部分属性
Record<K, T>创建键值对类型
Exclude<T, U>从T中排除U
Extract<T, U>从T中提取U
ReturnType获取函数返回类型
Parameters获取函数参数类型

9. 装饰器(Decorators)

难度:⭐⭐⭐(高级) 标签#TypeScript #装饰器 #ES7

问题描述: TypeScript装饰器有哪些类型?

参考答案要点

  • 类装饰器:修饰类
  • 方法装饰器:修饰方法
  • 属性装饰器:修饰属性
  • 参数装饰器:修饰参数
  • 需要开启experimentalDecorators配置

10. 命名空间(Namespace)与模块

难度:⭐⭐⭐(中级) 标签#TypeScript #Namespace #Module

问题描述: namespace和module有什么区别?

参考答案要点

  • namespace:内部模块,用于组织代码
  • module:外部模块,文件即模块
  • 现代TypeScript推荐使用ES模块
  • namespace主要用于类型声明文件

11. 类型声明文件(.d.ts)

难度:⭐⭐⭐(中级) 标签#TypeScript #声明文件 #类型定义

问题描述: 什么是.d.ts文件?有什么作用?

参考答案要点

  • 类型声明文件,只包含类型信息
  • 为JavaScript库提供类型支持
  • 使用declare关键字声明类型
  • 发布到@types组织或随库一起发布

12. any、unknown、never的区别

难度:⭐⭐⭐(中级) 标签#TypeScript #any #unknown #never

问题描述: any、unknown、never有什么区别?

参考答案要点

类型特点使用场景
any任意类型,无类型检查尽量避免使用
unknown未知类型,需要类型检查后才能使用比any更安全
never永不存在的类型表示不可能的值,如抛出错误的函数

13. 泛型约束与默认值

难度:⭐⭐⭐(中级) 标签#TypeScript #泛型 #约束 #默认值

问题描述: 如何对泛型进行约束和设置默认值?

代码示例

TYPESCRIPT
// 泛型约束
function getLength<T extends { length: number }>(arg: T): number {
  return arg.length;
}

// 泛型默认值
function createArray<T = string>(length: number, value: T): T[] {
  return Array(length).fill(value);
}

// 多泛型约束
interface Lengthwise {
  length: number;
}
function getLength<T extends Lengthwise, U extends keyof T>(obj: T, key: U) {
  return obj[key];
}

14. 逆变与协变

难度:⭐⭐⭐(高级) 标签#TypeScript #逆变 #协变 #类型系统

问题描述: 什么是逆变与协变?

参考答案要点

  • 协变(Covariant):子类型可以赋值给父类型(返回值位置)
  • 逆变(Contravariant):父类型可以赋值给子类型(参数位置)
  • 双变(Bivariant):两者都可以
  • 不变(Invariant):两者都不可以
  • TypeScript中,函数参数是双向协变的(配置决定)

15. 类型体操实践

难度:⭐⭐⭐(高级) 标签#TypeScript #类型体操 #高级类型

问题描述: 请实现DeepReadonly类型?

代码示例

TYPESCRIPT
type DeepReadonly<T> = {
  readonly [P in keyof T]: T[P] extends object
    ? T[P] extends Function
      ? T[P]
      : DeepReadonly<T[P]>
    : T[P];
};

// 使用
type X = {
  x: {
    a: 1;
    b: 'hi';
  };
  y: 'hey';
};

type Expected = DeepReadonly<X>;

复习建议

1. 分阶段复习

阶段时间内容
第一阶段1-2周基础题(⭐)- 必须全部掌握
第二阶段2-3周中级题(⭐⭐)- 理解原理
第三阶段1-2周高级题(⭐⭐⭐)- 手写实现

2. 复习方法

  1. 理解原理:不要死记硬背,理解背后的原理
  2. 手写练习:重点题目一定要手写实现
  3. 代码实践:在浏览器控制台或Node.js中验证
  4. 对比学习:相似的题目对比记忆
  5. 定期回顾:使用艾宾浩斯遗忘曲线复习

3. 面试技巧

  • 先回答核心概念,再展开细节
  • 结合代码示例说明
  • 提及实际应用场景
  • 展示对原理的深入理解
  • 不懂的问题诚实回答,不要猜测

重点题目推荐

手写题(必须掌握)

题目难度重要性
new操作符实现⭐⭐⭐⭐⭐⭐⭐⭐
寄生组合式继承⭐⭐⭐⭐⭐⭐⭐⭐
Promise.all实现⭐⭐⭐⭐⭐⭐⭐⭐
手写Promise⭐⭐⭐⭐⭐⭐⭐⭐
call/apply/bind实现⭐⭐⭐⭐⭐⭐⭐⭐
深拷贝(完整版)⭐⭐⭐⭐⭐⭐⭐⭐
防抖/节流实现⭐⭐⭐⭐⭐⭐⭐⭐
数组map/filter/reduce实现⭐⭐⭐⭐⭐⭐⭐
函数柯里化⭐⭐⭐⭐⭐⭐⭐
发布订阅模式实现⭐⭐⭐⭐⭐⭐⭐

核心概念题(必须掌握)

题目难度重要性
原型链和原型⭐⭐⭐⭐⭐⭐⭐⭐
闭包的理解⭐⭐⭐⭐⭐⭐⭐⭐
事件循环机制⭐⭐⭐⭐⭐⭐⭐⭐
this绑定规则⭐⭐⭐⭐⭐⭐⭐⭐
宏任务与微任务⭐⭐⭐⭐⭐⭐⭐⭐

附录:难度分布统计

分类基础题中级题高级题总计
ES6+新特性75315
原型链和继承38415
闭包和作用域28515
事件循环06612
异步编程15612
this指向和bind/call/apply25512
深拷贝和浅拷贝24612
类型判断和类型转换45312
数组和对象方法57315
函数式编程25310
设计模式19212
性能优化08412
TypeScript28515
总计318355169

文档说明:本文档整理了169道JavaScript面试题,涵盖13个核心知识点。建议按照难度循序渐进地学习,重点掌握手写题和核心概念题。祝你面试顺利!

最后更新于: 2026-02-27

目录