面向对象✅
什么是面向对象编程(OOP)?它与函数式编程有何区别?
答案
面向对象编程(Object-oriented programming OOP)是一种通过对象来封装数据与行为的编程范式。对象是具有状态(数据)和行为(方法)的实体,OOP 的核心思想在于将复杂问题分解为相互作用的对象,从而提高代码的模块化和可维护性。与函数式编程(FP)相比,OOP 更强调状态和可变性,而 FP 强调无副作用的纯函数和不可变数据。
在 OOP 中,开发者通过定义类(或构造函数)来创建对象,并利用继承、多态和封装来管理代码复杂度。通过对象实例来操作和维护状态。
OOP 通过封装、组合、继承、多态等特性促进代码复用和模块化。但在前端场景中,过度使用继承可能导致组件层级过深,增加维护成本。状态管理不当也容易引发 UI 的非预期变化。
在前端开发中,OOP 的典型场景如下:
- 组件化开发:如 React、Vue 的组件定义等,基于组件模式实现 UI 的封装与复用。
- 事件系统:利用发布-订阅模式(观察者模式的一种)实现组件间的解耦和灵活通信。
- Express 中间件:采用了职责链模式,请求在多个中间件中依次处理,每个中间件负责特定的任务。
JavaScript 中的对象和类是如何定义的?请举例说明
答案
对象创建方法
方法 | 语法示例 | 优势 | 局限性 |
---|---|---|---|
对象字面量 | const obj = { prop: value } | 简洁易读,适合单例对象 | 不支持创建多个相似结构的对象 |
new 关键字初始化 | function Person(name) { this.name = name } 或 class A {} | 可创建多个实例,支持原型继承 | 适用于复用的对象,简单对象创建建议采用字面量形式 |
Object.assign() | const obj = Object.assign({}, sourceObj) | 可合并多个对象,实现浅拷贝 | 只复制可枚举属性,嵌套对象仍共享引用 |
Object.create() | const obj = Object.create(proto) | 直接设置原型,实现继承 | 初始化属性较繁琐 |
类创建方法
方法 | 语法示例 | 优势 | 局限性 |
---|---|---|---|
ES6 类语法 | class Person { constructor(name) { this.name = name } } | 语法清晰,支持继承和静态方法 | 本质仍是原型继承的语法糖 |
构造函数+原型 | function Person(name){this.name=name}; Person.prototype.greet=function(){} | 兼容性好 | 分离实例属性和原型方法, 代码组织分散,不直观 |
类表达式 | const Person = class { constructor(name) { this.name = name } } | 可用于闭包和高阶函数 | 与类声明类似,仅语法形式不同 |
IIFE类模式 | const Person = (function(){return class{}}()) | 可创建私有状态和方法 | 较复杂,可读性较差 |
示例说明如下
- 对象创建
- 类创建
/**
* 1. 对象字面量
* 关键特性: 直接定义对象属性和方法
* 优势: 简洁易读,适合单例对象
* 局限性: 不支持创建多个相似结构的对象
* */
const objLiteral = { prop: 'value' }
console.log(objLiteral) // { prop: 'value' }
/**
* 2. new 关键字初始化
* 关键特性: 通过 new 关键字创建实例
* 优势: 可创建多个实例,支持原型继承
* 局限性: 适用于复用的对象,简单对象创建建议采用字面量形式
*/
// 2.1 使用构造函数模式
function Person (name) {
this.name = name
}
const person1 = new Person('Alice')
console.log(person1) // Person { name: 'Alice' }
// 2.2 使用类模式
class Animal {
constructor (type) {
this.type = type
}
}
const animal1 = new Animal('Dog')
console.log(animal1) // Animal { type: 'Dog' }
/**
* 3. Object.assign()
* 关键特性: 复制一个或多个源对象的属性到目标对象
* 优势: 可合并多个对象,实现浅拷贝
* 局限性: 只复制可枚举属性,嵌套对象仍共享引用
*/
//
const sourceObj = { a: 1, b: 2 }
const objAssign = Object.assign({}, sourceObj)
console.log(objAssign) // { a: 1, b: 2 }
/**
* 4. Object.create()
* 关键特性: 基于现有对象创建新对象
* 优势: 直接设置原型,实现继承
* 局限性: 初始化属性较繁琐
*/
const proto = { greet: function () { console.log('Hello!') } }
const objCreate = Object.create(proto)
console.log(objCreate) // {}
objCreate.greet() // Hello!
/**
* 1. ES6 类语法
* 关键特性:现代类语法,含构造器和方法定义
* 优势:语法清晰,支持继承和静态方法私有属性
* 局限性:本质仍是原型继承的语法糖
* */
class Person {
// ===== ES2015 (ES6) =====
static species = 'Human' // Static properties
constructor (name) {
this.name = name
Person.#count++
}
greet () { // Public methods
console.log(`Hello, my name is ${this.name}`)
}
get profile () { // Getter
return `${this.name}, age: ${this.age}`
}
set profile (value) { // Setter
[this.name, this.age] = value.split(',')
}
static getCount () { // Static methods
return this.#count
}
// ===== ES2019 =====
#privateField = 'private value' // Private fields
static #count = 0 // Static private fields
// ===== ES2020 =====
#privateMethod () { // Private methods
return `${this.name}'s private data: ${this.#privateField}`
}
// ===== ES2022 =====
name // Public class fields
age = 0
// Static initialization block
static {
console.log('Class initialization')
}
}
const person = new Person('Alice')
console.log(person.name) // Alice
/**
* 2. 构造函数+原型
* 关键特性:分离实例属性和原型方法
* 优势:内存效率高,方法共享
* 局限性:代码组织分散,不直观
* */
function Animal (type) {
this.type = type
}
Animal.prototype.speak = function () {
console.log(`${this.type} makes a noise.`)
}
const dog = new Animal('Dog')
dog.speak() // Dog makes a noise.
/**
* 3. 类表达式
* 关键特性:匿名或命名类表达式
* 优势:可用于闭包和高阶函数
* 局限性:与类声明类似,仅语法形式不同
* react 总一些高阶组件经常使用类表达式
*/
// 类表达式示例:UI组件工厂
// HOC with class expression example
// 模拟 React 组件用来说明概念
const React = { Component: {} }
const withExtraProps = (WrappedComponent) => {
return class HocDemo extends React.Component {
componentDidMount () {
console.log('HOC mounted')
}
constructor (props) {
super(props)
this.state = {
extraData: 'Enhanced with HOC'
}
}
render () {
// Pass the original props and add our extra props
return <WrappedComponent
{...this.props}
extraData={this.state.extraData}
/>
}
}
}
// 使用
class BaseComponent extends React.Component {
render () {
return <div>
{this.props.children}
{this.props.extraData && <p>{this.props.extraData}</p>}
</div>
}
}
// 增强类组件
const EnhancedComponent = withExtraProps(BaseComponent)
console.log(EnhancedComponent)
/**
* 4. IIFE类模式
* 关键特性:立即执行函数返回类定义
* 优势:可创建私有状态和方法用来模拟私有成员
* 局限性:较复杂,可读性较差
*/
// 典型示例:模块化 Counter
const Counter = (function () {
let count = 0 // 私有状态
class Counter {
constructor () {
if (typeof Counter.instance === 'object') {
return Counter.instance
}
Counter.instance = this
return this
}
increment () { // 公有方法
count++
console.log('count: ', count)
}
decrement () {
count--
console.log('count: ', count)
}
getCount () {
return count
}
}
return Counter
})()
const counter1 = new Counter()
counter1.increment() // 输出: count: 1
counter1.increment() // 输出: count: 2
const counter2 = new Counter()
counter2.decrement() // 输出: count: 1
console.log(counter1.getCount())
console.log(counter2.getCount())
注意类和对象的区别,对象是一个具体的实例,类是一个创建对象的模版,现实映射的话,类是工厂加工的模具用来批量产出同种类型的产品,而对象是模具加工出来的产品。
什么是继承,JavaScript 中的继承机制的实现方式?
答案
继承是面向对象编程中的一种机制,允许一个对象(子类)获取另一个对象(父类)的属性和方法。它使得代码复用成为可能,并且可以通过扩展父类的功能来实现多态。
JavaScript 中的使用原型链实现了继承功能,原型链原理如下
- 构造函数创建:
- 当你创建一个构造函数时,例如
function Person(name) { this.name = name; }
,JavaScript 会自动为Person
函数创建一个prototype
属性,Person.prototype
指向一个对象。 - 默认情况下,
Person.prototype
对象只有一个constructor
属性,指向Person
函数本身。
- 当你创建一个构造函数时,例如
- 设置原型属性/方法:
- 你可以向
Person.prototype
对象添加属性和方法,例如Person.prototype.sayHello = function() { console.log('Hello, ' + this.name); }
。 - 这些属性和方法会被所有通过
Person
构造函数创建的实例所继承。
- 你可以向
- 创建实例:
- 当你使用
new Person('Alice')
创建一个实例时,会发生以下事情:- 创建一个新的空对象。
- 将新对象的
__proto__
属性指向Person.prototype
。 - 以新对象为上下文(
this
)调用Person
构造函数,执行构造函数中的代码(例如this.name = name
)。 - 如果构造函数没有显式返回一个对象,则返回新创建的对象。
- 当你使用
- 属性查找:
- 当你访问
alice.sayHello()
时,JavaScript 引擎首先检查alice
对象自身是否有sayHello
属性。 - 如果没有,引擎会查找
alice.__proto__
,也就是Person.prototype
,看它是否有sayHello
属性。 - 如果找到了,就调用该方法。如果
Person.prototype
也没有sayHello
属性,引擎会继续向上查找Person.prototype.__proto__
,直到找到该属性或者到达原型链的顶端(Object.prototype.__proto__
为null
)
- 当你访问
- ES6 extens 说明 在 JavaScript 中,由于没有传统的类继承概念(ES6 引入的
class
实际上是语法糖,本质还是基于原型),因此原型链是实现继承的主要方式。
继承的语法
方法 | 语法示例 | 优势 | 局限性 |
---|---|---|---|
ES6 class 继承 | class Child extends Parent {} | 语法简洁,更符合面向对象编程的习惯,支持 super 调用父类方法 | 本质上仍然是原型继承的语法糖,需注意浏览器兼容性 |
es5 继承 | Child.prototype = Object.create(Parent.prototype); Child.prototype.constructor = Child; Parent.call(this, name); | 结合了原型链继承和构造函数继承的优点,既能继承实例属性,又能继承原型属性 | 父类构造函数会被调用两次,产生不必要的性能开销 |
虽然 JS 可以用 extends class
模式实现继承,但这个机制的本质还是基于对象的原型链,通过运行时查找确认对象的属性和方法。传统 OOP 语言的继承,比如 C++、JAVA 的继承特性是编译时确定的而不是运行时,而 JS 的继承是运行时动态绑定的。所以你也可以只通过动态的设置原型链来实现继承。而不需要拘泥于 class 的继承模式。
示例代码
- ES5 继承实现
- ES6 继承实现
- 只基于对象实现继承
function Parent (name) {
this.name = name
}
// 父类的实例方法
Parent.prototype.greet = function () {
console.log('Hello from Parent, my name is ' + this.name)
}
// 父类的静态方法
Parent.staticMethod = function () {
console.log('This is a static method in Parent')
}
const Child = (function () {
Object.setPrototypeOf(Child, Parent) // 设置子类的原型链
function Child (name, age) {
// 调用父类的构造函数
Parent.call(this, name)
this.age = age
}
return Child
})()
// 子类继承父类的原型方法
Child.prototype = Object.create(Parent.prototype)
Child.prototype.constructor = Child
// 子类重写父类的实例方法
Child.prototype.greet = function () {
Parent.prototype.greet.call(this) // 调用父类的实例方法
console.log('Hello from Child, I am ' + this.age + ' years old')
}
// 子类重写父类的静态方法
Child.staticMethod = function () {
Parent.staticMethod.call(this) // 调用父类的静态方法
console.log('This is a static method in Child')
}
// 实例化子类
const child = new Child('Alice', 10)
child.greet()
// 调用子类重写的静态方法
Child.staticMethod()
// 演示子类继承父类的静态方法
console.log('\n从子类调用父类的静态方法(未重写的情况下):')
delete Child.staticMethod // 删除子类重写的静态方法
Child.staticMethod() // 这将调用父类的静态方法
class Parent {
constructor (name) {
this.name = name
}
// 父类的实例方法
greet () {
console.log(`Hello from Parent, my name is ${this.name}`)
}
// 父类的静态方法
static staticMethod () {
console.log('This is a static method in Parent')
}
}
class Child extends Parent {
constructor (name, age) {
super(name) // 调用父类的构造函数
this.age = age
}
// 子类重写父类的实例方法
greet () {
super.greet() // 调用父类的实例方法
console.log(`Hello from Child, I am ${this.age} years old`)
}
// 子类重写父类的静态方法
static staticMethod () {
super.staticMethod() // 调用父类的静态方法
console.log('This is a static method in Child')
}
}
// 实例化子类
const child = new Child('Alice', 10)
child.greet()
// 调用子类重写的静态方法
Child.staticMethod()
// 演示子类继承父类的静态方法
console.log('\n从子类调用父类的静态方法(未重写的情况下):')
delete Child.staticMethod // 删除子类重写的静态方法
Child.staticMethod() // 这将调用父类的静态方法
// 基于原型链实现继承的示例:员工和经理
// 员工对象
const employee = {
name: 'Default Name',
position: 'Employee',
work: function () {
console.log(`${this.name} is working as a ${this.position}.`)
}
}
// 创建经理对象,继承自员工对象
const manager = Object.create(employee)
manager.position = 'Manager'
manager.manage = function () {
console.log(`${this.name} is managing the team.`)
}
// 使用示例
const alice = Object.create(manager)
alice.name = 'Alice'
alice.work() // 输出: Alice is working as a Manager.
alice.manage() // 输出: Alice is managing the team.
const bob = Object.create(employee)
bob.name = 'Bob'
bob.work() // 输出: Bob is working as a Employee.
什么是访问控制, JavaScript 中如何实现访问控制?
答案
访问控制是限制对对象内部状态和行为的访问权限,以保护数据完整性和实现信息隐藏。
ES6 之前,JavaScript 通过闭包和 Object.defineProperty,Object.defineProperties
实现访问控制。
ES6 引入了 class
语法
类型 | 描述 | 支持版本 |
---|---|---|
public | 公共属性和方法,可以被实例化对象和子类访问。 | 任何版本 |
private、# | 私有属性和方法,只能在类内部访问,不能被实例化对象或子类访问。 | ES2022+ |
protected | 受保护属性和方法,只能在类内部和子类中访问。 | Typescript |
示例代码
- class
- ES5 实现
class Person {
// ===== ES2015 (ES6) =====
static species = 'Human' // Static properties
constructor (name) {
this.name = name
Person.#count++
}
greet () { // Public methods
console.log(`Hello, my name is ${this.name}`)
}
get profile () { // Getter
return `${this.name}, age: ${this.age}`
}
set profile (value) { // Setter
[this.name, this.age] = value.split(',')
}
static getCount () { // Static methods
return this.#count
}
// ===== ES2019 =====
#privateField = 'private value' // Private fields
static #count = 0 // Static private fields
// ===== ES2020 =====
#privateMethod () { // Private methods
return `${this.name}'s private data: ${this.#privateField}`
}
// ===== ES2022 =====
name // Public class fields
age = 0
// Static initialization block
static {
console.log('Class initialization')
}
}
const person = new Person('Alice')
console.log(person.name) // Alice
'use strict'
// 通过 IIFE 和 defineProperty 实现类的访问控制模拟
const Person = (function () {
// 使用闭包实现私有静态变量
let _count = 0
// 创建私有数据存储
const _privateInstances = []
const _privateValues = []
// 查找或创建私有数据索引
function _getPrivateIndex (instance) {
let index = _privateInstances.indexOf(instance)
if (index === -1) {
index = _privateInstances.length
_privateInstances.push(instance)
_privateValues.push({})
}
return index
}
// 获取私有数据
function _getPrivateData (instance) {
return _privateValues[_getPrivateIndex(instance)]
}
// 构造函数(替代类声明)
function Person (name) {
// 实例属性初始化
this.name = name
this.age = 0 // 公共类字段转换为实例属性
// 设置私有实例属性
const privateData = _getPrivateData(this)
privateData.privateField = 'private value'
// 增加静态计数器
_count++
}
// 将公共实例方法添加到原型
Person.prototype.greet = function () {
console.log('Hello, my name is ' + this.name)
}
// 获取私有数据的辅助方法
Person.prototype._getPrivateField = function () {
return _getPrivateData(this).privateField
}
// 私有方法实现为闭包中的函数
function _privateMethod (instance) {
return instance.name + "'s private data: " + _getPrivateData(instance).privateField
}
// 提供访问私有方法的公开接口(可选)
Person.prototype._accessPrivateMethod = function () {
return _privateMethod(this)
}
// 使用Object.defineProperty实现getter和setter
Object.defineProperty(Person.prototype, 'profile', {
get: function () {
return this.name + ', age: ' + this.age
},
set: function (value) {
const parts = value.split(',')
this.name = parts[0]
this.age = parts[1]
},
enumerable: true,
configurable: true
})
// 在构造函数上定义静态属性和方法
Person.species = 'Human'
Person.getCount = function () {
return _count
}
// 静态初始化块转换为即时执行代码
console.log('Class initialization')
return Person
})()
// 使用示例
const person = new Person('Alice')
console.log(person.name) // Alice
console.log(Person.getCount()) // 1
什么是多态?JS 实现多态有哪些方式?
答案
多态是指不同类的对象可以通过同一接口进行交互,具体的实现方式取决于对象的实际类型。多态使得代码更具灵活性和可扩展性。多态的核心思想就是一个接口,多种实现
实现方式 | 示例说明 |
---|---|
方法重写(原型/类) | 子类覆盖父类同名方法 |
Duck Typing | 对象只要有对应方法即可使用 |
示例
- 方法重写
- Duck Typing
// 父类
class Animal {
speak () {
return 'Some sound'
}
}
// 子类重写父类方法
class Dog extends Animal {
speak () {
return 'Woof!'
}
}
class Cat extends Animal {
speak () {
return 'Meow!'
}
}
// 多态行为
const animals = [new Animal(), new Dog(), new Cat()]
animals.forEach(animal => {
console.log(animal.speak())
})
// 不同对象,没有继承关系,但都实现了相同的方法接口
const dog = {
name: 'Dog',
speak () {
return `${this.name} says: Woof!`
}
}
const cat = {
name: 'Cat',
speak () {
return `${this.name} says: Meow!`
}
}
const duck = {
name: 'Duck',
speak () {
return `${this.name} says: Quack!`
}
}
// 多态行为 - 相同的函数可以处理不同的对象
function makeSpeak (animal) {
console.log(animal.speak())
}
makeSpeak(dog) // Dog says: Woof!
makeSpeak(cat) // Cat says: Meow!
makeSpeak(duck) // Duck says: Quack!
什么是组合?JS 实现组合有哪些方式?
答案
组合是一种面向对象编程的设计模式,它通过将不同的对象实例组合在一起,形成更复杂的对象,从而实现功能的复用和扩展。组合强调的是对象之间的“有一个 (has-a)”关系,而不是继承的“是一个 (is-a)”关系。组合有如下优点:
- 高灵活性: 可以通过组合不同的对象来动态地改变一个对象的行为,更容易适应需求变化。
- 低耦合: 组合的对象之间相互独立,修改一个对象通常不会影响到其他对象,降低了系统的耦合度。
- 高复用性: 可以将小的、功能单一的对象在不同的上下文中进行组合,提高代码的复用率。
- 避免继承的局限性: 避免了继承可能带来的“脆弱的基类问题”和“多重继承的复杂性”。
- 更清晰的对象关系: “有一个”的关系比“是一个”的关系在某些场景下更符合现实世界的模型,使得对象之间的关系更加清晰。
方法 | 优势 | 局限性 |
---|---|---|
引用/委托 | 实现了真正的“有一个”关系,对象之间耦合度最低,职责分离清晰,易于测试和维护。被引用的对象可以独立演化。 | 可能需要编写更多的委托代码。 |
工厂/组合函数 | 灵活地创建具有特定行为的对象,行为以函数形式组织,易于复用和组合。可以更好地管理状态和行为之间的关系。 | 创建的对象不是特定类的实例,可能在类型检查和 instanceof 操作上有所不同。如果组合的行为依赖于共享的内部状态,需要小心处理。 |
类混入 (原型) | 可以将行为添加到现有类的所有实例中,实现代码复用。对于希望在类层面共享行为的场景比较适用。 | 可能导致命名冲突,混入的行为可能与类自身的方法产生意外的相互影响。如果混入过多,可能会使原型链变得复杂,降低可读性。 |
对象混入 (直接扩展) | 简单直接,易于理解和使用,适用于快速组合现有对象。 | 仅为浅拷贝,如果源对象包含引用类型的值,修改后会互相影响。如果混入的属性名冲突,后面的会覆盖前面的。对于需要创建多个具有相同组合行为的对象,代码复用性不高。 |
示例
- 引用
- 工厂组合
- 类混入
- 对象混入
// 定义一个负责日志记录的类
class Logger {
log (message) {
console.log(`[LOG]: ${message}`)
}
}
// 定义一个用户服务类,它依赖于 Logger 类来记录日志
class UserService {
constructor () {
this.logger = new Logger() // UserService "有一个" Logger
}
createUser (username) {
console.log(`Creating user: ${username}`)
this.logger.log(`User "${username}" created successfully.`) // 将日志记录的职责委托给 Logger 实例
return { id: Math.random(), username }
}
}
const userService = new UserService()
const newUser = userService.createUser('Bob')
console.log(newUser)
// 定义行走行为的函数
const canWalk = (state) => ({
walk: () => console.log(`${state.name} is walking at speed ${state.speed}.`)
})
// 定义跑步行为的函数
const canRun = (state) => ({
run: () => console.log(`${state.name} is running fast!`)
})
// 创建一个具有行走和跑步能力的生物的工厂函数
const createWalkerRunner = (name, speed) => {
const state = { name, speed }
return { ...state, ...canWalk(state), ...canRun(state) }
}
const cheetah = createWalkerRunner('Cheetah', 120)
cheetah.walk() // 输出: Cheetah is walking at speed 120.
cheetah.run() // 输出: Cheetah is running fast!
// 定义一个可以充电的混入对象
const canCharge = {
charge () {
console.log(`${this.model} is charging.`)
this.isCharging = true
},
stopCharge () {
console.log(`${this.model} stopped charging.`)
this.isCharging = false
}
}
class ElectronicDevice {
constructor (model) {
this.model = model
this.isCharging = false
}
}
// 将 canCharge 的属性和方法混入到 ElectronicDevice 的原型中
Object.assign(ElectronicDevice.prototype, canCharge)
const phone = new ElectronicDevice('iPhone')
phone.charge() // 输出: iPhone is charging.
console.log(phone.isCharging) // 输出: true
phone.stopCharge() // 输出: iPhone stopped charging.
const engineBehavior = {
startEngine () {
console.log('Engine started.')
this.engineOn = true
},
stopEngine () {
console.log('Engine stopped.')
this.engineOn = false
}
}
const carProperties = {
model: 'Sedan',
color: 'Red',
engineOn: false
}
// 将 engineBehavior 混入到 carProperties 对象中
const myCar = Object.assign({}, carProperties, engineBehavior)
console.log(myCar.model) // 输出: Sedan
myCar.startEngine() // 输出: Engine started.
console.log(myCar.engineOn) // 输出: true
在有些面向对象的语言中还支持类似 Trait 的概念,Trait 是一种轻量级的类,可以被多个类共享。Trait 允许你将行为分离到独立的单元中,从而实现更好的代码复用和组合。示例如下
- rust
- php
trait Walkable {
fn walk(&self);
}
trait Swimmable {
fn swim(&self);
}
struct Dog {
name: String,
}
impl Walkable for Dog {
fn walk(&self) {
println!("{} is walking.", self.name);
}
}
impl Swimmable for Dog {
fn swim(&self) {
println!("{} is swimming.", self.name);
}
}
fn main() {
let dog = Dog { name: String::from("Buddy") };
dog.walk(); // 输出: Buddy is walking.
dog.swim(); // 输出: Buddy is swimming.
}
trait Walkable {
public function walk() {
echo "I am walking.\n";
}
}
trait Swimmable {
public function swim() {
echo "I am swimming.\n";
}
}
class Animal {
public $name;
public function __construct($name) {
$this->name = $name;
}
}
class Dog extends Animal {
use Walkable, Swimmable;
}
$dog = new Dog("Buddy");
$dog->walk(); // 输出: I am walking.
$dog->swim(); // 输出: I am swimming.
什么是 SOLID 原则?
答案
SOLID 是面向对象设计的五项基本原则,它们分别为:
原则 | 描述 | 实际应用 |
---|---|---|
S (Single Responsibility Principle) | 每个模块/类只应负责一种功能。 | 使得每个组件或服务职责单一,便于维护和扩展。 |
O (Open/Closed Principle) | 模块对扩展开放,对修改封闭。 | 通过继承、接口或组合来扩展功能,而不修改已有代码。 |
L (Liskov Substitution Principle) | 子类对象应能够替换父类对象而不破坏系统行为。 | 在组件库中确保继承关系合理,保证组件可替换性。 |
I (Interface Segregation Principle) | 不应强迫客户端依赖它们不使用的方法。 | 在前端开发中设计 API 或服务接口时,尽量精简,避免臃肿。 |
D (Dependency Inversion Principle) | 高层模块不应依赖低层模块,两者都应该依赖于抽象。 | 通过依赖注入、接口设计实现模块独立性,提升测试与复用能力。 |
- 单一职责原则
- 开闭原则
- 里氏替换原则
- 接口隔离原则
- 依赖倒置原则
// 单一职责原则要求每个类或模块只负责一种功能。
// 拆分用户认证与日志记录逻辑
class AuthService {
login (username, password) {
// 验证用户信息,返回认证结果
return username === 'admin' && password === '1234'
}
}
class Logger {
log (message) {
console.log('LOG:', message)
}
}
// 前端调用时分别使用不同的服务
const authService = new AuthService()
const logger = new Logger()
if (authService.login('admin', '1234')) {
logger.log('User logged in successfully.')
}
/**
* 开放/封闭原则要求软件实体(如类、模块、函数)能够扩展,而不需要修改已存在的代码,以避免引入新的错误。
* 通过使用继承或组合模式扩展现有类的功能,而非直接修改。
*
*/
// 基础通知类
class Notifier {
send (message) {
console.log(`Sending: ${message}`)
}
}
// 扩展:通过继承增加新的发送方式
class EmailNotifier extends Notifier {
send (message) {
// 复用基础逻辑,并扩展邮件发送的细节
super.send(message)
console.log(`Sending email with: ${message}`)
}
}
const notifier = new EmailNotifier()
notifier.send('Hello World!')
/**
* 里氏替换原则在前端开发中的一个典型用例是组件的替换。
* 假设我们有一个基础按钮组件和它的子类组件,子类组件可以替换基础组件而不影响程序的行为。
*/
// 基础按钮组件
class Button {
render () {
console.log('Rendering a basic button')
}
}
// 子类:提交按钮
class SubmitButton extends Button {
render () {
console.log('Rendering a submit button')
}
}
// 子类:取消按钮
class CancelButton extends Button {
render () {
console.log('Rendering a cancel button')
}
}
// 使用里氏替换原则
function renderButton (button) {
button.render()
}
const basicButton = new Button()
const submitButton = new SubmitButton()
const cancelButton = new CancelButton()
// 替换基础按钮
renderButton(basicButton) // 输出: Rendering a basic button
renderButton(submitButton) // 输出: Rendering a submit button
renderButton(cancelButton) // 输出: Rendering a cancel button
/**
* 接口隔离原则要求客户端不应依赖它们不使用的接口,避免构建臃肿的接口。
* 在模块或服务设计时,应将接口拆分为多个小接口,各自关注单一功能,客户端仅依赖自身需要的那部分接口。
*
* 通过将 Reader 和 Writer 分离为独立的接口,客户端 FileManager 仅依赖它需要的功能。
* 这样可以避免臃肿的接口设计,增强代码的灵活性和可维护性。
*
*/
// 定义小接口:读操作接口和写操作接口
class Reader {
read () {
// 模拟读取数据
console.log('Reading data...')
}
}
class Writer {
write (data) {
// 模拟写入数据
console.log(`Writing data: ${data}`)
}
}
// 客户端可以按需组合
class FileManager extends Reader {
constructor (writer) {
super()
this.writer = writer
}
save (data) {
this.writer.write(data)
}
}
// 示例使用
const writer = new Writer()
const fileManager = new FileManager(writer)
// 只需要读取数据
fileManager.read()
// 只需要写入数据
fileManager.save('Sample Data')
/**
* 依赖倒置原则要求高层模块不依赖于低层模块,而应依赖于抽象,这样可以降低模块之间的耦合度。
* 通过抽象接口和依赖注入(Dependency Injection)使高层模块在运行时获得所需功能,而非硬编码具体实现。
* 前端模块化开发中常采用依赖注入框架或设计模式,使各模块独立开发和测试,便于维护和扩展。
*/
// 抽象接口
class Service {
execute () {
throw new Error('Method not implemented.')
}
}
// 具体实现
class ApiService extends Service {
execute () {
console.log('Calling API...')
}
}
// 高层模块通过依赖注入使用抽象接口
class Controller {
constructor (service) {
this.service = service
}
handleRequest () {
this.service.execute()
}
}
const serviceInstance = new ApiService()
const controller = new Controller(serviceInstance)
controller.handleRequest() // 输出:Calling API...
什么是依赖注入?它和依赖倒置原则原则区别是什么,在实际开发中如何使用?
答案
对比点 | 依赖注入(DI) | 依赖倒置原则(DIP) |
---|---|---|
定义 | 一种设计模式,外部注入依赖 | 一种设计原则,高层依赖抽象,不依赖具体 |
是否抽象必须 | 可以注入具体类或接口 | 强调依赖抽象(接口) |
目的 | 降低耦合,提升可测试性 | 构建稳定系统架构,使高层和低层依赖同一接口 |
举例 | constructor(logger) 外部注入 | constructor(ILogger) 只依赖抽象 |
关系 | DI 是实现 DIP 的一种手段 | DIP 是一种架构设计思维指导原则 |
前端典型使用场景为
场景 | 实现说明 Angular | 内建依赖注入系统(如 @Injectable()),控制反转容器自动注入服务 Vue 3 | provide/inject 实现组件树中的依赖注入 React | 使用 Context 实现依赖注入(如主题、store、i18n 等)
class Container {
constructor () {
this.services = new Map()
}
register (name, implementation) {
this.services.set(name, implementation)
}
resolve (name) {
const Service = this.services.get(name)
return new Service()
}
}
// 使用
class Logger {
log (msg) {
console.log(msg)
}
}
class UserService {
constructor (logger) {
this.logger = logger
}
static inject = ['logger']
createUser (name) {
this.logger.log(`创建用户:${name}`)
}
}
// 模拟注入
const container = new Container()
container.register('logger', Logger)
// 自动注入依赖
function inject (cls) {
const deps = cls.inject.map(dep => container.resolve(dep))
return new cls(...deps)
}
const userService = inject(UserService)
userService.createUser('Alice')
什么是设计模式?为什么在开发中使用设计模式?
答案
设计模式(Design Pattern) 是一套经过总结、可复用、经过验证的编程经验,它描述了在特定情境下,如何优雅地解决某类常见软件设计问题的通用方案。 设计模式不是代码模板,而是一种设计思想和结构方案,用于提升系统的可维护性、可扩展性和灵活性。
设计模式在开发中的作用主要包括:
作用点 | 说明 |
---|---|
提高复用性 | 提供通用的解决方案,避免重复造轮子 |
增强可读性 | 使用大家熟悉的模式能让代码更容易被团队理解 |
提升可维护性 | 模块职责清晰,结构可控,便于维护和扩展 |
应对变化 | 更好地应对需求变更,实现“对扩展开放,对修改封闭” |
降低耦合 | 通过抽象和封装减少模块之间的依赖,提高系统稳定性 |
根据设计模式:可复用面向对象软件的基础分为三大类:创建型(Creational)、结构型(Structural)、行为型(Behavioral)。 每类下可再按对象模式或类模式划分:
类型 | 模式名 | 类/对象 | 前端解释 | 前端典型场景 |
---|---|---|---|---|
创建型 | 单例模式(Singleton) | 对象 | 全局唯一实例 | Vuex、Redux Store |
工厂方法(Factory Method) | 类 | 统一创建对象的接口 | 动态组件生成 | |
抽象工厂(Abstract Factory) | 对象 | 创建一组相关对象 | UI 组件库中统一主题生成 | |
建造者模式(Builder) | 对象 | 分步骤构建复杂对象 | 表单构造器 | |
原型模式(Prototype) | 对象 | 克隆已有对象 | DOM 节点或状态深拷贝 | |
结构型 | 适配器模式(Adapter) | 对象 | 接口转换兼容旧系统 | axios 请求结果适配旧结构 |
装饰器模式(Decorator) | 对象 | 动态扩展对象功能 | React HOC、高阶函数增强 | |
外观模式(Facade) | 对象 | 提供统一接口屏蔽复杂实现 | API 接口统一封装 | |
组合模式(Composite) | 对象 | 树形结构统一处理 | Vue、React 组件树 | |
代理模式(Proxy) | 对象 | 控制访问权限或增强行为 | 虚拟滚动、懒加载、权限组件 | |
桥接模式(Bridge) | 对象 | 分离抽象和实现 | 主题系统与业务逻辑解耦 | |
享元模式(Flyweight) | 对象 | 共享对象减少内存 | 虚拟列表复用 item DOM | |
行为型 | 观察者模式(Observer) | 对象 | 订阅-发布模型 | Vue 响应式系统、事件监听 |
策略模式(Strategy) | 对象 | 替代 if-else 逻辑 | 表单验证策略切换 | |
状态模式(State) | 对象 | 状态切换影响行为 | 登录状态管理、流程控制 | |
命令模式(Command) | 对象 | 封装请求为对象 | 撤销、重做功能 | |
责任链模式(Chain of Responsibility) | 对象 | 多个处理器链式处理请求 | 中间件体系,如 Redux middleware | |
模板方法(Template Method) | 类 | 定义算法骨架,子类实现 | UI 组件渲染流程扩展 | |
中介者模式(Mediator) | 对象 | 控制组件间通信 | 前端事件总线、组件解耦 | |
迭代器模式(Iterator) | 对象 | 顺序访问集合元素 | for-of、生成器遍历数据源 | |
解释器模式(Interpreter) | 类 | 解释某种语言表达式 | 模板引擎、表达式计算器 | |
访问者模式(Visitor) | 对象 | 对结构中元素进行操作 | AST 转换器,如 Babel 插件 | |
备忘录模式(Memento) | 对象 | 保存对象状态以便恢复 | 表单撤销、可视化编辑器历史记录 |
设计模式的本质其实是在 OOP 编程范式下,提供了一系列的可复用的设计元语。但是模式本身并不拘泥于 OOP 也不仅局限于上面的分类。 重点是在日常开发中能够识别这些典型的模式,同时基于上面的思想沉淀其他的模式来实现复用。
解释以下创建型模式单例模式、工厂模式、建造者模式、抽象工厂模式的区别,说明实际开发中如何使用?
答案
模式之间的区别
模式名称 | 定义 | 使用场景 | 与其他模式的区别 |
---|---|---|---|
单例模式 | 保证一个类只有一个实例,并提供全局访问点 | 全局状态管理、缓存、唯一服务实例(如 EventBus) | 强调**“唯一性”和全局访问**,不关注对象的创建过程复杂度 |
工厂方法模式 | 定义一个用于创建对象的接口,由子类决定要实例化的类 | 创建过程相对简单,但需要根据类型动态创建不同对象,如图标组件、表单字段等 | 强调延迟到子类决定创建哪种对象,相比抽象工厂只创建一个对象 |
建造者模式 | 将一个复杂对象的构建与表示分离,同样构建过程可以创建不同表示 | 多步骤构建复杂对象(如 UI 表单、复杂配置),构建流程固定但参数可定制 | 适用于构建过程复杂的对象,强调按步骤组装;而工厂注重类型选择 |
抽象工厂模式 | 提供一个接口用于创建一系列相关或相互依赖的对象,无需指定具体类 | UI 主题风格切换(按钮、输入框风格统一)、跨平台组件创建等场景 | 能创建一组相关对象,比工厂方法更宏观、更全面(如创建整个 UI 套件,而不是一个组件) |
- 单例模式
- 工厂方法模式
- 建造者模式
- 抽象工厂模式
/**
* 典型场景:Vuex / Redux Store、全局 EventBus、Modal 管理器**
*
*/
// EventBus 单例
class EventBus {
constructor () {
if (!EventBus.instance) {
this.listeners = {}
EventBus.instance = this
}
return EventBus.instance
}
on (event, cb) {
(this.listeners[event] ||= []).push(cb)
}
emit (event, data) {
(this.listeners[event] || []).forEach(cb => cb(data))
}
}
// 使用
const bus = new EventBus()
bus.on('login', data => console.log('User logged in', data))
bus.emit('login', { user: 'Alice' })
/**
* 典型场景:根据类型动态创建不同组件(如表单字段组件)
*/
// 表单控件工厂
class FieldFactory {
static createField (type) {
switch (type) {
case 'text':
return new TextField()
case 'select':
return new SelectField()
default:
throw new Error('Unsupported field type')
}
}
}
class TextField {
render () {
return '<input type="text" />'
}
}
class SelectField {
render () {
return '<select><option>Option</option></select>'
}
}
// 使用
const field = FieldFactory.createField('select')
console.log(field.render())
/**
* 典型场景:动态构建复杂 UI 表单配置、图表配置、表格列配置
*/
// 构建器模式构造表格列配置
class TableBuilder {
constructor () {
this.columns = []
}
addColumn (title, key) {
this.columns.push({ title, dataIndex: key })
return this
}
addSortableColumn (title, key) {
this.columns.push({ title, dataIndex: key, sortable: true })
return this
}
build () {
return this.columns
}
}
// 使用
const columns = new TableBuilder()
.addColumn('姓名', 'name')
.addSortableColumn('年龄', 'age')
.build()
console.log(columns)
/**
* 典型场景:根据主题生成一组相关的 UI 组件(按钮、输入框、弹窗)
*/
class DarkButton {
render () {
console.log('渲染深色主题按钮')
}
}
class DarkInput {
render () {
console.log('渲染深色主题输入框')
}
}
class LightButton {
render () {
console.log('渲染浅色主题按钮')
}
}
class LightInput {
render () {
console.log('渲染浅色主题输入框')
}
}
// 抽象工厂
class UIAbstractFactory {
createButton () {}
createInput () {}
}
// 深色主题工厂
class DarkThemeFactory extends UIAbstractFactory {
createButton () {
return new DarkButton()
}
createInput () {
return new DarkInput()
}
}
// 浅色主题工厂
class LightThemeFactory extends UIAbstractFactory {
createButton () {
return new LightButton()
}
createInput () {
return new LightInput()
}
}
// 使用
function renderTheme (factory) {
const btn = factory.createButton()
const input = factory.createInput()
console.log(btn.render())
console.log(input.render())
}
console.log('Rendering Dark Theme:')
const darkFactory = new DarkThemeFactory()
renderTheme(darkFactory)
console.log('Rendering Light Theme:')
const lightFactory = new LightThemeFactory()
renderTheme(lightFactory)
解释以下结构型模式适配器模式、装饰器模式、外观模式,说明实际开发中如何使用?
答案
模式名称 | 定义 | 使用场景 | 与其他模式的区别 |
---|---|---|---|
适配器模式 | 将一个接口转换成客户端期望的另一个接口,解决接口不兼容问题 | 第三方库适配、老代码兼容新接口、统一数据格式(如后端返回数据与组件 props 不一致) | 关注接口转换,原有结构不变,强调“兼容” |
装饰器模式 | 动态地给对象添加额外的功能,而不影响原对象结构 | 高阶组件(HOC)、Vue/React 自定义指令、增强函数功能 | 强调在不修改原对象的前提下增强功能,适用于可选性增强逻辑 |
外观模式 | 提供一个统一接口,隐藏系统内部复杂性,简化调用 | 对外暴露简化 API(如封装复杂图表、动画库等),页面初始化流程封装 | 关注简化复杂系统的使用接口,提供一站式访问门面 |
- 适配器模式
- 装饰器模式
- 外观模式
/**
* 典型场景:将后端数据结构适配成前端组件所需格式
* 统一后端返回值,适配不同组件要求,避免直接修改第三方接口或组件源代码。
*/
// 原始数据(来自后端)
const backendUser = {
uid: 1001,
username: 'Alice',
gender: 1
}
// 前端组件需要的数据结构
// { id: number, name: string, genderText: string }
function userAdapter (rawUser) {
return {
id: rawUser.uid,
name: rawUser.username,
genderText: rawUser.gender === 1 ? '女' : '男'
}
}
// 使用
const adaptedUser = userAdapter(backendUser)
console.log(adaptedUser) // { id: 1001, name: 'Alice', genderText: '女' }
/**
*
* 典型场景:高阶组件 HOC / 函数增强器 / 指令系统
* - React 中 HOC:`withAuth(Component)`
* - Vue 中自定义指令:`v-permission`
* - 函数扩展日志、缓存、节流防抖等
*/
// 简易日志装饰器
function withLog (fn) {
return function (...args) {
console.log('调用参数:', args)
const result = fn(...args)
console.log('返回结果:', result)
return result
}
}
function add (a, b) {
return a + b
}
// 使用装饰器增强
const loggedAdd = withLog(add)
loggedAdd(1, 2) // 控制台打印调用信息
/**
* 典型场景:统一封装动画库/图表库/页面初始化逻辑
* - 页面初始化入口统一封装(initApp)
* - 封装复杂 API,降低组件间耦合度
* - 封装第三方库统一入口,如 ECharts、D3、Mapbox 调用
*/
// 外观函数封装复杂动画库的调用
function showModalAnimation (element) {
fadeIn(element)
scaleUp(element)
lockScroll()
}
function fadeIn (el) { /* 复杂动画逻辑 */ }
function scaleUp (el) { /* scale animation */ }
function lockScroll () { document.body.style.overflow = 'hidden' }
// 使用
showModalAnimation(document.querySelector('#modal'))
解释以下行为型模式观察者模式、策略模式、命令模式、状态模式,说明实际开发中如何使用?
模式名称 | 定义 | 使用场景 | 区别 |
---|---|---|---|
观察者模式 | 对象间的一对多依赖关系,当一个对象状态变化时,所有依赖它的对象都会收到通知并自动更新。 | 事件处理系统、发布订阅系统、全局状态管理(如 Redux、Vuex)。 | 关注对象状态变化时的通知机制,强调数据与视图的同步。 |
策略模式 | 定义一系列算法,将它们封装起来,并使它们可以互相替换。 | 多算法切换(如排序策略)、多支付方式处理、表单验证策略。 | 关注算法的动态替换,客户端决定使用哪种策略,与具体实现解耦。 |
命令模式 | 将请求封装成对象,允许参数化客户端、请求排队或记录日志,支持撤销操作。 | 菜单交互、事务处理、宏命令、撤销/重做功能(如编辑器)。 | 关注请求的封装与扩展,将调用者与接收者解耦,支持历史操作管理。 |
状态模式 | 允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。 | 状态机管理(如游戏角色状态)、UI 组件状态驱动(如按钮禁用/加载中/成功)。 | 关注对象内部状态的行为变化,状态转换逻辑集中在状态类中。 |
答案
- 观察者模式
- 策略模式
- 命令模式
- 状态模式
/**
* 观察者模式
*/
class EventEmitter {
constructor () {
this.events = {}
}
on (event, listener) {
if (!this.events[event]) {
this.events[event] = []
}
this.events[event].push(listener)
}
off (event, listener) {
if (!this.events[event]) return
this.events[event] = this.events[event].filter(l => l !== listener)
}
once (event, listener) {
const onceWrapper = (...args) => {
listener(...args)
this.off(event, onceWrapper)
}
this.on(event, onceWrapper)
}
emit (event, ...args) {
if (this.events[event]) {
this.events[event].forEach(listener => listener(...args))
}
}
}
const eventEmitter = new EventEmitter()
eventEmitter.on('event1', (data) => {
console.log(`Event 1 triggered with data: ${data}`)
})
eventEmitter.once('event2', (data) => {
console.log(`Event 2 triggered with data: ${data}`)
})
eventEmitter.emit('event1', 'Hello World!')
eventEmitter.emit('event1', 'Hello World!')
eventEmitter.emit('event2', 'Hello World!')
eventEmitter.emit('event2', 'Hello Again!') // Won't trigger
/**
* 策略模式
* 表单验证策略动态切换。
*/
// 定义策略对象
const validationStrategies = {
email: (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),
passwordStrength: (value) => /^(?=.*[A-Za-z])(?=.*\d).{8,}$/.test(value)
}
// 使用策略验证表单
function validateForm (strategy, value) {
return validationStrategies[strategy](value)
}
// 示例:验证邮箱
const isEmailValid = validateForm('email', 'user@example.com')
console.log(`Email valid: ${isEmailValid}`) // true
/**
* 命令模式
*
* 实现编辑器的撤销/重做功能。
* 将操作封装为对象,支持撤销、重做和日志记录。
*/
// 命令接口
class Command {
execute () {}
undo () {}
}
// 具体命令:添加文本
class AddTextCommand extends Command {
constructor (editor, text) {
super()
this.editor = editor
this.text = text
}
execute () {
this.editor.addText(this.text)
}
undo () {
this.editor.deleteText(this.text.length)
}
}
// 具体命令:清空文本
class ClearTextCommand extends Command {
constructor (editor) {
super()
this.editor = editor
this.previousText = ''
}
execute () {
this.previousText = this.editor.getText()
this.editor.clearText()
}
undo () {
this.editor.setText(this.previousText)
}
}
// 编辑器模拟
class Editor {
constructor () {
this.content = ''
}
addText (text) {
this.content += text
}
deleteText (length) {
this.content = this.content.slice(0, -length)
}
clearText () {
this.content = ''
}
setText (text) {
this.content = text
}
getText () {
return this.content
}
}
// 使用命令
const commandHistory = []
const editor = new Editor()
function executeCommand (command) {
command.execute()
commandHistory.push(command) // 记录命令
}
// 撤销操作
function undo () {
const lastCommand = commandHistory.pop()
if (lastCommand) lastCommand.undo()
}
// 示例操作
const addCommand = new AddTextCommand(editor, 'Hello, World!')
executeCommand(addCommand)
console.log(editor.getText()) // 输出: Hello, World!
const clearCommand = new ClearTextCommand(editor)
executeCommand(clearCommand)
console.log(editor.getText()) // 输出: (空字符串)
undo()
console.log(editor.getText()) // 输出: Hello, World!
undo()
console.log(editor.getText()) // 输出: (空字符串)
/**
* 状态模式
*
* 电商购物车状态管理(待支付、已支付、已发货)。
* 将状态转换逻辑集中管理,避免条件分支污染业务代码。
*/
// 状态接口
class CartState {
checkout (cart) {
console.log('当前操作不可用')
}
pay (cart) {
console.log('当前操作不可用')
}
}
// 具体状态:待支付
class PendingPaymentState extends CartState {
pay (cart) {
cart.setState(new PaidState())
console.log('支付成功,订单已确认')
}
}
// 具体状态:已支付
class PaidState extends CartState {
checkout (cart) {
cart.setState(new ShippedState())
console.log('订单已发货')
}
}
// 具体状态:已发货
class ShippedState extends CartState {
checkout (cart) {
console.log('订单已发货,无法重复操作')
}
pay (cart) {
console.log('订单已发货,无法支付')
}
}
// 购物车类
class ShoppingCart {
constructor () {
this.state = new PendingPaymentState()
}
setState (state) {
this.state = state
}
pay () {
this.state.pay(this)
}
checkout () {
this.state.checkout(this)
}
}
// 使用状态模式
const cart = new ShoppingCart()
// 示例操作
cart.pay() // 输出:支付成功,订单已确认
cart.checkout() // 输出:订单已发货
cart.pay() // 输出:订单已发货,无法支付
cart.checkout() // 输出:订单已发货,无法重复操作
解释职责链模式,说明实际开发中如何使用?
职责链模式是一种行为型设计模式,允许将请求沿着处理链传递,直到有对象能够处理它为止。通过解耦请求的发送者和接收者,使多个对象都有机会处理请求,但具体由哪个对象处理在运行时动态决定。
典型使用场景如下
答案
- 前端表单验证
- 中间件系统
- 事件冒泡机制
- 请求拦截器
/**
* 前端表单验证
*/
// 抽象处理者
class Validator {
constructor () {
this.nextValidator = null
}
setNext (validator) {
this.nextValidator = validator
return validator // 返回下一个验证器,便于链式调用
}
validate (input) {
if (this.nextValidator) {
return this.nextValidator.validate(input)
}
return true // 如果没有下一个验证器,则验证通过
}
}
// 具体处理者:必填字段验证器
class RequiredValidator extends Validator {
validate (input) {
if (!input.value) {
return { field: input.name, error: '此字段为必填项' }
}
return super.validate(input)
}
}
// 具体处理者:邮箱验证器
class EmailValidator extends Validator {
validate (input) {
if (input.type === 'email' && input.value) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
if (!emailRegex.test(input.value)) {
return { field: input.name, error: '请输入有效的电子邮件地址' }
}
}
return super.validate(input)
}
}
// 具体处理者:密码长度验证器
class PasswordLengthValidator extends Validator {
validate (input) {
if (input.type === 'password' && input.value && input.value.length < 8) {
return { field: input.name, error: '密码长度必须至少为8个字符' }
}
return super.validate(input)
}
}
// 客户端代码
function validateForm (formData) {
// 创建验证链
const requiredValidator = new RequiredValidator()
const emailValidator = new EmailValidator()
const passwordValidator = new PasswordLengthValidator()
requiredValidator
.setNext(emailValidator)
.setNext(passwordValidator)
const errors = []
// 对每一个表单字段进行验证
for (const field of formData) {
const result = requiredValidator.validate(field)
if (result !== true) {
errors.push(result)
}
}
return errors
}
// 使用示例
const formData = [
{ name: 'username', type: 'text', value: '' },
{ name: 'email', type: 'email', value: 'invalid-email' },
{ name: 'password', type: 'password', value: 'pass' }
]
const validationErrors = validateForm(formData)
console.log(validationErrors)
// 输出:
// [
// { field: 'username', error: '此字段为必填项' },
// { field: 'email', error: '请输入有效的电子邮件地址' },
// { field: 'password', error: '密码长度必须至少为8个字符' }
// ]
/**
* Express.js框架的中间件系统是职责链模式的一个典型应用:
*/
const express = require('express')
const app = express()
// 日志中间件
app.use((req, res, next) => {
console.log(`Request: ${req.method} ${req.url}`)
next() // 传递给下一个中间件
})
// 身份验证中间件
app.use((req, res, next) => {
const authHeader = req.headers.authorization
if (!authHeader) {
return res.status(401).send('Authentication required')
}
// 验证逻辑...
req.user = { id: 'user123' } // 将用户信息附加到请求对象
next()
})
// 授权中间件
app.use((req, res, next) => {
if (!req.user.hasPermission) {
return res.status(403).send('Permission denied')
}
next()
})
// 路由处理
app.get('/api/data', (req, res) => {
res.json({ data: 'Protected data' })
})
app.listen(3000)
/**
* DOM的事件冒泡机制也是职责链模式的一种应用:
*/
// HTML结构
// <div id="container">
// <div id="panel">
// <button id="button">Click Me</button>
// </div>
// </div>
// 事件处理
document.getElementById('button').addEventListener('click', function (event) {
console.log('Button clicked')
// 如果需要阻止冒泡:event.stopPropagation();
})
document.getElementById('panel').addEventListener('click', function (event) {
console.log('Panel clicked')
})
document.getElementById('container').addEventListener('click', function (event) {
console.log('Container clicked')
})
// 点击按钮时的输出:
// Button clicked
// Panel clicked
// Container clicked
/**
* 类似于Axios的拦截器系统,可以使用职责链模式处理HTTP请求和响应:
*/
class RequestHandler {
constructor () {
this.nextHandler = null
}
setNext (handler) {
this.nextHandler = handler
return handler
}
handle (request) {
if (this.nextHandler) {
return this.nextHandler.handle(request)
}
return request
}
}
// 添加认证令牌
class AuthTokenHandler extends RequestHandler {
handle (request) {
console.log('添加认证令牌')
request.headers = {
...request.headers,
Authorization: `Bearer ${localStorage.getItem('token')}`
}
return super.handle(request)
}
}
// 添加默认内容类型
class ContentTypeHandler extends RequestHandler {
handle (request) {
console.log('添加内容类型')
if (!request.headers['Content-Type']) {
request.headers = {
...request.headers,
'Content-Type': 'application/json'
}
}
return super.handle(request)
}
}
// 添加CSRF令牌
class CSRFTokenHandler extends RequestHandler {
handle (request) {
console.log('添加CSRF令牌')
if (request.method !== 'GET') {
request.headers = {
...request.headers,
'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]')?.content
}
}
return super.handle(request)
}
}
// 使用拦截器
class HttpClient {
constructor () {
// 创建请求处理链
this.requestChain = new AuthTokenHandler()
this.requestChain
.setNext(new ContentTypeHandler())
.setNext(new CSRFTokenHandler())
}
async fetch (url, options = {}) {
// 处理请求
const processedOptions = this.requestChain.handle({
...options,
headers: options.headers || {}
})
// 发送请求
try {
const response = await fetch(url, processedOptions)
return response.json()
} catch (error) {
console.error('Request failed:', error)
throw error
}
}
}
// 使用示例
const client = new HttpClient()
client.fetch('https://api.example.com/data', { method: 'POST', body: JSON.stringify({ key: 'value' }) })