JS三大原则之「过程抽象」 | 青训营笔记

99 阅读5分钟

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。

高阶函数

  • 高阶函数是一种特殊类型的函数,它以函数作为参数,并且把函数作为返回值。上面的例子我们也可以在函数处理前后添加其他操作,比如在发送数据的时候添加请求头等等。 img
  • 它们常用于作为函数装饰器。高阶函数可以在函数处理前后添加其他操作,如在函数执行前添加验证,或者在函数执行后添加日志记录等。

举个栗子(函数装饰器):

现在小明有一个计算函数 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()函数可以对一个列表的每个元素执行一个函数,并返回一个单一的值。

常用高阶函数

使用场景:控制事件触发频率,比如不论多快的点击按钮,都是隔500毫秒才执行一次事件

使用场景:也是用来限制事件的调用、但是它不仅是限制调用的频率,如用户在频繁的编辑一段文字,等到用户编辑停止几百毫秒之后再执行提交操作