什么是面向切面编程
面向切面编程(Aspect-Oriented Programming,AOP)是一种编程范式,是在面向对象编程(OOP)的基础上的一种补充。AOP提供了一种灵活的、易于维护和跨多个对象和组件的切面(Aspect)的方式,可以在不修改原有代码的情况下实现代码的特定功能。
下面是一个简单的JS示例代码,实现了AOP的日志记录功能:
js
//定义一个用于包装函数的高阶函数
function Logging(fn) {
return function() {
console.log(`entering ${fn.name}`);
const result = fn.apply(this, arguments);
console.log(`exiting ${fn.name}`);
return result;
}
}
//定义一个需要包装的函数
function add(x, y) {
return x + y;
}
//将函数用Logging包装
const loggedAdd = Logging(add);
//调用经过包装的函数
loggedAdd(2, 3);
在上面的代码中,Logging是一个高阶函数(Higher Order Function),它返回的函数可以包装一个函数,并能够记录该函数的输入和输出。add是一个需要包装的函数,它的功能是相加两个数。用Logging包装add得到的loggedAdd函数,可以在add函数执行前输出"entering add",在add函数执行后输出"exiting add",并返回add函数的执行结果。
只不过这样的使用方式不太友好,怎么才能更加简单呢,在VUE、Recat、Angular等框架中常见的装饰是个比较好的方式。
装饰器
JavaScript中的装饰器是一种特殊的函数,它可以用来修改类或对象的行为。装饰器可以在不修改原始类或对象定义的情况下,动态地添加、删除或修改它们的属性和方法。
装饰器可以被用来解决很多问题,比如增加验证、日志记录、性能分析等功能,这些功能可以通过在类或对象的定义上应用不同的装饰器来实现。使用装饰器可以让代码具有更好的可维护性和可扩展性。
通常情况下,装饰器可以在函数、类、类成员上应用。
我们准备对防抖和节流这两个最常用的函数进行升级。升级前先看看最基本的使用方式。
函数防抖(debounce)
概念: 在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。
生活中的实例: 如果有人进电梯(触发事件),那电梯将在10秒钟后出发(执行事件监听器),这时如果又有人进电梯了(在10秒内再次触发该事件),我们又得等10秒再出发(重新计时)。
JS函数防抖:
js
function debounce(fn, wait) {
var timer = null;
return function () {
var context = this
var args = arguments
if (timer) {
clearTimeout(timer);
timer = null;
}
timer = setTimeout(function () {
fn.apply(context, args)
}, wait)
}
}
var fn = function () {
console.log('boom')
}
setInterval(debounce(fn,500),1000) // 第一次在1500ms后触发,之后每1000ms触发一次
setInterval(debounce(fn,2000),1000) // 不会触发一次(我把函数防抖看出技能读条,如果读条没完成就用技能,便会失败而且重新读条)
函数节流(throttle)
概念: 规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。
生活中的实例: 我们知道目前的一种说法是当 1 秒内连续播放 24 张以上的图片时,在人眼的视觉中就会形成一个连贯的动画,所以在电影的播放(以前是,现在不知道)中基本是以每秒 24 张的速度播放的,为什么不 100 张或更多是因为 24 张就可以满足人类视觉需求的时候,100 张就会显得很浪费资源。
JS函数节流:
js
function throttle(fn, gapTime) {
let _lastTime = null;
return function () {
let _nowTime = + new Date()
if (_nowTime - _lastTime > gapTime || !_lastTime) {
fn();
_lastTime = _nowTime
}
}
}
let fn = ()=>{
console.log('boom')
}
setInterval(throttle(fn,1000),10)
在使用上面的函数时我们要对原有的函数进行调整,增加业务逻辑,改动起来比较麻烦。
使用装饰器封装函数防抖和函数节流,代码如下:
装饰器-函数防抖
js
/**
* 函数防抖
* @export
* @param {*} delay
* @returns {(target, name, descriptor) => any}
*/
export function debounce(delay) {
let timer: any = null;
return function (target, name, descriptor) {
const originFn = descriptor.value;
descriptor.value = function (...args) {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
originFn.apply(this, args);
}, delay);
}
return descriptor;
}
}
装饰器-函数节流
js
/**
* 函数节流
* @param {function} fn 函数
* @param {number} delay 延迟执行毫秒数
* @returns {(target: any, name: string, descriptor: PropertyDescriptor) => any}
*/
export function throttle(delay) {
let timer: any = null;
return function (target, name, descriptor) {
const originFn = descriptor.value;
descriptor.value = function (...args) {
if (timer) {
return;
}
timer = setTimeout(() => {
originFn.apply(this, args);
timer = undefined;
}, delay);
}
return descriptor;
}
}
使用方式比较简单,在需要处理的函数上添加上装饰器就行。
js
// 函数防抖
@debounce(100)
init(){
......
}
// 函数节流
@throttle(100)
init(){
......
}
这样在使用时不会影响到原始的函数,代码也不需要进行调整。完美!!
加个关注,不迷路