如果将 Spring Boot 的依赖注入(IOC)和面向切面编程(AOP)在前端实现,会碰撞出怎样有趣的火花?
前言
在现代开发中,依赖注入(IOC)和面向切面编程(AOP)是构建模块化、可维护代码的重要设计理念。IOC 通过将对象的依赖关系管理交由容器处理,减少了组件之间的耦合度,使代码更具扩展性;而 AOP 则以灵活的方式在不改变核心逻辑的前提下,实现如日志、事务等通用功能的横切关注点,提升了代码的可维护性和可复用性。
随着前端工程的日益复杂,将这些思想应用于前端开发,能为前端代码带来更强的模块化和解耦优势。博主尝试探索如何在前端实现这两大思想,期待为前端开发带来新的思路与启发。
在此,致谢所有为开源事业默默奉献的开发者们,感谢你们为技术社区带来的创新与推动力。
使用
创建容器
创建一个依赖注入容器 Container.js
class Container {
constructor() {
this.services = {};
}
register(name, definition, dependencies) {
this.services[name] = { definition, dependencies, instance: null };
}
get(name) {
const service = this.services[name];
if (!service) {
throw new Error(`Service '${name}' not found`);
}
// 如果实例已经存在,直接返回
if (service.instance) {
return service.instance;
}
// 解析依赖并实例化服务
service.instance = service.definition(
...(service.dependencies || []).map(dep => this.get(dep))
);
return service.instance;
}
}
module.exports = Container;
创建切面
创建一个面向切换 Aspect.js
class Aspect {
constructor(target, aspect) {
return new Proxy(target, {
get(obj, prop) {
if (typeof obj[prop] === 'function') {
return (...args) => {
aspect.before && aspect.before(prop, args);
const result = obj[prop](...args);
aspect.after && aspect.after(prop, args, result);
return result; // 确保返回原始方法的执行结果
};
}
return obj[prop];
}
});
}
}
module.exports = Aspect;
实现
const Container = require('./Container');
const Aspect = require('./Aspect');
// 3. 创建容器并注册服务
const container = new Container();
// 注册一个日志服务
container.register('logger', () => {
return {
log: (message) => console.log(`[Logger]: ${message}`)
};
});
// 注册一个用户服务,依赖于日志服务
container.register('userService', (logger) => {
return {
getUser: () => {
logger.log('执行中。。。');
return { name: 'Alice', age: 25 };
}
};
}, ['logger']);
// 4. 应用 AOP 到 userService
const userService = container.get('userService');
const userServiceWithAspect = new Aspect(userService, {
before(method, args) {
console.log(`执行前:`, args,method);
},
after(method, args, result) {
result.age = 19; // 更改年龄
console.log(`执行后`,args,method,result);
}
});
let user = userServiceWithAspect.getUser();
console.log(user);
输出:
为什么?
执行前打印的是一个空的数组[]
因为在调用userServiceWithAspect.getUser(); 方法的时候,未传入任何数据进去,而是在userService注册的时候,返回了该对象
同时返回的年龄为 age = 25
在after 执行之后的,可以对age进行更改,返回19
从而实现了一个环绕切面实现
表单案例
const Container = require('./Container');
const Aspect = require('./Aspect');
// 3. 创建容器并注册服务
const container = new Container();
container.register('validationService', () => {
return {
validate: (formData) => {
// 假设所有字段必须非空
console.log("数据=====执行中====",formData)
for (const key in formData) {
if (!formData[key]) {
throw new Error(`Field '${key}' cannot be empty`);
}
}
return true; // 添加返回值
}
};
});
const validationService = container.get('validationService');
const validationServiceWithAspect = new Aspect(validationService, {
before(method, args) {
console.log(`执行前:`, args,method);
},
after(method, args,result) {
console.log(`执行后`,args,method,result);
}
});
// 表单提交时调用验证
try {
validationServiceWithAspect.validate({ username: 'Alice', password: '12345' });
} catch (error) {
console.error(error.message);
}
输出:
跟上面实现不同的是,为什么这里的before能打印出参数呢
因为在调用 validationServiceWithAspect.validate({ username: 'Alice', password: '12345' }); 传入了该对象,
所以执行之前能打印出来,
依次内推的思路,在before中修改我们对应想要的值
在after 修改对应的值,然后最后返回
实现了一个环绕切面
api 请求示例
const Container = require('./Container');
const Aspect = require('./Aspect');
// 3. 创建容器并注册服务
const container = new Container();
// 3. 全局 API 请求管理
container.register('apiService', () => {
return {
request: (url, data,options) => {
// 模拟 API 请求
console.log(`执行中 请求url: ${url} 参数: ${JSON.stringify(data)} 方法:${JSON.stringify(options)}`);
return { data: 'response data' };
}
};
});
const apiService = container.get('apiService');
const apiServiceWithAspect = new Aspect(apiService, {
before(method, args) {
console.log(`执行前: ${method} ${JSON.stringify(args)}`);
},
after(method, args, result) {
console.log(`执行后: ${method} ${JSON.stringify(args)} ${JSON.stringify(result)}`);
}
});
apiServiceWithAspect.request('/api/user',{ username: 'Alice', password: '12345' }, { method: 'GET' });
输出:
组件示例
const Container = require('./Container');
const Aspect = require('./Aspect');
// 3. 创建容器并注册服务
const container = new Container();
// 4. 跨组件状态共享
container.register('themeService', () => {
let currentTheme = 'light';
return {
getTheme: () => currentTheme,
setTheme: (theme) => {
currentTheme = theme;
console.log(`主题改为: ${theme}`);
}
};
});
const themeService = container.get('themeService');
const themeServiceWithAspect = new Aspect(themeService, {
after(method, args) {
if (method === 'setTheme') {
console.log(`主题更新,触发UI重新渲染. 方法体:${method} 参数:${args}`);
}
}
});
themeServiceWithAspect.setTheme('dark');
输出: