JS三大原则之「过程抽象」 | 青训营笔记
这是我参与「第五届青训营 」伴学笔记创作活动的的第4天。
写好JS的三大原则:
- 各司其责: 组件封装过程抽象让HTML、CSS和JavaScript职能分离。
- 组件封装: 好的UI组件具备正确性、扩展性、复用性。
- 过程抽象: 应用函数式编程思想。
接下来将详细探讨一下 「过程抽象」 这一原则。
过程抽象
- 过程抽象是一种思维方式和编程方式。它可以帮助我们更好地组织和管理代码。
- 函数式编程就是这种思想的体现,通过将程序组成一系列可组合的函数来实现抽象。函数被看作一种 “控制器” ,可以控制输入和输出,而且可以通过组合函数来实现更复杂的功能。
- 这种方式可以减少副作用,提高代码的可维护性和可重用性。
举个栗子
如有以下一个列表,当任务完成,勾选之后该任务就消失,消失的时候有一个动画效果
实现方案(反例)
可能有的人会这样实现
- 先得到列表list
- 监听每个按钮的点击事件
- 加一个定时,2s之后移除完成的
- 通过改变class实现一个过渡的动画效果
const list = document.querySelector('ul');
const buttons = list.querySelectorAll('button');
buttons.forEach((button) => {
button.addEventListener('click', (evt) => {
const target = evt.target;
target.parentNode.className = 'completed';
setTimeout(() => {
list.removeChild(target.parentNode);
}, 2000);
});
});
存在问题
- 如果连续多次点击复选框,复选框已经被移除,后面会报Dom节点不存在的错误
原因
- 多次点击,就会执行多个setTimeout,当同一的DOM被移除一次之后,之后的remove就找不到了这个DOM了
改进
- 应该只触发一次click事件
扩展
- 一些异步交互
- 一次性的HTTP请求
- 提交表单,或者其他一些任务都应该限制操作次数
Once
为了能够让“只执行一次“的需求覆盖不同的事件处理,我们可以将这个需求剥离出来。这个过程我们称为过程抽象。
function once(fn) {
return function(...args) {
if(fn) {
const ret = fn.apply(this, args);
fn = null;
return ret;
}
}
}
const list = document.querySelector('ul');
const buttons = list.querySelectorAll('button');
buttons.forEach((button) => {
button.addEventListener('click', once((evt) => {
const target = evt.target;
target.parentNode.className = 'completed';
setTimeout(() => {
list.removeChild(target.parentNode);
}, 2000);
}));
});
这里给函数传入function,第一次进来首先会为true,if中的内容执行,在执行之后立马function被设为null,下次进入这个函数的时候,if就不会执行了,因为null在条件判断中相当于false。
高阶函数
- 高阶函数是一种特殊类型的函数,它以函数作为参数,并且把函数作为返回值。上面的例子我们也可以在函数处理前后添加其他操作,比如在发送数据的时候添加请求头等等。
- 它们常用于作为函数装饰器。高阶函数可以在函数处理前后添加其他操作,如在函数执行前添加验证,或者在函数执行后添加日志记录等。
举个栗子(函数装饰器):
现在小明有一个计算函数 calculate() ,他想在函数执行前后记录日志,小明结合函数装饰器的知识可以这样写:
import time
def log_decorator(func):
def wrapper(*args, **kwargs):
print(f'Calling {func.__name__} at {time.time()}')
result = func(*args, **kwargs)
print(f'{func.__name__} returned {result} at {time.time()}')
return result
return wrapper
@log_decorator
def calculate(a,b):
return a+b
calculate(1,2)
输出结果为:
Calling calculate at 1613191239.8678887
calculate returned 3 at 1613191239.8678887
这里小明定义了一个 log_decorator 函数, 该函数是一个高阶函数,它接收一个函数作为参数,返回一个新的函数。新函数会在调用原始函数之前和之后打印日志。
用 @log_decorator 装饰器装饰 calculate 函数,这样小明不需要在 calculate 函数中手动添加日志代码,而是由装饰器自动完成。
这个例子中我们简单的使用了时间戳来记录日志,但是在实际应用中,我们可以使用更复杂的日志库,并且可以做更多的事情例如,性能监控, 缓存, 权限验证等。
- 函数装饰器可以给函数增加额外的功能, 例如在函数执行前后加上日志记录, 性能监控, 缓存, 以及权限验证等.
举个栗子(额外功能)
现在小明有一个函数 get_user_data() 用于获取用户信息。小明想在调用这个函数之前进行权限验证,小明是这样写的:
def auth_decorator(func):
def wrapper(*args, **kwargs):
user_id = kwargs.get('user_id')
if not user_id:
raise Exception("user_id missing")
if not is_authorized(user_id):
raise Exception("user not authorized")
return func(*args, **kwargs)
return wrapper
@auth_decorator
def get_user_data(user_id):
return user_data[user_id]
get_user_data(user_id=1)
这里小明定义了一个 auth_decorator 函数, 该函数是一个高阶函数,它接收一个函数作为参数,返回一个新的函数。新函数会在调用原始函数之前进行权限验证。
用 @auth_decorator 装饰器装饰 get_user_data 函数,这样小明不需要在 get_user_data 函数中手动添加权限验证代码,而是由装饰器自动完成。
这个例子中我们简单的使用了一个检查user_id 是否存在及是否有权限的代码,但是在实际应用中, 我们可以使用更复杂的权限管理库来实现权限验证。
- 高阶函数也常用于函数式编程, 例如 map, filter, reduce 等。通过高阶函数可以简化代码,提高代码的可维护性和可重用性。
map()函数可以对一个列表的每个元素执行一个函数,并返回一个新的列表。filter()函数可以对一个列表的每个元素执行一个函数,并返回一个新的列表,其中只包含函数返回 true 的元素。reduce()函数可以对一个列表的每个元素执行一个函数,并返回一个单一的值。
常用高阶函数
- Once
- Throttle(节流函数)
使用场景:控制事件触发频率,比如不论多快的点击按钮,都是隔500毫秒才执行一次事件
使用场景:也是用来限制事件的调用、但是它不仅是限制调用的频率,如用户在频繁的编辑一段文字,等到用户编辑停止几百毫秒之后再执行提交操作