简介:JavaScript 设计模式是提升代码质量的通用解决方案模板,涵盖了如单例模式、Promise以及工厂、观察者、装饰器、代理、命令、策略和适配器等模式。该课程深入探讨这些模式的实现和应用,帮助开发者编写更健壮、可维护的JavaScript代码,并提高团队协作效率。
1. 设计模式在JavaScript中的作用
在现代软件开发中,设计模式是一组经过时间检验的最佳实践,它们提供了一种针对特定问题的通用解决方案。JavaScript 作为一种灵活而强大的语言,在处理客户端和服务器端逻辑时,也能够从设计模式中获益匪浅。
1.1 设计模式的必要性
在编写 JavaScript 代码时,随着项目的扩大和复杂性的增加,代码会逐渐变得难以管理和扩展。设计模式能够帮助开发者以一致、可预测的方式解决问题,它提供了一系列的模板和准则,使得开发团队能够遵循标准实践,提高代码的可读性和可维护性。它们不仅能够简化复杂问题的解决过程,还能够加强团队成员之间的沟通效率。
1.2 设计模式在JavaScript中的体现
JavaScript 是一种多范式编程语言,它支持面向对象、命令式以及函数式编程。这意味着,设计模式可以以不同的方式在 JavaScript 中得以实现和应用。例如,由于 JavaScript 中的原型链继承,我们可以更灵活地应用工厂模式或单例模式;而其基于原型的面向对象风格,允许我们使用装饰器模式来动态地扩展对象功能。
设计模式的正确应用能够帮助 JavaScript 开发者在处理诸如模块化、事件驱动、异步操作等常见的编程任务时,实现更加优雅和高效的解决方案。下一章将深入探讨单例模式,这是一个确保全局只有一个实例并且提供全局访问点的设计模式。
2. 单例模式实现及其应用场景
2.1 单例模式的理论基础
2.1.1 单例模式的定义和特点
单例模式是一种常见的设计模式,属于创建型模式。它提供了一种创建对象的最佳方式,当一个类只有一个实例并且能够被全局访问时,这个类就被认为是单例模式。该模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
单例模式的特点包括: - 确保只有一个实例存在。 - 自行实例化该实例。 - 提供一个访问它的全局访问点。 - 该实例在系统运行期间不会被销毁。
单例模式的实现通常涉及到两个关键概念: - 私有的构造函数:防止其他对象创建该类的实例。 - 一个静态的私有变量:存储类的唯一实例。 - 一个公共的静态方法:提供全局访问点用于获取实例。
2.1.2 实现单例模式的JavaScript代码示例
下面是一个简单的JavaScript实现单例模式的示例代码:
// 单例模式的实现
const Singleton = (function() {
// 私有变量
let instance;
// 私有构造函数
function SingletonConstructor() {
// 初始化单例实例的属性
this.value = 'I am the only one!';
}
// 公有方法:用于获取实例的全局访问点
return {
getInstance: function() {
if (!instance) {
instance = new SingletonConstructor();
}
return instance;
}
};
})();
// 测试代码
const singleA = Singleton.getInstance();
const singleB = Singleton.getInstance();
console.log(singleA === singleB); // 输出:true
上述代码中, Singleton
是一个自执行的函数。这个函数内部定义了一个 SingletonConstructor
构造函数,该函数仅在第一次调用 getInstance
方法时被调用,并创建了单例的实例。之后的所有调用都返回同一个实例。这样保证了该单例模式类在整个应用中只有一个实例。
2.2 单例模式的应用场景分析
2.2.1 配置管理系统的单例实例
在配置管理系统中,可能会有许多组件需要使用同一个配置对象。这种情况下,创建一个全局的单例配置对象可以使得所有组件的配置保持一致,同时避免了多份相同配置对象的创建和管理。
class Config {
constructor() {
this.settings = {
theme: 'dark',
language: 'en',
};
}
}
// 使用单例模式获取配置对象
const myConfig = (function() {
let instance;
function createInstance() {
return new Config();
}
return {
getInstance: function() {
if (!instance) {
instance = createInstance();
}
return instance;
}
};
})();
// 现在无论何时何地,获取到的都是同一个 Config 实例
const config1 = myConfig.getInstance();
const config2 = myConfig.getInstance();
console.log(config1 === config2); // 输出:true
2.2.2 前端路由管理中的单例应用
在前端路由管理中,单例模式可以用来管理路由实例。通过单例模式确保整个应用中只有一个路由实例,从而使得路由跳转、导航等功能可以被统一管理,提高效率。
// 假设有一个简单的路由管理类
class Router {
constructor() {
// 初始化路由映射
}
// 定义路由跳转方法
}
// 实现单例模式管理路由实例
const RouterManager = (function() {
let instance;
function createRouterInstance() {
return new Router();
}
return {
getInstance: function() {
if (!instance) {
instance = createRouterInstance();
}
return instance;
}
};
})();
// 获取单例路由实例
const router = RouterManager.getInstance();
2.2.3 实践中的单例模式优化策略
单例模式虽然有用,但也需要谨慎使用,因为它可能导致全局状态,这可能会增加代码的耦合性和维护难度。在实践中,应当考虑以下优化策略:
- 将单例的状态限制在最小范围内。
- 避免在单例中存储过多的状态数据。
- 在合适的情况下考虑使用依赖注入来管理单例依赖。
- 在需要时进行单例模式和原型模式的权衡选择。
单例模式在应用中通常适用于那些需要全局访问的场景,例如全局缓存、数据库连接、日志记录器等,可以确保资源的唯一性和高效访问。
总结本章节,单例模式作为一种基本的设计模式,在JavaScript中通过模块化和闭包的特性可以很容易地实现。理解和掌握单例模式的实现方式和应用场景,有助于提高代码质量和系统设计的合理性。在下一章,我们将探索如何通过Promise机制处理JavaScript中的异步编程。
3. Promise机制和异步编程
3.1 JavaScript异步编程的基本概念
3.1.1 同步与异步代码执行的差异
在JavaScript的世界里,程序的执行遵循单线程模型。这意味着,JavaScript代码在执行时,是按顺序逐行处理的。对于同步代码,每一行代码都必须等待前一行代码执行完毕后才能开始执行。例如:
function printNumbersSync() {
console.log(1);
console.log(2);
console.log(3);
}
printNumbersSync();
// 输出结果将为:
// 1
// 2
// 3
以上代码中, console.log
语句同步执行,按照顺序打印数字 1, 2, 3。
然而,在处理网络请求、文件读写或其他耗时操作时,同步执行会阻塞代码的执行,导致程序在等待期间无响应,用户体验极差。这就是异步编程的用武之地。异步操作允许程序在等待耗时操作结果的同时继续执行其他任务。
3.1.2 回调函数的局限性
为了实现非阻塞的异步操作,JavaScript 提供了回调函数。回调函数是一种在异步操作完成后被调用的函数。使用回调函数可以避免程序在异步操作时停止响应。但是,过度嵌套的回调函数(回调地狱)会导致代码难以阅读和维护。
function getData(callback) {
setTimeout(() => {
let data = 'data';
callback(data);
}, 1000);
}
getData(function(data) {
console.log(data);
});
以上代码展示了最基本的异步回调模式。在复杂的场景下,许多异步操作可能需要在前一个操作完成后才能执行,这常常导致深层嵌套的回调,使得代码结构复杂且难以理解。
function asyncOperation1(callback) {
// 异步操作1
callback();
}
function asyncOperation2(callback) {
// 异步操作2
callback();
}
asyncOperation1(() => {
asyncOperation2(() => {
console.log('Both operations completed');
});
});
在本段代码中,嵌套的回调使得阅读和维护变得更加困难。
3.2 Promise机制的原理和应用
3.2.1 Promise的基本用法
Promise是解决回调地狱问题的一种方式。它是一个代表了异步操作最终完成或失败的对象,解决了回调地狱的问题,并能提供更优雅的错误处理机制。
const promise = new Promise((resolve, reject) => {
// 异步操作完成时调用resolve(),失败时调用reject()
setTimeout(() => resolve("Success!"), 1000);
});
promise.then((value) => {
console.log(value); // Success!
});
以上代码展示了如何创建和使用一个Promise对象,通过 .then()
方法来处理异步操作成功的结果。
3.2.2 Promise链式调用和错误处理
Promise的链式调用特性让代码更加清晰,每个 .then()
方法都会返回一个新的Promise对象,这让我们可以顺序地执行多个异步操作。
const promise = new Promise((resolve, reject) => {
setTimeout(() => resolve(1), 1000);
});
promise.then((result) => {
console.log(result); // 1
return result + 2;
}).then((result) => {
console.log(result); // 3
return result + 3;
}).then((result) => {
console.log(result); // 6
});
在本段代码中,连续使用 .then()
方法实现了对异步操作的结果进行顺序处理。
错误处理通常通过 .catch()
方法来实现,该方法能够捕获链中前面任何位置发生的错误。
promise.then((result) => {
// 成功的代码
}).catch((error) => {
// 错误处理的代码
});
3.2.3 异步编程的高级实践:async/await
async/await
是建立在Promise之上的语法糖,它提供了一种更简洁、直观的方式来处理异步代码。使用 async/await
可以让异步代码的书写和理解接近同步代码的风格。
async function getData() {
try {
const result1 = await asyncOperation1();
const result2 = await asyncOperation2(result1);
console.log(result2);
} catch (error) {
console.error(error);
}
}
getData();
在这个例子中, async
关键字声明了一个异步函数, await
关键字后面跟着一个Promise。函数中的其他代码会在Promise解决之后继续执行。
通过将 async/await
与Promise结合使用,可以有效地管理复杂的异步代码,同时保持代码的可读性和可维护性。
3.2.4 实践案例:使用Promise重构复杂的异步操作
让我们来看一个实际的异步操作的代码示例,并通过Promise改进它:
// 异步函数模拟
function fetchDataFromServer() {
return new Promise((resolve, reject) => {
// 模拟网络请求
setTimeout(() => resolve('Data from server'), 2000);
});
}
function processAndDisplayData(data) {
// 处理数据
const processedData = `Processed: ${data}`;
// 显示数据
console.log(processedData);
}
// 使用回调函数嵌套
fetchDataFromServer(function(data) {
processAndDisplayData(data);
}, function(error) {
console.log('Error:', error);
});
// 使用Promise重构
fetchDataFromServer()
.then(processAndDisplayData)
.catch(error => console.log('Error:', error));
重构之后的代码通过Promise清晰表达了异步流程,更易于理解和维护。
在异步编程的章节中,我们深入探讨了JavaScript异步编程的核心概念,包括同步与异步代码的执行差异、回调函数的局限性、Promise机制的原理和应用、以及async/await的高级实践。通过具体案例,展示了如何利用这些概念和技术重构复杂的异步操作,从而提升代码的可读性和维护性。随着前端应用的日益复杂,掌握这些异步编程技巧对于任何前端开发者来说都是至关重要的。
4. ```
第四章:工厂模式和对象创建
工厂模式是一种创建型设计模式,它提供了一种在不暴露对象创建逻辑的情况下创建对象实例的方法。工厂模式通过定义一个用于创建对象的接口,让子类决定实例化哪一个类。这使得一个类的实例化被推迟到子类中进行,从而更加灵活,并且可以应用于多种复杂的场景中。
4.1 工厂模式的理论与实现
4.1.1 工厂模式的定义和分类
工厂模式主要分为三种基本类型:
-
简单工厂模式(Simple Factory) :定义了一个创建对象的类,但由这个类来决定要实例化的类是哪一个。工厂类拥有一个静态方法,根据不同的输入参数返回不同类的实例。
-
工厂方法模式(Factory Method) :定义了一个创建对象的抽象方法,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类中进行。
-
抽象工厂模式(Abstract Factory) :提供一个接口,用于创建相关的或依赖对象的家族,而不需要明确指定具体类。
4.1.2 工厂模式在JavaScript中的实现方式
在JavaScript中,工厂模式的实现比较灵活,因为JavaScript是一门动态类型的语言,我们不需要像在静态语言中那样严格地定义类型。以下是一个简单的工厂模式实现示例:
// 创建一个简单的工厂函数
function createProduct(type) {
if (type === 'ProductA') {
return new ProductA();
} else if (type === 'ProductB') {
return new ProductB();
}
// 可以根据需要扩展更多的产品类型
}
// 产品构造函数
function ProductA() {
this.type = 'A';
this.use = function() {
console.log('Product A is used.');
};
}
function ProductB() {
this.type = 'B';
this.use = function() {
console.log('Product B is used.');
};
}
// 使用工厂函数创建产品
const product1 = createProduct('ProductA');
product1.use(); // 输出: Product A is used.
const product2 = createProduct('ProductB');
product2.use(); // 输出: Product B is used.
4.2 工厂模式的应用场景
4.2.1 复杂对象的创建和管理
工厂模式特别适合用于创建复杂对象,尤其是当对象的创建依赖于复杂的逻辑判断时。通过工厂模式,可以将创建对象的逻辑封装在一个单独的地方,这样当需要创建对象时,只需调用工厂方法即可。
4.2.2 模块化编程中的工厂模式实践
在模块化编程中,我们可以使用工厂模式来创建模块的实例,这样可以在运行时动态地创建不同的模块实例,提高代码的模块化程度。
// 定义一个模块工厂
function createModule(moduleType) {
const modules = {
'ModuleA': function() { /* ... */ },
'ModuleB': function() { /* ... */ }
};
if (!modules[moduleType]) {
throw new Error(`Module ${moduleType} does not exist.`);
}
return new modules[moduleType]();
}
// 使用模块工厂
const moduleA = createModule('ModuleA');
// moduleA 现在是一个 ModuleA 的实例
4.2.3 工厂模式与其他设计模式的结合
工厂模式可以与其他设计模式灵活结合,例如单例模式、建造者模式等,以实现更复杂的对象创建逻辑。
// 结合单例模式
function createSingleton(type) {
let instance;
function create() {
if (type === 'SingletonA') {
return new SingletonA();
} else {
throw new Error('Type does not match any available singleton.');
}
}
return {
getInstance: function() {
return instance || (instance = create());
}
};
}
// SingletonA 构造函数
function SingletonA() {
if (new.target) {
throw new Error('Cannot construct SingletonA instances directly.');
}
this.value = 'Only one instance exists.';
}
const singleton1 = createSingleton('SingletonA').getInstance();
const singleton2 = createSingleton('SingletonA').getInstance();
console.log(singleton1 === singleton2); // 输出: true
工厂模式是软件开发中一种非常实用的设计模式。通过它的封装和抽象,可以大大提高系统的扩展性和维护性。在实际开发中,根据不同的需求和场景,我们可以灵活地选择合适的工厂模式,并与其他设计模式结合使用,构建出更加高效、灵活的代码结构。
# 5. 观察者模式和对象间依赖关系
## 5.1 观察者模式的基本原理
### 5.1.1 观察者模式的定义和组件
观察者模式(Observer Pattern)是一种行为设计模式,允许对象在状态改变时通知其他对象。观察者模式通常由以下两个主要部分组成:
- **Subject(主题)**:定义了注册、移除和通知观察者的接口。主题维护一组观察者,并在状态改变时通知它们。
- **Observer(观察者)**:定义了一个更新接口,用于接收主题状态变化的通知。当主题状态改变时,观察者的更新方法将被调用。
观察者模式的目的是建立对象间的一种一对多依赖关系,当一个对象改变状态时,所有依赖于它的对象都会收到通知并自动更新。
### 5.1.2 观察者模式的JavaScript实现
在JavaScript中实现观察者模式,通常会使用发布订阅模式(Publish/Subscribe)。以下是一个简单的实现示例:
```javascript
class Subject {
constructor() {
this.observers = [];
}
subscribe(observer) {
this.observers.push(observer);
}
unsubscribe(observer) {
this.observers = this.observers.filter(obs => obs !== observer);
}
notify(data) {
this.observers.forEach(observer => {
observer.update(data);
});
}
}
class Observer {
update(data) {
console.log('Observer received data:', data);
}
}
// 使用示例
const subject = new Subject();
const observerA = new Observer();
const observerB = new Observer();
subject.subscribe(observerA);
subject.subscribe(observerB);
subject.notify('New data!');
// 输出:
// Observer received data: New data!
// Observer received data: New data!
在上述代码中, Subject
类定义了 subscribe
和 unsubscribe
方法来管理观察者的注册和移除, notify
方法用来向所有观察者发送通知。 Observer
类定义了一个 update
方法,当有新数据时会被调用。
5.2 观察者模式的应用技巧
5.2.1 前端事件处理机制
在前端开发中,DOM事件监听机制本质上是一种观察者模式的应用。浏览器会维护一个事件监听器列表,并在相应事件触发时,调用这些监听器。使用 addEventListener
方法可以注册事件监听器,这是对观察者模式的直接应用。
const button = document.querySelector('button');
button.addEventListener('click', () => {
console.log('Button clicked!');
});
// 点击按钮时,控制台将输出 "Button clicked!"
5.2.2 数据绑定和状态管理
观察者模式在现代前端框架中,如React和Vue,广泛应用于数据绑定和状态管理。通过观察者模式,当组件状态变化时,依赖于该状态的视图或其他组件能够自动更新。
在Vue中, data
属性的变化会自动通知视图层更新,这是通过Vue内置的响应式系统实现的,其底层机制与观察者模式相似。
5.2.3 观察者模式在组件通信中的应用
在组件化的前端开发中,组件间的通信是常见需求。观察者模式允许组件间不直接依赖,而是通过事件来进行通信,这样增加了模块间的解耦,提升了代码的可维护性。
例如,在React中,可以通过回调函数的方式将数据从父组件传递到子组件,并在子组件中触发回调函数以通知父组件状态变化,这类似于观察者模式的实现。
class Parent extends React.Component {
handleChildUpdate(data) {
console.log('Data from child:', data);
}
render() {
return (
<Child onChildUpdate={this.handleChildUpdate} />
);
}
}
class Child extends React.Component {
handleClick() {
this.props.onChildUpdate('Updated data');
}
render() {
return (
<button onClick={this.handleClick}>Send Data</button>
);
}
}
在上述代码中, Child
组件触发了一个事件, Parent
组件订阅了这个事件,并在事件发生时接收数据。这种模式体现了观察者模式的组件间通信机制。
通过以上章节的介绍,观察者模式的原理和应用在现代前端开发中扮演了关键角色。它不仅支持了复杂状态的管理,而且促进了组件间的解耦,使得代码更加模块化和易于维护。
6. ```
第六章:装饰器模式和代码扩展性
装饰器模式是一种在不改变对象自身的基础上,通过添加新的方法或者属性来扩展对象功能的设计模式。它通过提供一个装饰类,将对象包裹在其中,允许向一个现有的对象添加新的功能,同时又不改变其结构。本章将深入探讨装饰器模式的定义、实现及其在实际开发中的应用。
6.1 装饰器模式的概念与实现
6.1.1 装饰器模式的定义和核心思想
装饰器模式由以下几个核心概念构成:
- 组件(Component) :定义一个对象接口,可以给这些对象动态添加职责。
- 具体组件(Concrete Component) :定义了一个具体的对象,也可以给这个对象添加一些职责。
- 装饰(Decorator) :维持一个指向组件对象的引用,并定义一个与组件接口一致的接口。
- 具体装饰(Concrete Decorators) :负责给组件添加新的职责。
装饰器模式的动机是为了解决继承存在的问题,避免创建多个子类,同时增加功能的灵活性。核心思想是在不必改变现有对象的情况下,动态地添加功能。
6.1.2 JavaScript中的装饰器模式实现
在JavaScript中,装饰器模式可以通过实现一个装饰函数来完成,该函数接收一个组件对象作为参数,返回一个带有增强功能的新对象。以下是一个简单的示例:
// 组件接口
function Component() {}
// 具体组件
function ConcreteComponent() {
this.baseOperation = function() {
console.log('ConcreteComponent Operation');
};
}
// 装饰器接口
function Decorator(component) {
this.component = component;
}
// 具体装饰器
Decorator.prototype = {
baseOperation: function() {
return this.component.baseOperation();
},
addedBehavior: function() {
console.log('Decorator Added Behavior');
}
};
// 使用装饰器模式
var myComponent = new ConcreteComponent();
var myDecorator = new Decorator(myComponent);
// 调用装饰后的对象
myDecorator.baseOperation(); // 输出 ConcreteComponent Operation
myDecorator.addedBehavior(); // 输出 Decorator Added Behavior
上述代码中, Decorator
是一个装饰器,它包含了一个 Component
类型的实例,并向该实例添加了新的行为。 addedBehavior
方法就是通过装饰器添加的行为。
装饰器模式的一个重要特点是在装饰过程中不会修改原有对象,而是创建一个新对象来扩展原有对象的功能。
6.2 装饰器模式的实践应用
6.2.1 提高代码复用性的装饰策略
装饰器模式的一个显著优势是它增强了函数的复用性。我们可以创建一个装饰器,向任意组件添加通用的功能,而无需修改组件本身。这在构建可复用组件库时尤其有用。
例如,我们可以创建一个日志装饰器,向任何组件添加日志记录功能:
function logDecorator(component) {
return function(...args) {
console.log('Before component call');
const result = component.apply(this, args);
console.log('After component call');
return result;
}
}
function myFunction(a, b) {
console.log(a + b);
}
// 使用日志装饰器
var myLoggedFunction = logDecorator(myFunction);
myLoggedFunction(2, 3); // 输出日志并返回5
6.2.2 装饰器模式在面向切面编程中的运用
面向切面编程(AOP)是一种编程范式,旨在将横切关注点与业务主体进行分离,以提高模块化。装饰器模式在AOP中扮演了重要角色,特别是在前端框架和库中,如React、Redux等,它们利用装饰器模式来增强组件的行为。
在React中,高阶组件(HOC)就是一种装饰器模式的运用:
const EnhancedComponent = higherOrderComponent(WrappedComponent);
这里, higherOrderComponent
可以被认为是一个装饰器,它接收一个组件,并返回一个拥有增强功能的新组件。
6.2.3 装饰器模式与函数式编程的融合
在函数式编程中,装饰器模式可以用来创建高阶函数,它们接收函数作为参数,并返回一个新的函数。这允许我们以声明式的方式对函数行为进行扩展。
const withLogging = (func) => {
return (...args) => {
console.log('Before function call');
const result = func.apply(this, args);
console.log('After function call');
return result;
}
}
const originalFunction = (a, b) => a + b;
const loggedFunction = withLogging(originalFunction);
loggedFunction(2, 3); // 输出日志并返回5
总结
在本章节中,我们深入了解了装饰器模式,它不仅能够在不修改对象本身的前提下,添加新的功能,还能够增强代码的复用性、模块化和可维护性。装饰器模式在JavaScript中有着广泛的应用,无论是在库的构建、面向切面编程、还是在函数式编程中,它都提供了一种优雅的方式来扩展和增强对象的行为。通过本章内容的学习,希望读者能够更加自如地运用装饰器模式来优化自己的代码。
# 7. 代理模式和访问控制
代理模式是一种结构型设计模式,它为其他对象提供一个代理或占位符以控制对这个对象的访问。在JavaScript中,代理模式可以用来实现各种功能,包括访问控制、延迟加载、日志记录等。
## 7.1 代理模式的理论框架
### 7.1.1 代理模式的定义和应用场景
代理模式通常包含三个角色:主题(Subject)、真实主题(RealSubject)和代理(Proxy)。代理对象和真实对象都实现了同一个接口或继承同一个类,这样代理对象可以在客户端和真实对象之间起到中介的作用。
应用场景包括:
- **访问控制**:在执行实际操作前进行权限检查。
- **日志记录**:在执行方法前后记录日志。
- **缓存机制**:对于重复的请求,直接返回缓存结果而不是执行实际方法。
- **延迟加载**:将某些耗时操作延迟到真正需要时才执行。
### 7.1.2 实现JavaScript中的代理模式
下面是一个简单的代理模式实现,使用了JavaScript的ES6 Proxy特性:
```javascript
// 真实主题
class RealSubject {
request() {
console.log('Requesting data...');
// 模拟一些耗时的数据处理
return 'Data after processing';
}
}
// 代理
class ProxySubject {
constructor(realSubject) {
this.realSubject = realSubject;
}
request() {
console.log('Access control check...');
const result = this.realSubject.request();
console.log('Logging...');
return result;
}
}
// 使用代理
const realSubject = new RealSubject();
const proxy = new ProxySubject(realSubject);
// 客户端代码
proxy.request();
在这个例子中, ProxySubject
类创建了一个代理来控制对 RealSubject
类的访问。实际请求前,会进行访问控制检查和日志记录。
7.2 代理模式的深入应用
7.2.1 缓存机制的代理实现
缓存是代理模式一个常见的应用场景,通过存储已经处理过的请求结果来优化性能。
function createCacheProxy() {
const cache = {};
return new Proxy({}, {
get: function(target, key) {
if (!(key in cache)) {
cache[key] = Reflect.get(...arguments);
}
return cache[key];
},
set: function(target, key, value) {
cache[key] = value;
return true;
}
});
}
const cacheProxy = createCacheProxy();
cacheProxy.data = 'test'; // 缓存 'test' 到 key 'data'
console.log(cacheProxy.data); // 从缓存获取 'test'
7.2.2 安全控制和访问权限管理
代理可以控制对敏感数据的访问。例如,我们可以通过代理来实现对私有属性的访问控制:
class SecretInfo {
#secret = "Top Secret Data";
constructor() {
this.proxy = new Proxy(this, {
get: function(target, propKey) {
if (propKey.toString().startsWith('#')) {
throw new Error('Access denied: private property');
}
return target[propKey];
}
});
}
}
const secret = new SecretInfo();
console.log(secret.proxy.secret); // 可以访问
console.log(secret.proxy.#secret); // 抛出错误
7.2.3 高阶代理:虚拟代理和保护代理
虚拟代理和保护代理是代理模式的高级应用,通常用于处理更复杂的场景。
- 虚拟代理 :可以处理资源加载延迟的问题。例如,在图片加载之前显示一个占位符。
- 保护代理 :可以对非法请求进行拦截,例如,不允许访问者在特定时间内访问某些内容。
在设计高阶代理时,需要根据实际应用场景来灵活地扩展和使用代理模式。
代理模式通过代理机制为对象的创建、访问和管理提供了强大的控制力。在实际开发中,它可以与观察者模式、策略模式等多种设计模式结合使用,从而实现更加复杂和灵活的系统架构。
简介:JavaScript 设计模式是提升代码质量的通用解决方案模板,涵盖了如单例模式、Promise以及工厂、观察者、装饰器、代理、命令、策略和适配器等模式。该课程深入探讨这些模式的实现和应用,帮助开发者编写更健壮、可维护的JavaScript代码,并提高团队协作效率。