1. 解释var
、let
和const
的区别
答案:
var
存在变量提升,函数作用域let
块级作用域,无变量提升,存在暂时性死区const
与let
类似但声明时必须赋值且不可重新赋值(对象属性可修改) 最佳实践:默认使用const
,需要重新赋值时使用let
,避免使用var
2. Promise、async/await如何处理异步操作?
答案:
Promise
提供链式调用处理异步,有三种状态:pending、fulfilled、rejectedasync/await
是Promise的语法糖,使异步代码看起来像同步代码 最佳实践:优先使用async/await处理复杂异步逻辑,提高可读性
3. 什么是闭包?实际应用场景有哪些?
答案:闭包是函数能够访问其词法作用域外的变量。应用场景:
- 数据隐藏和封装
- 实现私有变量
- 函数工厂
- 防抖节流函数实现
4. 解释事件循环(Event Loop)机制
答案:
- 宏任务(setTimeout,setInterval,I/O)
- 微任务(Promise.then,queueMicrotask,MutationObserver)
- 先执行同步代码,再清空微任务队列,然后执行一个宏任务,重复此过程
5. 如何进行深拷贝?
答案:
// 方法1:使用JSON (有局限性,不能处理函数、循环引用等)
const deepCopy = JSON.parse(JSON.stringify(obj))
// 方法2:递归实现
function deepClone(obj, hash = new WeakMap()) {
if (obj === null || typeof obj !== 'object') return obj
if (hash.has(obj)) return hash.get(obj)
let clone = Array.isArray(obj) ? [] : {}
hash.set(obj, clone)
Object.keys(obj).forEach(key => {
clone[key] = deepClone(obj[key], hash)
})
return clone
}
// 方法3:使用结构化克隆算法
const deepCopy = structuredClone(obj) // 现代浏览器支持
6. this关键字如何工作?
答案:this
指向取决于函数调用方式:
- 普通函数调用:指向全局对象(非严格模式)或undefined(严格模式)
- 对象方法调用:指向调用该方法的对象
- 构造函数调用:指向新创建的实例
- 箭头函数:继承外层作用域的this值,不能被修改
- call/apply/bind:显式指定this
7. ES6+中哪些新特性最常用?
答案:
- 解构赋值
- 箭头函数
- 模板字符串
- Promise和async/await
- 扩展运算符
- 可选链操作符
?.
和空值合并运算符??
- BigInt、动态import()
8. 如何优化前端性能?
答案:
- 减少HTTP请求(合并资源、雪碧图、按需加载)
- 代码分割与懒加载
- 资源压缩(Gzip、图片优化)
- 利用浏览器缓存
- 避免DOM频繁操作,使用DocumentFragment
- 使用Web Workers处理密集计算
- CDN加速静态资源
9. 如何处理跨域问题?
答案:
- CORS(跨域资源共享)
- JSONP(只支持GET请求)
- 代理服务器
- postMessage跨窗口通信
- WebSocket协议
10. JavaScript垃圾回收机制如何工作?
答案:
- 标记清除:标记所有可达对象,清除未标记对象
- 引用计数:计算对象引用次数,为0时回收(存在循环引用问题)
- 内存泄漏常见原因:未清除的定时器、闭包、全局变量过多
11. 浏览器存储方式有哪些?区别是什么?
答案:
- localStorage:持久存储,容量大(5MB),同源访问
- sessionStorage:会话期间存储,关闭页面丢失
- IndexedDB:大容量结构化存储,支持索引和事务
- Cookie:容量小(4KB),每次请求发送给服务器
12. Promise.all与Promise.allSettled的区别?
答案:
- Promise.all:所有Promise都成功才成功,一个失败则立即reject
- Promise.allSettled:等待所有Promise完成(无论成功失败),返回每个Promise结果状态
13. 如何实现函数节流(throttle)和防抖(debounce)?
答案:
// 防抖:延迟执行,频繁触发重新计时
function debounce(fn, delay) {
let timer = null
return function(...args) {
clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(this, args)
}, delay)
}
}
// 节流:一定时间内只执行一次
function throttle(fn, interval) {
let last = 0
return function(...args) {
const now = Date.now()
if (now - last >= interval) {
last = now
fn.apply(this, args)
}
}
}
14. 什么是原型链?如何实现继承?
答案:原型链是JavaScript实现继承的机制,对象通过__proto__
链接到其构造函数的prototype。
// 现代实现方式(ES6)
class Parent {
constructor(name) {
this.name = name
}
sayHello() {
console.log(`Hello, ${this.name}`)
}
}
class Child extends Parent {
constructor(name, age) {
super(name)
this.age = age
}
}
15. Map与Object的区别?Set与Array的区别?
答案:
- Map: 键可以是任意类型,有序,迭代顺序是插入顺序,专为频繁增删设计
- Object: 键只能是字符串或Symbol,无序
- Set: 存储唯一值,有序,自动去重
- Array: 允许重复值,通过索引访问
16. JavaScript模块化方案演进?
答案:
- 全局函数+命名空间
- CommonJS(Node.js)
- AMD(RequireJS)和CMD(SeaJS)
- ES Modules(官方标准) 最佳实践:现代项目使用ES Modules,支持静态分析和tree-shaking
17. 如何处理大规模数据渲染?
答案:
- 虚拟滚动(只渲染可视区域)
- 分页加载
- Web Workers处理数据
- requestAnimationFrame批量更新DOM
- 使用DocumentFragment减少重排重绘
18. React和Vue响应式原理区别?
答案:
- Vue: 基于Object.defineProperty(Vue2)/Proxy(Vue3)实现数据劫持,自动跟踪依赖
- React: 基于不可变数据和虚拟DOM比较,手动调用setState/useState触发更新
19. 前端安全问题及防范
答案:
- XSS攻击:输入过滤,CSP,HttpOnly cookie
- CSRF攻击:CSRF Token,SameSite cookie
- 点击劫持:X-Frame-Options
- 中间人攻击:HTTPS
20. TypeScript与JavaScript区别及优势?
答案:
- 强类型系统,编译时检查错误
- 接口、泛型等高级类型特性
- 更好的IDE支持和代码补全
- 提高代码可维护性和重构安全性
21. 如何实现服务端渲染(SSR)?优缺点?
答案:
- 实现:Next.js(React)、Nuxt.js(Vue)
- 优点:更好的SEO、首屏加载速度快
- 缺点:服务器压力大、开发复杂度高
22. JavaScript装饰器如何使用?
答案:
// 方法装饰器
function log(target, name, descriptor) {
const original = descriptor.value
descriptor.value = function(...args) {
console.log(`调用方法${name},参数:`, args)
return original.apply(this, args)
}
return descriptor
}
class Calculator {
@log
add(a, b) {
return a + b
}
}
23. Web Components如何使用?
答案:
// 定义自定义元素
class MyComponent extends HTMLElement {
constructor() {
super()
// 创建Shadow DOM
this.attachShadow({mode: 'open'})
this.shadowRoot.innerHTML = `
<style>h1 { color: red; }</style>
<h1>Hello, Web Components!</h1>
`
}
connectedCallback() {
// 元素添加到DOM时调用
}
}
// 注册自定义元素
customElements.define('my-component', MyComponent)
24. 常见设计模式在JavaScript中的应用?
答案:
- 单例模式:全局状态管理
- 观察者模式:事件系统
- 工厂模式:复杂对象创建
- 策略模式:表单验证
- 代理模式:数据绑定(Vue响应式系统)
25. Service Worker如何实现离线缓存?
答案:
// 注册Service Worker
navigator.serviceWorker.register('/sw.js');
// sw.js
self.addEventListener('install', e => {
e.waitUntil(
caches.open('my-cache').then(cache => {
return cache.addAll([
'/',
'/styles.css',
'/app.js',
'/offline.html'
])
})
)
})
self.addEventListener('fetch', e => {
e.respondWith(
caches.match(e.request).then(response => {
return response || fetch(e.request).catch(() => {
return caches.match('/offline.html')
})
})
)
})
26. 如何处理JavaScript异步错误?
答案:
// Promise错误处理
promise.catch(error => console.error(error))
// async/await错误处理
async function handleAsync() {
try {
const result = await asyncOperation()
} catch (error) {
console.error(error)
}
}
// 全局未捕获错误
window.addEventListener('unhandledrejection', event => {
console.error('Unhandled promise rejection:', event.reason)
})
window.addEventListener('error', event => {
console.error('Uncaught error:', event.error)
})
27. 如何优化React/Vue项目性能?
答案:
- 虚拟列表渲染大数据
- React.memo/PureComponent/shouldComponentUpdate避免无用渲染
- Vue使用v-once、keep-alive缓存组件
- 使用Web Workers处理耗时计算
- 代码分割和路由懒加载
- 使用key优化列表渲染
28. 函数式编程在JavaScript中的应用?
答案:
- 纯函数:相同输入产生相同输出,无副作用
- 不可变数据:使用扩展运算符或immer等库
- 高阶函数:map、filter、reduce
- 函数组合:compose、pipe函数串联多个操作
- 柯里化:将多参数函数转为单参数函数序列
29. 微前端架构如何实现?主要解决什么问题?
答案:
- 实现方式:single-spa、qiankun(基于single-spa)、Module Federation
- 解决问题:多团队协作、技术栈隔离、渐进式升级
- 关键技术:应用加载、隔离、通信、路由分发
30. WebAssembly的应用场景及优势?
答案:
- 应用场景:图像处理、游戏引擎、AI计算、视频编解码
- 优势:接近原生的执行速度、语言无关性(C/C++/Rust等编译)
- 与JavaScript协同工作,扩展Web性能边界
- 安全沙箱执行,不直接访问DOM
31. React Hooks与类组件的区别及优势?
答案:
- Hooks优势:状态逻辑复用、避免this混淆、按功能而非生命周期组织代码
- 常用Hooks:useState、useEffect、useContext、useReducer、useMemo、useCallback
- 自定义Hook可提取和复用状态逻辑,以"use"开头命名
32. JavaScript中的内存管理与优化
答案:
- 避免全局变量,及时清除事件监听器
- WeakMap/WeakSet处理临时对象引用
- 数组/对象过大时考虑分片处理
- Chrome DevTools的Memory面板分析内存泄漏
- 使用对象池模式减少垃圾回收频率
33. 实现一个EventBus(发布订阅模式)
答案:
class EventEmitter {
constructor() {
this.events = {}
}
on(eventName, callback) {
if (!this.events[eventName]) {
this.events[eventName] = []
}
this.events[eventName].push(callback)
return this
}
off(eventName, callback) {
if (!this.events[eventName]) return this
if (!callback) {
delete this.events[eventName]
} else {
this.events[eventName] = this.events[eventName].filter(cb => cb !== callback)
}
return this
}
emit(eventName, ...args) {
const callbacks = this.events[eventName] || []
callbacks.forEach(cb => cb.apply(this, args))
return this
}
once(eventName, callback) {
const wrapper = (...args) => {
callback.apply(this, args)
this.off(eventName, wrapper)
}
this.on(eventName, wrapper)
return this
}
}
34. 前端模块联邦(Module Federation)解决什么问题?
答案:
- 运行时共享代码和依赖,不同应用可共享模块
- 实现微前端架构,多个独立部署的应用组合
- 避免重复加载相同依赖,减小打包体积
- Webpack 5原生支持,适合大型前端应用分割
35. 如何使用Web Workers优化计算密集任务?
答案:
// 主线程
const worker = new Worker('worker.js')
worker.postMessage({data: complexData})
worker.onmessage = function(e) {
console.log('处理结果:', e.data.result)
}
// worker.js
self.onmessage = function(e) {
const result = complexCalculation(e.data.data)
self.postMessage({result})
}
36. JavaScript代码如何实现懒加载?
答案:
// 图片懒加载示例
const lazyImages = document.querySelectorAll('[data-src]')
const lazyLoad = target => {
const io = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target
img.src = img.dataset.src
img.removeAttribute('data-src')
observer.disconnect()
}
})
})
io.observe(target)
}
lazyImages.forEach(lazyLoad)
37. 如何处理JavaScript国际化?
答案:
- Intl内置对象:格式化日期、数字、货币等
- i18next等库管理翻译文本
- 动态加载语言包减小初始加载体积
- 考虑RTL布局和不同语言长度问题
38. 什么是JavaScript的尾调用优化?
答案:
- 尾调用:函数最后一步是函数调用,无需保留外层函数栈帧
- 严格模式下,尾递归不会导致栈溢出
- 常用于优化递归函数性能
// 非尾递归斐波那契(低效)
function fib(n) {
if (n <= 1) return n
return fib(n - 1) + fib(n - 2)
}
// 尾递归优化版本
function fib(n, a = 0, b = 1) {
if (n === 0) return a
return fib(n - 1, b, a + b)
}
39. CSS-in-JS方案优缺点及实现原理
答案:
- 代表库:styled-components、emotion
- 优点:动态样式、无命名冲突、CSS与组件绑定
- 缺点:运行时开销、SSR复杂性增加
- 原理:通过JS生成唯一类名并插入样式表
40. 如何设计大型前端应用的状态管理?
答案:
- 单一数据源原则减少状态同步问题
- 分层设计:UI状态、领域状态、应用状态
- 利用Context + useReducer实现轻量状态管理
- 复杂场景考虑Redux、MobX、Zustand等专用库
- 避免过度全局状态,合理拆分
41. JavaScript装饰器模式实际应用
答案:
// 类装饰器
function logger(loggerName) {
return function(target) {
return class extends target {
constructor(...args) {
super(...args)
console.log(`${loggerName}: 实例化 ${target.name}`)
}
}
}
}
// 属性装饰器
function readonly(target, key, descriptor) {
descriptor.writable = false
return descriptor
}
@logger('App')
class MyClass {
@readonly
name = 'default'
}
42. 实现一个简易的Promise
答案:
class MyPromise {
constructor(executor) {
this.status = 'pending'
this.value = undefined
this.reason = undefined
this.onFulfilledCallbacks = []
this.onRejectedCallbacks = []
const resolve = value => {
if (this.status === 'pending') {
this.status = 'fulfilled'
this.value = value
this.onFulfilledCallbacks.forEach(fn => fn())
}
}
const reject = reason => {
if (this.status === 'pending') {
this.status = 'rejected'
this.reason = reason
this.onRejectedCallbacks.forEach(fn => fn())
}
}
try {
executor(resolve, reject)
} catch (e) {
reject(e)
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }
const promise2 = new MyPromise((resolve, reject) => {
if (this.status === 'fulfilled') {
setTimeout(() => {
try {
const x = onFulfilled(this.value)
resolve(x)
} catch (e) {
reject(e)
}
})
}
if (this.status === 'rejected') {
setTimeout(() => {
try {
const x = onRejected(this.reason)
resolve(x)
} catch (e) {
reject(e)
}
})
}
if (this.status === 'pending') {
this.onFulfilledCallbacks.push(() => {
setTimeout(() => {
try {
const x = onFulfilled(this.value)
resolve(x)
} catch (e) {
reject(e)
}
})
})
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onRejected(this.reason)
resolve(x)
} catch (e) {
reject(e)
}
})
})
}
})
return promise2
}
catch(onRejected) {
return this.then(null, onRejected)
}
static resolve(value) {
return new MyPromise(resolve => resolve(value))
}
static reject(reason) {
return new MyPromise((_, reject) => reject(reason))
}
}
43. 如何处理复杂异步流程?
答案:
- Promise链:then/catch处理线性流程
- Promise.all/allSettled/race/any并行处理
- async/await处理同步风格的异步代码
- 使用Redux-saga/redux-thunk管理复杂状态变更
- RxJS处理基于流的复杂异步操作
44. 前端构建工具演进及最佳实践
答案:
- Webpack: 支持高度定制,适合复杂项目
- Vite: 基于ESM的快速开发服务器,生产使用Rollup
- Turbopack: 高性能构建系统(Next.js团队)
- esbuild: 极速Go编写的打包工具
- 最佳实践:开发使用Vite/Turbopack,生产静态资源分包和懒加载
45. 如何实现前端单元测试与集成测试?
答案:
- 单元测试:Jest、Vitest测试函数和组件
- 组件测试:React Testing Library、Vue Test Utils
- 端到端测试:Cypress、Playwright
- 快照测试:验证UI组件渲染一致性
- 测试金字塔:多单元测试,适量集成测试,少量E2E测试
46. JavaScript静态类型检查工具对比
答案:
- TypeScript:微软开发,完整类型系统,广泛支持
- Flow:Facebook开发,渐进式类型检查
- JSDoc:使用注释提供类型信息,无需编译
- 最佳实践:大型项目优先选择TypeScript,小型可考虑JSDoc
47. 如何实现前端权限控制?
答案:
// 基于角色的权限控制
const hasPermission = (userRoles, requiredPermissions) => {
return userRoles.some(role => requiredPermissions.includes(role))
}
// React权限组件示例
function AuthorizedComponent({ userRoles, requiredRoles, children, fallback = null }) {
if (hasPermission(userRoles, requiredRoles)) {
return children
}
return fallback
}
// 路由权限控制
const PrivateRoute = ({ component: Component, userRoles, requiredRoles, ...rest }) => (
<Route
{...rest}
render={props =>
hasPermission(userRoles, requiredRoles) ? (
<Component {...props} />
) : (
<Redirect to="/unauthorized" />
)
}
/>
)
48. 实现一个LRU缓存
答案:
class LRUCache {
constructor(capacity) {
this.capacity = capacity
this.cache = new Map()
}
get(key) {
if (!this.cache.has(key)) return -1
const value = this.cache.get(key)
// 删除后重新插入,使其成为最新使用的
this.cache.delete(key)
this.cache.set(key, value)
return value
}
put(key, value) {
// 如果已存在,先删除
if (this.cache.has(key)) {
this.cache.delete(key)
}
// 缓存满了,删除最久未使用的项(Map的第一项)
if (this.cache.size >= this.capacity) {
const firstKey = this.cache.keys().next().value
this.cache.delete(firstKey)
}
// 添加新项
this.cache.set(key, value)
}
}
49. 如何实现可拖拽组件?
答案:
// 核心API实现
function makeDraggable(element) {
let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0
element.onmousedown = dragMouseDown
function dragMouseDown(e) {
e.preventDefault()
// 获取鼠标起始位置
pos3 = e.clientX
pos4 = e.clientY
document.onmouseup = closeDragElement
document.onmousemove = elementDrag
}
function elementDrag(e) {
e.preventDefault()
// 计算鼠标移动距离
pos1 = pos3 - e.clientX
pos2 = pos4 - e.clientY
pos3 = e.clientX
pos4 = e.clientY
// 设置元素新位置
element.style.top = (element.offsetTop - pos2) + "px"
element.style.left = (element.offsetLeft - pos1) + "px"
}
function closeDragElement() {
// 停止移动
document.onmouseup = null
document.onmousemove = null
}
}
50. 现代前端工程化全景解析
答案:
- 开发规范:ESLint+Prettier+Husky+lint-staged
- 类型检查:TypeScript/JSDoc
- 包管理:npm/yarn/pnpm,支持Monorepo
- 构建工具:Vite/Webpack+Babel,支持CSS预处理
- 测试:Jest/Vitest+RTL/Cypress
- CI/CD:GitHub Actions/Jenkins,自动测试与部署
- 最佳实践:模块化设计、自动化流程、严格代码质量控制
51. 手写call、apply和bind方法
答案:
// 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
}
// apply实现
Function.prototype.myApply = function(context, args = []) {
context = context || window
const fn = Symbol('fn')
context[fn] = this
const result = context[fn](...args)
delete context[fn]
return result
}
// bind实现
Function.prototype.myBind = function(context, ...bindArgs) {
const self = this
return function(...args) {
return self.apply(context, [...bindArgs, ...args])
}
}
52. 实现数组扁平化flat函数
答案:
// 方法1:递归
function flatten(arr, depth = 1) {
return depth > 0
? arr.reduce((acc, val) =>
acc.concat(Array.isArray(val) ? flatten(val, depth - 1) : val),
[])
: arr.slice()
}
// 方法2:栈实现
function flatten(arr, depth = 1) {
const result = []
const stack = [...arr.map(item => ({ value: item, depth }))]
while (stack.length) {
const { value, depth } = stack.pop()
if (Array.isArray(value) && depth > 0) {
stack.push(...value.map(item => ({ value: item, depth: depth - 1 })))
} else {
result.unshift(value)
}
}
return result
}
53. 实现compose函数(函数式编程)
答案:
// compose从右到左执行函数
function compose(...fns) {
if (fns.length === 0) return arg => arg
if (fns.length === 1) return fns[0]
return fns.reduce((a, b) => (...args) => a(b(...args)))
}
// 示例
const add5 = x => x + 5
const multiply2 = x => x * 2
const subtract3 = x => x - 3
const calculate = compose(subtract3, multiply2, add5)
console.log(calculate(4)) // ((4 + 5) * 2) - 3 = 15
54. 解释JavaScript中的原型污染攻击
答案:
- 原型污染是通过修改Object.prototype添加/修改属性影响所有对象
- 典型攻击场景:不安全的对象合并/递归扩展函数
- 防御措施:
- 使用Object.create(null)创建无原型对象
- Object.freeze(Object.prototype)冻结原型
- 使用Map替代普通对象存储用户数据
- 深拷贝时检查__proto__等特殊属性
55. 实现一个简单的JSON解析器
答案:
function parseJSON(str) {
let i = 0
function parseValue() {
skipWhitespace()
const char = str[i]
if (char === '{') return parseObject()
if (char === '[') return parseArray()
if (char === '"') return parseString()
if (char === 't' && str.slice(i, i + 4) === 'true') {
i += 4
return true
}
if (char === 'f' && str.slice(i, i + 5) === 'false') {
i += 5
return false
}
if (char === 'n' && str.slice(i, i + 4) === 'null') {
i += 4
return null
}
return parseNumber()
}
function skipWhitespace() {
while (/\s/.test(str[i])) i++
}
function parseObject() {
i++ // Skip '{'
const obj = {}
skipWhitespace()
if (str[i] === '}') {
i++ // Skip '}'
return obj
}
while (true) {
const key = parseString()
skipWhitespace()
expect(':')
const value = parseValue()
obj[key] = value
skipWhitespace()
if (str[i] === '}') {
i++ // Skip '}'
return obj
}
expect(',')
}
}
function parseArray() {
i++ // Skip '['
const arr = []
skipWhitespace()
if (str[i] === ']') {
i++ // Skip ']'
return arr
}
while (true) {
arr.push(parseValue())
skipWhitespace()
if (str[i] === ']') {
i++ // Skip ']'
return arr
}
expect(',')
}
}
function parseString() {
i++ // Skip '"'
let result = ''
while (str[i] !== '"') {
if (str[i] === '\\') {
i++
if (str[i] === 'n') result += '\n'
else if (str[i] === 'r') result += '\r'
else if (str[i] === 't') result += '\t'
else result += str[i]
} else {
result += str[i]
}
i++
}
i++ // Skip closing '"'
return result
}
function parseNumber() {
const start = i
while (/[\d\.e\+\-]/i.test(str[i])) i++
return Number(str.slice(start, i))
}
function expect(char) {
skipWhitespace()
if (str[i] !== char) {
throw new Error(`Expected '${char}' at position ${i}`)
}
i++
skipWhitespace()
}
return parseValue()
}
56. JavaScript中的事件委托机制
答案:
// 事件委托示例
document.getElementById('parent-list').addEventListener('click', function(e) {
// 检查是否点击了列表项
if (e.target.tagName === 'LI') {
// 处理点击事件
console.log('点击了:', e.target.textContent)
}
})
// 优势:
// 1. 减少事件监听器数量,提升性能
// 2. 动态添加的元素自动获得事件处理能力
// 3. 内存占用更少
// 4. 无需在组件卸载时手动解绑多个事件
57. 实现一个简单的虚拟DOM和Diff算法
答案:
// 虚拟DOM节点
function h(tag, props, children) {
return { tag, props, children }
}
// 创建真实DOM
function createElement(vnode) {
if (typeof vnode === 'string') {
return document.createTextNode(vnode)
}
const { tag, props, children } = vnode
const element = document.createElement(tag)
// 设置属性
for (const key in props) {
if (key.startsWith('on')) {
// 事件处理
const eventName = key.slice(2).toLowerCase()
element.addEventListener(eventName, props[key])
} else {
element.setAttribute(key, props[key])
}
}
// 添加子节点
if (children) {
children.forEach(child => {
element.appendChild(createElement(child))
})
}
return element
}
// 简单的Diff算法
function updateElement(parent, oldVNode, newVNode, index = 0) {
// 新节点不存在
if (!newVNode) {
parent.removeChild(parent.childNodes[index])
return
}
// 旧节点不存在,创建新节点
if (!oldVNode) {
parent.appendChild(createElement(newVNode))
return
}
// 节点类型变化,替换
if (
typeof oldVNode !== typeof newVNode ||
(typeof oldVNode === 'string' && oldVNode !== newVNode) ||
oldVNode.tag !== newVNode.tag
) {
parent.replaceChild(
createElement(newVNode),
parent.childNodes[index]
)
return
}
// 如果是元素节点,继续比较属性和子节点
if (typeof newVNode !== 'string') {
// 对比更新属性...
// 对比子节点
const oldChildren = oldVNode.children || []
const newChildren = newVNode.children || []
const maxLength = Math.max(oldChildren.length, newChildren.length)
for (let i = 0; i < maxLength; i++) {
updateElement(
parent.childNodes[index],
oldChildren[i],
newChildren[i],
i
)
}
}
}
58. 实现图片懒加载(不使用IntersectionObserver)
答案:
function lazyLoad() {
const lazyImages = [].slice.call(document.querySelectorAll('img.lazy'))
if ('IntersectionObserver' in window) {
// 现代浏览器使用IntersectionObserver
// ...之前已经实现过
} else {
// 兼容方案
let active = false
const lazyLoad = function() {
if (active === false) {
active = true
setTimeout(function() {
lazyImages.forEach(function(lazyImage) {
if ((lazyImage.getBoundingClientRect().top <= window.innerHeight
&& lazyImage.getBoundingClientRect().bottom >= 0)
&& getComputedStyle(lazyImage).display !== 'none') {
lazyImage.src = lazyImage.dataset.src
lazyImage.classList.remove('lazy')
lazyImages = lazyImages.filter(function(image) {
return image !== lazyImage
})
if (lazyImages.length === 0) {
document.removeEventListener('scroll', lazyLoad)
window.removeEventListener('resize', lazyLoad)
window.removeEventListener('orientationchange', lazyLoad)
}
}
})
active = false
}, 200)
}
}
document.addEventListener('scroll', lazyLoad)
window.addEventListener('resize', lazyLoad)
window.addEventListener('orientationchange', lazyLoad)
}
}
// 初始化
document.addEventListener('DOMContentLoaded', lazyLoad)
59. 手写实现数组sort方法(快速排序)
答案:
// 快速排序实现
Array.prototype.mySort = function(compareFn) {
// 默认比较函数
compareFn = compareFn || ((a, b) => {
// 将值转为字符串进行比较
let sa = String(a)
let sb = String(b)
if (sa < sb) return -1
if (sa > sb) return 1
return 0
})
const arr = [...this]
function quickSort(arr, left = 0, right = arr.length - 1) {
if (left >= right) return
const pivot = partition(arr, left, right)
quickSort(arr, left, pivot - 1)
quickSort(arr, pivot + 1, right)
return arr
}
function partition(arr, left, right) {
// 选择中间点作为基准可以优化
const pivotIndex = Math.floor((left + right) / 2)
const pivotValue = arr[pivotIndex]
// 将基准移至末尾
[arr[pivotIndex], arr[right]] = [arr[right], arr[pivotIndex]]
let storeIndex = left
for (let i = left; i < right; i++) {
if (compareFn(arr[i], pivotValue) < 0) {
[arr[i], arr[storeIndex]] = [arr[storeIndex], arr[i]]
storeIndex++
}
}
// 将基准放到正确位置
[arr[storeIndex], arr[right]] = [arr[right], arr[storeIndex]]
return storeIndex
}
return quickSort(arr)
}
60. 实现一个并发控制的Promise调度器
答案:
class Scheduler {
constructor(maxConcurrent) {
this.maxConcurrent = maxConcurrent
this.running = 0
this.queue = []
}
add(promiseCreator) {
return new Promise(resolve => {
this.queue.push({
promiseCreator,
resolve
})
this.run()
})
}
run() {
if (this.running < this.maxConcurrent && this.queue.length) {
const { promiseCreator, resolve } = this.queue.shift()
this.running++
promiseCreator()
.then(result => {
resolve(result)
this.running--
this.run()
})
.catch(err => {
console.error(err)
this.running--
this.run()
})
}
}
}
// 使用示例
const scheduler = new Scheduler(2) // 最多同时执行2个任务
const timeout = time => new Promise(resolve => setTimeout(resolve, time))
const addTask = (time, order) => {
scheduler.add(() => timeout(time).then(() => console.log(order)))
}
addTask(1000, '1')
addTask(500, '2')
addTask(300, '3')
addTask(400, '4')
// 2和1同时执行
// 500ms后, 2完成,3开始
// 800ms后, 3完成,4开始
// 1000ms后, 1完成
// 1200ms后, 4完成
61. 手写实现一个简单的单向数据绑定
答案:
// 简单的发布-订阅模式实现数据绑定
class Observable {
constructor(data) {
this.data = data
this.observers = {}
// 对data的每个属性进行包装
Object.keys(data).forEach(key => {
let value = data[key]
Object.defineProperty(this.data, key, {
get: () => value,
set: newValue => {
if (value !== newValue) {
value = newValue
this.notify(key)
}
}
})
})
}
subscribe(key, callback) {
if (!this.observers[key]) {
this.observers[key] = []
}
this.observers[key].push(callback)
// 初始化调用一次
callback(this.data[key])
}
notify(key) {
if (this.observers[key]) {
this.observers[key].forEach(callback => callback(this.data[key]))
}
}
}
// 使用示例
const data = new Observable({
name: '张三',
age: 25
})
function updateDOM(value) {
document.getElementById('name').textContent = value
}
data.subscribe('name', updateDOM)
// 数据改变时会自动更新DOM
setTimeout(() => {
data.data.name = '李四' // 触发DOM更新
}, 2000)
62. 如何处理JavaScript中的浮点数精度问题?
答案:
// 常见浮点数问题
console.log(0.1 + 0.2) // 输出0.30000000000000004,而不是0.3
// 解决方案1:使用toFixed转为字符串
function addFixed(a, b) {
return (a + b).toFixed(2) // 返回字符串"0.30"
}
// 解决方案2:放大为整数计算
function add(a, b, precision = 10) {
const multiplier = Math.pow(10, precision)
return (Math.round(a * multiplier) + Math.round(b * multiplier)) / multiplier
}
// 解决方案3:专业库
// 使用decimal.js、big.js或bignumber.js等专业库处理
// 通用解决方案
function strip(num, precision = 12) {
return +parseFloat(num.toPrecision(precision))
}
63. 实现一个简单的前端路由
答案:
// Hash模式路由
class HashRouter {
constructor() {
this.routes = {}
this.currentUrl = ''
// 监听hash变化
window.addEventListener('load', this.refresh.bind(this), false)
window.addEventListener('hashchange', this.refresh.bind(this), false)
}
route(path, callback) {
this.routes[path] = callback || function() {}
}
refresh() {
this.currentUrl = location.hash.slice(1) || '/'
this.routes[this.currentUrl] && this.routes[this.currentUrl]()
}
}
// History模式路由
class HistoryRouter {
constructor() {
this.routes = {}
this.currentUrl = ''
window.addEventListener('load', this.refresh.bind(this), false)
window.addEventListener('popstate', this.refresh.bind(this), false)
// 监听链接点击,阻止默认跳转
window.addEventListener('click', e => {
const target = e.target
if (target.tagName === 'A') {
e.preventDefault()
this.push(target.getAttribute('href'))
}
})
}
route(path, callback) {
this.routes[path] = callback || function() {}
}
push(path) {
history.pushState({}, null, path)
this.routes[path] && this.routes[path]()
}
refresh() {
this.currentUrl = location.pathname
this.routes[this.currentUrl] && this.routes[this.currentUrl]()
}
}
// 使用示例
const router = new HashRouter()
router.route('/', function() {
document.getElementById('app').innerHTML = '首页'
})
router.route('/about', function() {
document.getElementById('app').innerHTML = '关于页面'
})
64. 基于Promise封装一个带重试功能的请求函数
答案:
function fetchWithRetry(url, options = {}, retries = 3, delay = 300) {
return new Promise((resolve, reject) => {
function attempt(attemptCount) {
fetch(url, options)
.then(resolve)
.catch(err => {
console.log(`请求失败(${attemptCount}), 剩余重试次数: ${retries - attemptCount}`)
if (attemptCount < retries) {
setTimeout(() => {
attempt(attemptCount + 1)
}, delay * Math.pow(2, attemptCount)) // 指数退避策略
} else {
reject(err)
}
})
}
attempt(0)
})
}
// 使用示例
fetchWithRetry('/api/data', { method: 'GET' }, 3)
.then(res => res.json())
.then(data => console.log(data))
.catch(err => console.error('最终请求失败', err))
65. 分析比较Proxy与Object.defineProperty
答案: Proxy优势:
- 能劫持整个对象而非单个属性,监听新增属性
- 可以监听数组变化,不需特殊处理
- 劫持方法更多(13种),如apply、construct、deleteProperty
- 性能更好,不需要递归遍历对象
Object.defineProperty限制:
- 不能监听数组方法如push/pop/splice
- 必须递归监听嵌套对象的所有属性
- 不能监听新增属性(Vue需要Vue.set)
- 操作繁琐,每个属性单独定义
示例比较:
// Object.defineProperty
function observe(obj) {
if (!obj || typeof obj !== 'object') return
Object.keys(obj).forEach(key => {
let value = obj[key]
if (typeof value === 'object') observe(value)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
console.log(`访问属性: ${key}, 值: ${value}`)
return value
},
set(newValue) {
console.log(`设置属性: ${key}, 值: ${newValue}`)
value = newValue
if (typeof newValue === 'object') observe(newValue)
}
})
})
}
// Proxy
const handler = {
get(target, property, receiver) {
console.log(`访问属性: ${property}`)
const value = Reflect.get(target, property, receiver)
if (typeof value === 'object' && value !== null) {
return new Proxy(value, handler)
}
return value
},
set(target, property, value, receiver) {
console.log(`设置属性: ${property}, 值: ${value}`)
return Reflect.set(target, property, value, receiver)
},
deleteProperty(target, property) {
console.log(`删除属性: ${property}`)
return Reflect.deleteProperty(target, property)
}
}
const observed = new Proxy({}, handler)
66. JS中的原型链继承
答案:
// ES5原型链继承
function Parent(name) {
this.name = name
}
Parent.prototype.sayName = function() {
console.log(this.name)
}
function Child(name, age) {
// 调用父类构造函数
Parent.call(this, name) // 借用构造函数继承属性
this.age = age
}
// 继承方法
Child.prototype = Object.create(Parent.prototype)
// 修正constructor指向
Child.prototype.constructor = Child
Child.prototype.sayAge = function() {
console.log(this.age)
}
67. 实现LazyMan函数
答案:
class _LazyMan {
constructor(name) {
this.name = name
this.tasks = []
// 添加第一个任务
this.tasks.push(() => {
console.log(`Hi! This is ${this.name}`)
this.next()
})
// 放入事件循环末尾,确保tasks被完全添加后执行
setTimeout(() => {
this.next()
}, 0)
}
next() {
const task = this.tasks.shift()
task && task()
}
sleep(time) {
this.tasks.push(() => {
setTimeout(() => {
console.log(`等待了${time}秒...`)
this.next()
}, time * 1000)
})
return this
}
sleepFirst(time) {
this.tasks.unshift(() => {
setTimeout(() => {
console.log(`等待了${time}秒...`)
this.next()
}, time * 1000)
})
return this
}
eat(food) {
this.tasks.push(() => {
console.log(`Eat ${food}`)
this.next()
})
return this
}
}
function LazyMan(name) {
return new _LazyMan(name)
}
// 示例
// LazyMan('Tony').eat('lunch').eat('dinner').sleepFirst(2).sleep(1)
// 会输出:
// (等待2秒)
// 等待了2秒...
// Hi! This is Tony
// Eat lunch
// Eat dinner
// (等待1秒)
// 等待了1秒...
68. 红绿灯交替问题(Promise实现)
答案:
function red() {
console.log('red')
}
function green() {
console.log('green')
}
function yellow() {
console.log('yellow')
}
const light = function(timer, cb) {
return new Promise(resolve => {
setTimeout(() => {
cb()
resolve()
}, timer)
})
}
const step = function() {
Promise.resolve()
.then(() => {
return light(3000, red)
})
.then(() => {
return light(1000, yellow)
})
.then(() => {
return light(2000, green)
})
.then(() => {
step() // 循环执行
})
}
step()
69. 实现大数相加
答案:
function add(a, b) {
// 确保a和b是字符串
a = a.toString()
b = b.toString()
// 找出最长的长度
const maxLength = Math.max(a.length, b.length)
// 用0补齐两个数字的长度
a = a.padStart(maxLength, '0')
b = b.padStart(maxLength, '0')
let carry = 0 // 进位
let result = '' // 结果
// 从个位开始相加
for (let i = maxLength - 1; i >= 0; i--) {
const sum = parseInt(a[i]) + parseInt(b[i]) + carry
result = (sum % 10) + result // 当前位的结果
carry = Math.floor(sum / 10) // 进位
}
// 如果最后还有进位
if (carry > 0) {
result = carry + result
}
return result
}
console.log(add('9999999999999999', '1')) // "10000000000000000"
70. 实现Promise.race和Promise.any
答案:
// 实现Promise.race
Promise.myRace = function(promises) {
return new Promise((resolve, reject) => {
for (let promise of promises) {
Promise.resolve(promise).then(resolve, reject)
}
})
}
// 实现Promise.any
Promise.myAny = function(promises) {
return new Promise((resolve, reject) => {
let errors = []
let count = 0
if (promises.length === 0) {
reject(new AggregateError([], 'All promises were rejected'))
return
}
promises.forEach((promise, index) => {
Promise.resolve(promise)
.then(value => {
resolve(value)
})
.catch(err => {
errors[index] = err
count++
if (count === promises.length) {
reject(new AggregateError(errors, 'All promises were rejected'))
}
})
})
})
}
71. 实现函数柯里化(curry)
答案:
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))
}
}
}
}
// 示例
function add(a, b, c) {
return a + b + c
}
const curriedAdd = curry(add)
console.log(curriedAdd(1)(2)(3)) // 6
console.log(curriedAdd(1, 2)(3)) // 6
console.log(curriedAdd(1)(2, 3)) // 6
72. 实现一个简单的发布订阅模式
答案:
class EventEmitter {
constructor() {
this.events = {}
}
// 订阅
on(eventName, callback) {
if (!this.events[eventName]) {
this.events[eventName] = []
}
this.events[eventName].push(callback)
return this
}
// 取消订阅
off(eventName, callback) {
if (!this.events[eventName]) return this
if (!callback) {
this.events[eventName] = []
} else {
this.events[eventName] = this.events[eventName].filter(cb => cb !== callback)
}
return this
}
// 发布
emit(eventName, ...args) {
if (!this.events[eventName]) return this
this.events[eventName].forEach(callback => {
callback.apply(this, args)
})
return this
}
// 订阅一次
once(eventName, callback) {
const wrapper = (...args) => {
callback.apply(this, args)
this.off(eventName, wrapper)
}
this.on(eventName, wrapper)
return this
}
}
// 使用示例
const emitter = new EventEmitter()
function user1(data) {
console.log('user1', data)
}
emitter.on('message', user1)
emitter.emit('message', 'Hello') // 输出: user1 Hello
73. 手写instanceof操作符
答案:
function myInstanceof(obj, constructor) {
// 检查基础类型和null
if (obj === null || typeof obj !== 'object') {
return false
}
// 获取对象的原型
let proto = Object.getPrototypeOf(obj)
// 沿原型链查找
while (proto) {
if (proto === constructor.prototype) {
return true
}
proto = Object.getPrototypeOf(proto)
}
return false
}
// 测试
console.log(myInstanceof({}, Object)) // true
console.log(myInstanceof([], Array)) // true
console.log(myInstanceof([], Object)) // true
console.log(myInstanceof({}, Array)) // false
74. 手写new操作符实现
答案:
function myNew(constructor, ...args) {
// 1. 创建一个新对象,并链接到构造函数的原型
const obj = Object.create(constructor.prototype)
// 2. 将构造函数的this绑定到新对象上并执行
const result = constructor.apply(obj, args)
// 3. 如果构造函数返回一个对象,则返回该对象,否则返回新创建的对象
return (typeof result === 'object' && result !== null) ? result : obj
}
// 使用示例
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sayHi = function() {
console.log(`Hi, I'm ${this.name}`)
}
const p = myNew(Person, '张三', 25)
console.log(p.name) // 张三
p.sayHi() // Hi, I'm 张三
75. 实现函数记忆(memoization)
答案:
function memoize(fn) {
const cache = new Map()
return function(...args) {
// 将参数转成字符串作为缓存的key
const key = JSON.stringify(args)
if (cache.has(key)) {
console.log('从缓存获取结果')
return cache.get(key)
}
console.log('计算结果')
const result = fn.apply(this, args)
cache.set(key, result)
return result
}
}
// 示例
function fibonacci(n) {
if (n <= 1) return n
return fibonacci(n - 1) + fibonacci(n - 2)
}
const memoFibonacci = memoize(function(n) {
if (n <= 1) return n
return memoFibonacci(n - 1) + memoFibonacci(n - 2)
})
console.time('普通')
fibonacci(35) // 很慢
console.timeEnd('普通')
console.time('记忆')
memoFibonacci(35) // 很快
console.timeEnd('记忆')
76. 实现一个简易版本的深度比较isEqual
答案:
function isEqual(a, b) {
// 处理基本类型比较
if (a === b) return true
// 处理null和undefined
if (a === null || b === null || a === undefined || b === undefined) return false
// 处理不同类型
if (typeof a !== typeof b) return false
// 处理日期对象
if (a instanceof Date && b instanceof Date) {
return a.getTime() === b.getTime()
}
// 处理正则表达式
if (a instanceof RegExp && b instanceof RegExp) {
return a.toString() === b.toString()
}
// 处理对象和数组
if (typeof a === 'object') {
const keysA = Object.keys(a)
const keysB = Object.keys(b)
// 检查键的数量
if (keysA.length !== keysB.length) return false
// 检查每个键值对
return keysA.every(key => {
return keysB.includes(key) && isEqual(a[key], b[key])
})
}
return false
}
// 测试
console.log(isEqual({a: 1, b: 2}, {a: 1, b: 2})) // true
console.log(isEqual({a: 1, b: 2}, {b: 2, a: 1})) // true
console.log(isEqual([1, 2, 3], [1, 2, 3])) // true
console.log(isEqual({a: 1}, {a: '1'})) // false
77. 实现一个简单的Redux
答案:
function createStore(reducer, initialState, enhancer) {
// 处理中间件
if (typeof enhancer === 'function') {
return enhancer(createStore)(reducer, initialState)
}
let state = initialState
const listeners = []
// 获取当前状态
function getState() {
return state
}
// 订阅状态变更
function subscribe(listener) {
listeners.push(listener)
// 返回取消订阅的函数
return function unsubscribe() {
const index = listeners.indexOf(listener)
listeners.splice(index, 1)
}
}
// 派发action
function dispatch(action) {
state = reducer(state, action)
// 通知所有监听器
listeners.forEach(listener => listener())
return action
}
// 初始化store
dispatch({type: '@@redux/INIT'})
return {
getState,
subscribe,
dispatch
}
}
// 示例使用
const initialState = { count: 0 }
function reducer(state = initialState, action) {
switch (action.type) {
case 'INCREMENT':
return { count: state.count + 1 }
case 'DECREMENT':
return { count: state.count - 1 }
default:
return state
}
}
const store = createStore(reducer)
store.subscribe(() => console.log(store.getState()))
store.dispatch({ type: 'INCREMENT' }) // { count: 1 }
store.dispatch({ type: 'INCREMENT' }) // { count: 2 }
78. JavaScript设计模式 - 单例模式实现
答案:
// ES6单例模式
class Singleton {
constructor(name) {
this.name = name
}
static getInstance(name) {
if (!this.instance) {
this.instance = new Singleton(name)
}
return this.instance
}
}
// 使用
const instance1 = Singleton.getInstance('instance1')
const instance2 = Singleton.getInstance('instance2')
console.log(instance1 === instance2) // true
console.log(instance1.name) // "instance1" (第二次调用没有生效)
// 闭包实现单例模式
function SingletonFactory(name) {
let instance
return function(name) {
if (!instance) {
instance = {
name
}
}
return instance
}
}
const createSingleton = SingletonFactory()
const instance3 = createSingleton('instance3')
const instance4 = createSingleton('instance4')
console.log(instance3 === instance4) // true
79. 解析URL参数为对象
答案:
function parseURLParams(url) {
// 提取查询字符串部分
const queryString = url.split('?')[1]
if (!queryString) return {}
// 按&分割参数
return queryString.split('&').reduce((params, param) => {
// 处理=分隔的键值对
let [key, value] = param.split('=')
// 解码URL编码的值
key = decodeURIComponent(key)
value = decodeURIComponent(value || '')
// 处理数组参数 如 colors=red&colors=blue
if (params.hasOwnProperty(key)) {
params[key] = [].concat(params[key], value)
} else {
params[key] = value
}
return params
}, {})
}
// 示例
const url = 'https://example.com/page?name=John&age=30&colors=red&colors=blue'
console.log(parseURLParams(url))
// {name: "John", age: "30", colors: ["red", "blue"]}
80. 实现虚拟DOM渲染为真实DOM
答案:
// 虚拟DOM结构
const vdom = {
type: 'div',
props: { id: 'container', className: 'wrapper' },
children: [
{ type: 'h1', props: {}, children: ['标题'] },
{
type: 'p',
props: { className: 'content' },
children: ['这是一段文字']
},
{
type: 'ul',
props: {},
children: [
{ type: 'li', props: {}, children: ['项目1'] },
{ type: 'li', props: {}, children: ['项目2'] }
]
}
]
}
// 渲染函数
function render(vnode, container) {
if (typeof vnode === 'string') {
container.appendChild(document.createTextNode(vnode))
return
}
// 创建元素
const element = document.createElement(vnode.type)
// 添加属性
const props = vnode.props || {}
Object.keys(props).forEach(key => {
if (key === 'className') {
element.setAttribute('class', props[key])
} else if (key.startsWith('on')) {
const eventName = key.slice(2).toLowerCase()
element.addEventListener(eventName, props[key])
} else {
element.setAttribute(key, props[key])
}
})
// 递归渲染子节点
const children = vnode.children || []
children.forEach(child => {
render(child, element)
})
// 将元素添加到容器
container.appendChild(element)
}
// 使用示例
// render(vdom, document.getElementById('root'))
81. 工厂模式
定义:创建对象的接口,让子类决定实例化哪个类
实现:
// 简单工厂
class UserFactory {
static createUser(type) {
switch(type) {
case 'admin':
return new Admin();
case 'member':
return new Member();
default:
return new Guest();
}
}
}
// 使用
const admin = UserFactory.createUser('admin');
应用:jQuery的$()、React.createElement()
82. 构造器模式
定义:使用构造函数创建特定类型对象
实现:
function Person(name, age) {
this.name = name;
this.age = age;
this.sayName = function() {
console.log(this.name);
};
}
// ES6类语法
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
sayName() {
console.log(this.name);
}
}
// 使用
const person = new Person('张三', 25);
83. 模块模式
定义:为应用程序提供私有和公共封装
实现:
// IIFE方式
const Calculator = (function() {
// 私有变量
let result = 0;
// 私有方法
function validate(n) {
return typeof n === 'number';
}
// 公共API
return {
add(n) {
if(validate(n)) {
result += n;
}
return this;
},
subtract(n) {
if(validate(n)) {
result -= n;
}
return this;
},
getResult() {
return result;
}
};
})();
// ES6模块
// math.js
const privateValue = 0;
function privateFunc() {}
export function publicFunc() {}
应用:前端模块化开发、库的封装
84. 观察者模式/发布订阅模式
定义:定义对象间的一种依赖关系,当一个对象状态改变时,其依赖对象都会收到通知
实现:
class EventEmitter {
constructor() {
this.events = {};
}
on(event, callback) {
if(!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(callback);
return this;
}
emit(event, ...args) {
const callbacks = this.events[event] || [];
callbacks.forEach(callback => callback.apply(this, args));
return this;
}
off(event, callback) {
if(!callback) {
delete this.events[event];
} else {
this.events[event] = this.events[event].filter(cb => cb !== callback);
}
return this;
}
}
应用:DOM事件、Vue的事件总线、Node.js的EventEmitter
85. 策略模式
定义:定义一系列算法,封装每个算法,并使它们可互换
实现:
// 表单验证示例
const strategies = {
isNonEmpty(value, errorMsg) {
return value.trim() ? '' : errorMsg;
},
minLength(value, length, errorMsg) {
return value.length >= length ? '' : errorMsg;
},
isMobile(value, errorMsg) {
return /^1\d{10}$/.test(value) ? '' : errorMsg;
}
};
class Validator {
constructor() {
this.validations = [];
}
add(value, rule, errorMsg) {
const strategyArgs = rule.split(':');
const strategy = strategyArgs.shift();
this.validations.push(() => {
strategyArgs.unshift(value);
strategyArgs.push(errorMsg);
return strategies[strategy].apply(null, strategyArgs);
});
}
validate() {
for(let validation of this.validations) {
const errorMsg = validation();
if(errorMsg) return errorMsg;
}
return '';
}
}
应用:表单验证、动画算法、支付方式选择
86. 命令模式
定义:将请求封装为对象,使发出请求的责任和执行请求的责任分割开
实现:
class Command {
constructor(receiver) {
this.receiver = receiver;
}
execute() {
throw new Error('子类必须实现execute方法');
}
}
class LightOnCommand extends Command {
execute() {
this.receiver.turnOn();
}
}
class LightOffCommand extends Command {
execute() {
this.receiver.turnOff();
}
}
class Light {
turnOn() {
console.log('灯打开了');
}
turnOff() {
console.log('灯关闭了');
}
}
class RemoteControl {
submit(command) {
command.execute();
}
}
// 使用
const light = new Light();
const lightOn = new LightOnCommand(light);
const remote = new RemoteControl();
remote.submit(lightOn); // 灯打开了
应用:撤销/重做功能、操作队列、宏命令
87. 装饰器模式
定义:动态地给对象添加额外的职责
实现:
// ES7装饰器
function readonly(target, key, descriptor) {
descriptor.writable = false;
return descriptor;
}
function log(target, name, descriptor) {
const original = descriptor.value;
descriptor.value = function(...args) {
console.log(`调用: ${name}, 参数: ${JSON.stringify(args)}`);
const result = original.apply(this, args);
console.log(`结果: ${result}`);
return result;
};
return descriptor;
}
class Math {
@log
@readonly
add(a, b) {
return a + b;
}
}
// 函数式实现
function withLogging(wrappedFunction) {
return function(...args) {
console.log(`调用参数: ${JSON.stringify(args)}`);
const result = wrappedFunction.apply(this, args);
console.log(`结果: ${result}`);
return result;
};
}
const add = (a, b) => a + b;
const loggingAdd = withLogging(add);
应用:日志记录、性能测量、权限验证
88. 适配器模式
定义:将一个类的接口转换成客户希望的另一个接口
实现:
// 旧API
class OldAPI {
getItems() {
return [{ name: 'item1', cost: 100 }];
}
}
// 新API期望的格式
class NewAPI {
getProducts() {
return [{ productName: 'product1', price: 100 }];
}
}
// 适配器
class APIAdapter {
constructor(oldAPI) {
this.oldAPI = oldAPI;
}
getProducts() {
const items = this.oldAPI.getItems();
return items.map(item => ({
productName: item.name,
price: item.cost
}));
}
}
// 使用
const oldAPI = new OldAPI();
const adapter = new APIAdapter(oldAPI);
// 现在可以使用adapter.getProducts()替代oldAPI.getItems()
应用:兼容不同数据格式、整合第三方库、旧接口迁移
89. 代理模式
定义:为其他对象提供一种代理以控制对这个对象的访问
实现:
// 实际图片对象
class RealImage {
constructor(filename) {
this.filename = filename;
this.loadFromDisk();
}
loadFromDisk() {
console.log(`加载图片: ${this.filename}`);
}
display() {
console.log(`展示图片: ${this.filename}`);
}
}
// 代理图片
class ProxyImage {
constructor(filename) {
this.filename = filename;
this.realImage = null;
}
display() {
if(!this.realImage) {
this.realImage = new RealImage(this.filename);
}
this.realImage.display();
}
}
// 使用
const image = new ProxyImage('test.jpg');
// 图片未加载
image.display();
// 输出: 加载图片: test.jpg
// 输出: 展示图片: test.jpg
// ES6 Proxy实现
const target = {
name: '张三',
age: 30
};
const handler = {
get(target, prop) {
console.log(`获取属性: ${prop}`);
return target[prop];
},
set(target, prop, value) {
console.log(`设置属性: ${prop} = ${value}`);
target[prop] = value;
return true;
}
};
const proxy = new Proxy(target, handler);
应用:Vue3响应式系统、图片延迟加载、权限控制
90. 中介者模式
定义:定义一个中介对象来封装一系列对象之间的交互
实现:
class Mediator {
constructor() {
this.colleagues = [];
}
register(colleague) {
this.colleagues.push(colleague);
colleague.mediator = this;
}
send(message, sender) {
this.colleagues.forEach(colleague => {
if(colleague !== sender) {
colleague.receive(message);
}
});
}
}
class Colleague {
constructor(name) {
this.name = name;
this.mediator = null;
}
send(message) {
console.log(`${this.name} 发送消息: ${message}`);
this.mediator.send(message, this);
}
receive(message) {
console.log(`${this.name} 接收消息: ${message}`);
}
}
// 使用
const mediator = new Mediator();
const c1 = new Colleague('用户1');
const c2 = new Colleague('用户2');
const c3 = new Colleague('用户3');
mediator.register(c1);
mediator.register(c2);
mediator.register(c3);
c1.send('Hello');
// 输出:
// 用户1 发送消息: Hello
// 用户2 接收消息: Hello
// 用户3 接收消息: Hello
应用:聊天室、表单组件通信、飞机塔台调度
91. 享元模式
定义:通过共享相同的对象部分来最小化内存使用
实现:
// 享元工厂
class FlyweightFactory {
constructor() {
this.flyweights = {};
}
getFlyweight(key) {
if(!this.flyweights[key]) {
this.flyweights[key] = new ConcreteFlyweight(key);
}
return this.flyweights[key];
}
getCount() {
return Object.keys(this.flyweights).length;
}
}
// 享元对象
class ConcreteFlyweight {
constructor(intrinsicState) {
this.intrinsicState = intrinsicState;
}
operation(extrinsicState) {
console.log(`内部状态: ${this.intrinsicState}, 外部状态: ${extrinsicState}`);
}
}
// 客户端
class FlyweightClient {
constructor() {
this.flyweightFactory = new FlyweightFactory();
this.flyweights = [];
this.states = [];
}
addFlyweight(intrinsicState, extrinsicState) {
const flyweight = this.flyweightFactory.getFlyweight(intrinsicState);
this.flyweights.push(flyweight);
this.states.push(extrinsicState);
}
process() {
for(let i = 0; i < this.flyweights.length; i++) {
this.flyweights[i].operation(this.states[i]);
}
}
}
// 使用
const client = new FlyweightClient();
client.addFlyweight('红色', 'circle');
client.addFlyweight('蓝色', 'square');
client.addFlyweight('红色', 'triangle');
client.process();
console.log(`享元对象数量: ${client.flyweightFactory.getCount()}`); // 2
应用:文字编辑器中的字符表示、游戏中的共享图形资源
92. 组合模式
定义:将对象组合成树形结构以表示"部分-整体"的层次结构
实现:
// 组件接口
class Component {
constructor(name) {
this.name = name;
}
add() { /* 默认实现 */ }
remove() { /* 默认实现 */ }
getChild() { /* 默认实现 */ }
operation() {
throw new Error('子类必须实现operation方法');
}
}
// 叶子节点
class Leaf extends Component {
operation() {
console.log(`叶子节点 ${this.name} 操作`);
}
}
// 复合节点
class Composite extends Component {
constructor(name) {
super(name);
this.children = [];
}
add(component) {
this.children.push(component);
}
remove(component) {
const index = this.children.indexOf(component);
if(index !== -1) {
this.children.splice(index, 1);
}
}
getChild(index) {
return this.children[index];
}
operation() {
console.log(`复合节点 ${this.name} 操作`);
this.children.forEach(child => {
child.operation();
});
}
}
// 使用
const root = new Composite('根节点');
const branch1 = new Composite('分支1');
const branch2 = new Composite('分支2');
const leaf1 = new Leaf('叶子1');
const leaf2 = new Leaf('叶子2');
const leaf3 = new Leaf('叶子3');
root.add(branch1);
root.add(branch2);
branch1.add(leaf1);
branch1.add(leaf2);
branch2.add(leaf3);
root.operation();
应用:文件系统目录结构、HTML/XML文档结构、UI组件树
93. 责任链模式
定义:将请求沿着处理链进行传递,链上的对象决定是处理还是传递给下一个
实现:
class Handler {
constructor() {
this.nextHandler = null;
}
setNext(handler) {
this.nextHandler = handler;
return handler;
}
handle(request) {
if(this.nextHandler) {
return this.nextHandler.handle(request);
}
return null;
}
}
class ConcreteHandlerA extends Handler {
handle(request) {
if(request === 'TypeA') {
return `处理器A处理了${request}`;
}
return super.handle(request);
}
}
class ConcreteHandlerB extends Handler {
handle(request) {
if(request === 'TypeB') {
return `处理器B处理了${request}`;
}
return super.handle(request);
}
}
class ConcreteHandlerC extends Handler {
handle(request) {
if(request === 'TypeC') {
return `处理器C处理了${request}`;
}
return super.handle(request);
}
}
// 使用
const handlerA = new ConcreteHandlerA();
const handlerB = new ConcreteHandlerB();
const handlerC = new ConcreteHandlerC();
handlerA.setNext(handlerB).setNext(handlerC);
console.log(handlerA.handle('TypeB')); // 处理器B处理了TypeB
console.log(handlerA.handle('TypeA')); // 处理器A处理了TypeA
console.log(handlerA.handle('TypeX')); // null
应用:事件冒泡、异常处理链、中间件处理
94. 模板方法模式
定义:定义一个算法的骨架,将具体步骤延迟到子类中实现
实现:
class Beverage {
// 模板方法
prepare() {
this.boilWater();
this.brew();
this.pourInCup();
if(this.customerWantsCondiments()) {
this.addCondiments();
}
}
boilWater() {
console.log('将水煮沸');
}
brew() {
throw new Error('子类必须实现brew方法');
}
pourInCup() {
console.log('倒入杯中');
}
customerWantsCondiments() {
return true; // 钩子方法,默认返回true
}
addCondiments() {
throw new Error('子类必须实现addCondiments方法');
}
}
class Coffee extends Beverage {
brew() {
console.log('冲泡咖啡');
}
addCondiments() {
console.log('加糖和牛奶');
}
customerWantsCondiments() {
// 实现自己的逻辑
return Math.random() > 0.5;
}
}
class Tea extends Beverage {
brew() {
console.log('浸泡茶叶');
}
addCondiments() {
console.log('加柠檬');
}
}
// 使用
const coffee = new Coffee();
coffee.prepare();
const tea = new Tea();
tea.prepare();
应用:构建UI组件、处理HTTP请求响应流程、生命周期钩子
95. 迭代器模式
定义:提供一种方法顺序访问一个聚合对象中的各个元素,而不暴露其内部表示
实现:
class Iterator {
constructor(collection) {
this.collection = collection;
this.index = 0;
}
hasNext() {
return this.index < this.collection.length;
}
next() {
return this.hasNext() ? this.collection[this.index++] : null;
}
current() {
return this.collection[this.index];
}
reset() {
this.index = 0;
}
}
class Collection {
constructor() {
this.items = [];
}
addItem(item) {
this.items.push(item);
}
createIterator() {
return new Iterator(this.items);
}
get length() {
return this.items.length;
}
}
// ES6原生迭代器
class ES6Collection {
constructor() {
this.items = [];
}
addItem(item) {
this.items.push(item);
}
// 实现可迭代协议
[Symbol.iterator]() {
let index = 0;
const items = this.items;
return {
next() {
return {
value: items[index],
done: index++ >= items.length
};
}
};
}
}
// 使用
const collection = new Collection();
collection.addItem('项目1');
collection.addItem('项目2');
collection.addItem('项目3');
const iterator = collection.createIterator();
while(iterator.hasNext()) {
console.log(iterator.next());
}
// 使用ES6迭代器
const es6Collection = new ES6Collection();
es6Collection.addItem('A');
es6Collection.addItem('B');
for(let item of es6Collection) {
console.log(item); // A, B
}
应用:遍历不同数据结构、实现惰性计算序列、处理复杂嵌套结构
96. 访问者模式
定义:表示一个作用于对象结构中的各元素的操作,使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作
实现:
// 元素接口
class Element {
accept(visitor) {
throw new Error('子类必须实现accept方法');
}
}
// 具体元素
class ConcreteElementA extends Element {
accept(visitor) {
visitor.visitElementA(this);
}
operationA() {
return 'ElementA 操作';
}
}
class ConcreteElementB extends Element {
accept(visitor) {
visitor.visitElementB(this);
}
operationB() {
return 'ElementB 操作';
}
}
// 访问者接口
class Visitor {
visitElementA(elementA) {
throw new Error('子类必须实现visitElementA方法');
}
visitElementB(elementB) {
throw new Error('子类必须实现visitElementB方法');
}
}
// 具体访问者
class ConcreteVisitor1 extends Visitor {
visitElementA(elementA) {
console.log(`访问者1: ${elementA.operationA()}`);
}
visitElementB(elementB) {
console.log(`访问者1: ${elementB.operationB()}`);
}
}
class ConcreteVisitor2 extends Visitor {
visitElementA(elementA) {
console.log(`访问者2: ${elementA.operationA()}`);
}
visitElementB(elementB) {
console.log(`访问者2: ${elementB.operationB()}`);
}
}
// 对象结构
class ObjectStructure {
constructor() {
this.elements = [];
}
addElement(element) {
this.elements.push(element);
}
accept(visitor) {
this.elements.forEach(element => {
element.accept(visitor);
});
}
}
// 使用
const structure = new ObjectStructure();
structure.addElement(new ConcreteElementA());
structure.addElement(new ConcreteElementB());
const visitor1 = new ConcreteVisitor1();
const visitor2 = new ConcreteVisitor2();
structure.accept(visitor1);
structure.accept(visitor2);
应用:操作复杂对象结构、实现双分派、添加新操作而不修改类
97. 状态模式
定义:允许一个对象在其内部状态改变时改变它的行为
实现:
// 状态接口
class State {
handle(context) {
throw new Error('子类必须实现handle方法');
}
}
// 具体状态
class ConcreteStateA extends State {
handle(context) {
console.log('当前是状态A');
context.setState(new ConcreteStateB());
}
}
class ConcreteStateB extends State {
handle(context) {
console.log('当前是状态B');
context.setState(new ConcreteStateA());
}
}
// 上下文
class Context {
constructor() {
this.state = new ConcreteStateA();
}
setState(state) {
this.state = state;
}
request() {
this.state.handle(this);
}
}
// 使用
const context = new Context();
context.request(); // 当前是状态A
context.request(); // 当前是状态B
context.request(); // 当前是状态A
应用:游戏角色状态、UI组件状态、订单处理流程
98. 备忘录模式
定义:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态
实现:
// 发起人
class Originator {
constructor() {
this.state = '';
}
setState(state) {
this.state = state;
}
getState() {
return this.state;
}
createMemento() {
return new Memento(this.state);
}
restoreFromMemento(memento) {
this.state = memento.getState();
}
}
// 备忘录
class Memento {
constructor(state) {
this.state = state;
}
getState() {
return this.state;
}
}
// 管理者
class Caretaker {
constructor() {
this.mementos = [];
}
addMemento(memento) {
this.mementos.push(memento);
}
getMemento(index) {
return this.mementos[index];
}
}
// 使用
const originator = new Originator();
const caretaker = new Caretaker();
originator.setState('状态1');
caretaker.addMemento(originator.createMemento());
originator.setState('状态2');
caretaker.addMemento(originator.createMemento());
originator.setState('状态3');
console.log(`当前状态: ${originator.getState()}`);
// 恢复到状态1
originator.restoreFromMemento(caretaker.getMemento(0));
console.log(`恢复后状态: ${originator.getState()}`);
应用:实现撤销/重做功能、保存游戏进度、编辑器状态保存
99. 原型模式
定义:用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象
实现:
// ES5实现
function Prototype() {
this.name = 'default';
this.features = ['feature1', 'feature2'];
}
Prototype.prototype.clone = function() {
const clone = new Prototype();
clone.name = this.name;
clone.features = [...this.features]; // 深拷贝数组
return clone;
};
// ES6实现
class PrototypeES6 {
constructor() {
this.name = 'default';
this.features = ['feature1', 'feature2'];
}
clone() {
// 创建一个新的实例
const clone = Object.create(
Object.getPrototypeOf(this),
Object.getOwnPropertyDescriptors(this)
);
// 深拷贝数组等引用类型
clone.features = [...this.features];
return clone;
}
}
// 使用
const prototype1 = new Prototype();
prototype1.name = 'prototype1';
prototype1.features.push('feature3');
const prototype2 = prototype1.clone();
console.log(prototype2.name); // prototype1
console.log(prototype2.features); // ['feature1', 'feature2', 'feature3']
prototype2.features.push('feature4');
console.log(prototype1.features); // ['feature1', 'feature2', 'feature3']
console.log(prototype2.features); // ['feature1', 'feature2', 'feature3', 'feature4']
应用:对象复制、预定义对象配置、减少对象创建开销
100. MVC、MVP和MVVM模式
MVC模式:
// 模型
class UserModel {
constructor(name, age) {
this.name = name;
this.age = age;
}
setName(name) {
this.name = name;
}
setAge(age) {
this.age = age;
}
getName() {
return this.name;
}
getAge() {
return this.age;
}
}
// 视图
class UserView {
constructor() {
this.controller = null;
}
setController(controller) {
this.controller = controller;
}
render(name, age) {
console.log(`用户名: ${name}, 年龄: ${age}`);
}
// 用户交互
handleUserUpdate(name, age) {
this.controller.updateUser(name, age);
}
}
// 控制器
class UserController {
constructor(model, view) {
this.model = model;
this.view = view;
this.view.setController(this);
this.refreshView();
}
updateUser(name, age) {
this.model.setName(name);
this.model.setAge(age);
this.refreshView();
}
refreshView() {
this.view.render(this.model.getName(), this.model.getAge());
}
}
// 使用
const model = new UserModel('张三', 25);
const view = new UserView();
const controller = new UserController(model, view);
// 用户交互,更新数据
view.handleUserUpdate('李四', 30);
MVVM模式:
// 简化版MVVM实现
class Model {
constructor(data) {
this.data = data;
}
getData() {
return this.data;
}
setData(data) {
this.data = {...this.data, ...data};
return this.data;
}
}
// ViewModel
class ViewModel {
constructor(model) {
this.model = model;
this.bindings = {};
this.proxiedData = this.createProxy();
}
createProxy() {
const vm = this;
return new Proxy({}, {
get(target, property) {
return vm.model.getData()[property];
},
set(target, property, value) {
const newData = {};
newData[property] = value;
vm.model.setData(newData);
vm.notifySubscribers(property);
return true;
}
});
}
bind(property, element, attribute) {
if(!this.bindings[property]) {
this.bindings[property] = [];
}
this.bindings[property].push({element, attribute});
this.updateElement({element, attribute}, property);
}
notifySubscribers(property) {
if(this.bindings[property]) {
this.bindings[property].forEach(binding => {
this.updateElement(binding, property);
});
}
}
updateElement(binding, property) {
const value = this.model.getData()[property];
if(binding.attribute === 'textContent') {
binding.element.textContent = value;
} else {
binding.element.setAttribute(binding.attribute, value);
}
}
}
// 使用
const model = new Model({name: '张三', age: 25});
const viewModel = new ViewModel(model);
// 假设DOM元素存在
const nameElement = document.createElement('div');
const ageElement = document.createElement('span');
document.body.appendChild(nameElement);
document.body.appendChild(ageElement);
// 绑定元素
viewModel.bind('name', nameElement, 'textContent');
viewModel.bind('age', ageElement, 'textContent');
// 使用代理修改数据,会自动更新UI
viewModel.proxiedData.name = '李四';
viewModel.proxiedData.age = 30;
应用:前端框架架构、复杂UI交互设计、数据绑定系统