JavaScript 面试题
前端面试题 - JavaScript篇
JavaScript是前端开发的核心语言,在面试中占据重要地位。本文档整理了169道JavaScript面试题,涵盖13个核心知识点,帮助你系统复习和准备面试。
文档概览
| 项目 | 内容 |
|---|---|
| 题目总数 | 169道 |
| 覆盖分类 | 13个核心知识点 |
| 难度分布 | 基础(31道) / 中级(83道) / 高级(55道) |
| 更新时间 | 2025年 |
难度标识说明
- ⭐ 基础题:必须掌握的核心概念
- ⭐⭐ 中级题:深入理解和实际应用
- ⭐⭐⭐ 高级题:原理分析和手写实现
目录
- 一、ES6+新特性
- 二、原型链和继承
- 三、闭包和作用域
- 四、事件循环
- 五、异步编程
- 六、this指向和bind/call/apply
- 七、深拷贝和浅拷贝
- 八、类型判断和类型转换
- 九、数组和对象方法
- 十、函数式编程
- 十一、设计模式
- 十二、性能优化
- 十三、TypeScript
- 复习建议
- 重点题目推荐
- 学习资源推荐
一、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操作符的执行过程是什么?
参考答案要点:
- 创建一个空对象obj
- 将obj的proto指向构造函数的prototype
- 将构造函数的this指向obj
- 执行构造函数代码
- 如果构造函数返回对象则返回该对象,否则返回obj
代码示例:
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. 如何实现一个完美的寄生组合式继承?
难度:⭐⭐⭐(高级)
标签:#继承 #手写题 #寄生组合式继承
问题描述: 请手写寄生组合式继承的实现?
代码示例:
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. 执行上下文的生命周期
难度:⭐⭐⭐(高级)
标签:#执行上下文 #生命周期 #底层原理
问题描述: 执行上下文的生命周期是怎样的?
参考答案要点:
- 创建阶段:
- 创建变量对象(VO)/活动对象(AO)
- 建立作用域链
- 确定this指向
- 执行阶段:
- 变量赋值
- 函数引用
- 执行代码
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
问题描述: 以下代码输出什么?为什么?
代码示例:
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. 经典输出题分析
难度:⭐⭐⭐(高级)
标签:#事件循环 #经典题目 #输出顺序
问题描述: 以下代码输出顺序是什么?
代码示例:
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 #经典题目
问题描述: 以下代码输出顺序?
代码示例:
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?
代码示例:
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. 并发控制(限制请求数量)
难度:⭐⭐⭐(高级)
标签:#并发控制 #手写题 #异步
问题描述: 如何实现并发请求数量限制?
代码示例:
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实现?
代码示例:
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?
代码示例:
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?
代码示例:
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?
代码示例:
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 #隐式绑定 #经典问题
问题描述: 什么是隐式绑定的丢失?举例说明?
代码示例:
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实现函数柯里化?
代码示例:
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. 手写深拷贝(基础版)
难度:⭐⭐⭐(中级)
标签:#深拷贝 #手写题 #递归
问题描述: 请手写一个深拷贝函数?
代码示例:
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的深拷贝?
代码示例:
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等
代码示例:
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?
代码示例:
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. 手写类型判断函数
难度:⭐⭐⭐(中级)
标签:#类型判断 #手写题 #工具函数
问题描述: 请实现一个通用的类型判断函数?
代码示例:
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. 经典类型转换面试题
难度:⭐⭐⭐(中级)
标签:#类型转换 #经典题目 #面试陷阱
问题描述: 以下表达式结果是什么?
代码示例:
[] + []
[] + {}
{} + []
{} + {}
[] == ![]参考答案要点:
[] + []→ ""(空字符串)[] + {}→ "[object Object]"{} + []→ 0({}被解析为代码块){} + {}→ "[object Object][object Object]"(非严格模式)[] == ![]→ true([]转数字为0,![]为false也转0)
9. Array.isArray的实现
难度:⭐⭐⭐(中级)
标签:#Array.isArray #类型判断 #数组
问题描述: Array.isArray是如何实现的?
代码示例:
// 简易实现
function isArray(obj) {
return Object.prototype.toString.call(obj) === "[object Array]"
}
// 或者使用instanceof(有跨frame问题)
function isArray(obj) {
return obj instanceof Array
}
// 或者使用Array.isArray(最可靠)
Array.isArray([]) // true10. 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 + indexOf:
arr.filter((item, i) => arr.indexOf(item) === i) - reduce:
arr.reduce((acc, cur) => acc.includes(cur) ? acc : [...acc, cur], []) - 对象键值对:适用于基本类型
2. 数组扁平化
难度:⭐⭐⭐(中级)
标签:#数组扁平化 #递归 #手写题
问题描述: 如何实现数组扁平化?
代码示例:
// 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?
代码示例:
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?
代码示例:
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?
代码示例:
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有什么区别?
参考答案要点:
| 特性 | splice | slice |
|---|---|---|
| 作用 | 删除/添加元素 | 截取部分数组 |
| 改变原数组 | 是 | 否 |
| 返回值 | 被删除的元素数组 | 新数组 |
| 参数 | 起始位置、删除个数、新增元素 | 起始位置、结束位置 |
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. 函数柯里化
难度:⭐⭐⭐(中级)
标签:#柯里化 #函数式编程 #手写题
问题描述: 什么是函数柯里化?如何实现?
参考答案要点:
- 将多参数函数转换为一系列单参数函数
- 每次调用返回一个新函数,接收剩余参数
- 直到所有参数收集完毕才执行
代码示例:
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)))
代码示例:
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. 单例模式
难度:⭐⭐⭐(中级)
标签:#单例模式 #创建型模式 #手写题
问题描述: 什么是单例模式?如何实现?
参考答案要点:
- 确保一个类只有一个实例,并提供全局访问点
- 应用场景:全局配置、缓存、日志对象
代码示例:
// 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. 发布订阅模式
难度:⭐⭐⭐(中级)
标签:#发布订阅 #观察者模式 #事件系统
问题描述: 什么是发布订阅模式?与观察者模式有什么区别?
参考答案要点:
| 特性 | 观察者模式 | 发布订阅模式 |
|---|---|---|
| 耦合度 | 松耦合 | 完全解耦 |
| 中间层 | 无 | 有事件中心/经纪人 |
| 角色 | 观察者+被观察者 | 发布者+订阅者+事件中心 |
| 使用场景 | 单个应用内部 | 跨应用/消息中间件 |
代码示例:
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. 工厂模式
难度:⭐⭐⭐(中级)
标签:#工厂模式 #创建型模式 #设计模式
问题描述: 什么是工厂模式?有什么应用场景?
参考答案要点:
- 定义创建对象的接口,让子类决定实例化哪个类
- 将对象创建逻辑封装起来
- 应用场景:
- 创建复杂对象
- 根据不同条件创建不同对象
- 解耦对象创建和使用
代码示例:
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语句
- 应用场景:表单验证、支付方式选择、排序算法选择
代码示例:
const strategies = {
isNonEmpty: (value) => value !== "",
isNumber: (value) => !isNaN(parseFloat(value)),
isEmail: (value) => /^\w+@\w+\.\w+$/.test(value),
}5. 装饰器模式
难度:⭐⭐⭐(中级)
标签:#装饰器模式 #结构型模式 #ES7
问题描述: 什么是装饰器模式?
参考答案要点:
- 动态地给对象添加额外的职责
- 比继承更灵活
- ES7 Decorator语法支持
代码示例:
function readonly(target, key, descriptor) {
descriptor.writable = false
return descriptor
}
class Person {
@readonly
name = "John"
}6. 代理模式
难度:⭐⭐⭐(中级)
标签:#代理模式 #Proxy #结构型模式
问题描述: 什么是代理模式?与装饰器模式有什么区别?
参考答案要点:
- 为对象提供一个代理,控制对原对象的访问
- 应用场景:懒加载、访问控制、缓存、日志记录
- 与装饰器区别:代理控制访问,装饰器增强功能
- ES6 Proxy实现
7. 观察者模式
难度:⭐⭐⭐(中级)
标签:#观察者模式 #行为型模式 #设计模式
问题描述: 什么是观察者模式?
参考答案要点:
- 定义对象间的一对多依赖关系
- 当一个对象状态改变时,所有依赖者都会收到通知
- 被观察者维护观察者列表
- 应用场景:事件系统、数据绑定、消息推送
8. 模块模式
难度:⭐⭐(基础)
标签:#模块模式 #闭包 #IIFE
问题描述: 什么是模块模式?
参考答案要点:
- 使用闭包封装私有变量和方法
- 暴露公共API
- IIFE实现
代码示例:
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有什么区别?
参考答案要点:
| 特性 | MVC | MVVM |
|---|---|---|
| 通信 | View和Model直接通信 | View和ViewModel双向绑定 |
| 更新 | 手动更新 | 自动同步 |
| 控制器 | Controller处理逻辑 | ViewModel处理逻辑 |
| 代表框架 | Backbone | Vue、Angular |
十二、性能优化
1. 防抖(Debounce)
难度:⭐⭐⭐(中级)
标签:#防抖 #性能优化 #手写题
问题描述: 什么是防抖?如何实现?
参考答案要点:
- 在事件触发后延迟执行,如果期间再次触发则重新计时
- 应用场景:搜索框输入、窗口resize、表单验证
代码示例:
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事件
代码示例:
// 时间戳版本
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
问题描述: 如何实现图片懒加载?
代码示例:
// 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异步组件
- Webpack动态导入:
- 好处:减少首屏加载时间
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中常见的内存泄漏场景有哪些?
参考答案要点:
- 全局变量:意外创建的全局变量
- 闭包:未释放的闭包引用
- 定时器:未清除的setInterval/setTimeout
- DOM引用:已移除DOM但JS仍有引用
- 事件监听:未移除的事件监听器
- Map/Set:未清理的键值对
- 循环引用:现代浏览器已处理,但需注意
排查方法:
- 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有什么区别?
参考答案要点:
| 特性 | Interface | Type |
|---|---|---|
| 扩展 | extends | &交叉类型 |
| 合并 | 自动合并(声明合并) | 不能合并 |
| 实现 | 可以被类实现 | 不能 |
| 联合类型 | 不支持 | 支持 |
| 映射类型 | 不支持 | 支持 |
3. 泛型的理解和使用
难度:⭐⭐⭐(中级)
标签:#TypeScript #泛型 #Generic
问题描述: 什么是泛型?有什么作用?
参考答案要点:
- 在定义时不指定具体类型,使用时再指定
- 提高代码复用性和类型安全
代码示例:
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>value或value as Type - 类型断言只在编译时有效,不影响运行时
5. 类型守卫(Type Guards)
难度:⭐⭐⭐(中级)
标签:#TypeScript #类型守卫 #类型收窄
问题描述: 什么是类型守卫?有哪些实现方式?
参考答案要点:
- 在运行时检查类型,缩小类型范围
- 实现方式:
typeof:判断基本类型instanceof:判断类实例in:判断属性是否存在- 自定义类型守卫函数:
value is Type
代码示例:
function isString(value: unknown): value is string {
return typeof value === 'string';
}6. 映射类型(Mapped Types)
难度:⭐⭐⭐(高级)
标签:#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 - 可以嵌套使用
代码示例:
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 #泛型 #约束 #默认值
问题描述: 如何对泛型进行约束和设置默认值?
代码示例:
// 泛型约束
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类型?
代码示例:
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. 复习方法
- 理解原理:不要死记硬背,理解背后的原理
- 手写练习:重点题目一定要手写实现
- 代码实践:在浏览器控制台或Node.js中验证
- 对比学习:相似的题目对比记忆
- 定期回顾:使用艾宾浩斯遗忘曲线复习
3. 面试技巧
- 先回答核心概念,再展开细节
- 结合代码示例说明
- 提及实际应用场景
- 展示对原理的深入理解
- 不懂的问题诚实回答,不要猜测
重点题目推荐
手写题(必须掌握)
| 题目 | 难度 | 重要性 |
|---|---|---|
| new操作符实现 | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 寄生组合式继承 | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| Promise.all实现 | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 手写Promise | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| call/apply/bind实现 | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 深拷贝(完整版) | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 防抖/节流实现 | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 数组map/filter/reduce实现 | ⭐⭐⭐ | ⭐⭐⭐⭐ |
| 函数柯里化 | ⭐⭐⭐ | ⭐⭐⭐⭐ |
| 发布订阅模式实现 | ⭐⭐⭐ | ⭐⭐⭐⭐ |
核心概念题(必须掌握)
| 题目 | 难度 | 重要性 |
|---|---|---|
| 原型链和原型 | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 闭包的理解 | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 事件循环机制 | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| this绑定规则 | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 宏任务与微任务 | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
附录:难度分布统计
| 分类 | 基础题 | 中级题 | 高级题 | 总计 |
|---|---|---|---|---|
| ES6+新特性 | 7 | 5 | 3 | 15 |
| 原型链和继承 | 3 | 8 | 4 | 15 |
| 闭包和作用域 | 2 | 8 | 5 | 15 |
| 事件循环 | 0 | 6 | 6 | 12 |
| 异步编程 | 1 | 5 | 6 | 12 |
| this指向和bind/call/apply | 2 | 5 | 5 | 12 |
| 深拷贝和浅拷贝 | 2 | 4 | 6 | 12 |
| 类型判断和类型转换 | 4 | 5 | 3 | 12 |
| 数组和对象方法 | 5 | 7 | 3 | 15 |
| 函数式编程 | 2 | 5 | 3 | 10 |
| 设计模式 | 1 | 9 | 2 | 12 |
| 性能优化 | 0 | 8 | 4 | 12 |
| TypeScript | 2 | 8 | 5 | 15 |
| 总计 | 31 | 83 | 55 | 169 |
文档说明:本文档整理了169道JavaScript面试题,涵盖13个核心知识点。建议按照难度循序渐进地学习,重点掌握手写题和核心概念题。祝你面试顺利!