JS成功学之「过程抽象」 | 青训营笔记

97 阅读4分钟

JS成功学之「过程抽象」 | 青训营笔记

这是我参与「第四届青训营 」笔记创作活动的的第4天。

写好JS的三大原则:

  • 各司其责: 组件封装过程抽象让HTML、CSS和JavaScript职能分离
  • 组件封装: 好的UI组件具备正确性、扩展性、复用性
  • 过程抽象: 应用函数式编程思想

接下来将详细探讨一下 「过程抽象」 这一原则。

过程抽象

  • 所谓过程抽象,其实是一种思维方式,也是一种编程方式,我们可以把函数当成一个控制器,控制这函数的输入和输出,它也是函数式编程思想的基础。
img

示例

如有以下一个列表,当任务完成,勾选之后该任务就消失,消失的时候有一个动画效果

image.png #### 实现方案(反例)

可能有的人会这样实现

  • 先得到列表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节点不存在的错误
img

原因

  • 多次点击,就会执行多个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

 function HOF0(fn) {
     return function(...args) {
         return fn.apply(this, args);
     }
 }

常用高阶函数

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

使用场景:也是用来限制事件的调用、但是它不仅是限制调用的频率,如用户在频繁的编辑一段文字,这段文字会自动进行保存,如果用户很快的修改,而我们一直在提交,对服务器压力是很大的,这个时候我们可以等到用户编辑停止几百毫秒之后再执行提交操作

为什么要用高阶函数?

函数可以分为

  • 纯函数
  • 非纯函数:

一个项目、系统或库,理论上来说纯函数越多,可维护性越好。(纯函数也便于编写单元测试)

img

纯函数

当一个function,输入的值确定了,输出的值是唯一确定的,那么这样的一个函数就是纯函数。

纯函数的两个原则

  • 这个函数无副作用
  • 具有幂等性,在任何时间、次序下去调用它,它的结果是唯一的,不会被改变的

举个栗子:

 // 纯函数
 function add(a,b){
     return a + b;
 };
 // 非纯函数
 let num = 6;
 function add1(){
     return  num++;
 }
 console.log(add(1,2));//3
 console.log(add1());//6
 console.log(add1());//7

编程范式

命令式与声明式

img

有的语言是纯命令式,有的是纯声明式,但是JavaScript都有!

如以下,要实现数组元素*2操作,既可以用命令式又可以用声明式

 //命令式
 let list = [1, 2, 3, 4];
 let mapl = [];
 for(let i = 0; i < list.length; i++) {
     mapl.push(list[i] * 2);
 }
 //声明式
 let list = [1, 2, 3, 4];
 const double = x => x * 2;
 list.map(double);

示例

  • 命令式(强调的是我们要怎么做,就是How)

  • 声明式(强调的是我们要做什么,就是What,可扩展性更强)

    img

实现一个开关

  • 命令式写法
 switcher.onclick = function(evt){
     if(evt.target.className === 'on'){
         evt.target.className = 'off';
     }else{
         evt.target.className = 'on';
     }
 }
  • 声明式写法
 function toggle(...actions){
   return function(...args){
     let action = actions.shift();
     actions.push(action);
     return action.apply(this, args);
   }
 }
 ​
 switcher.onclick = toggle(
   evt => evt.target.className = 'off',
   evt => evt.target.className = 'on'
 );

当只有两种状态时,似乎没有区别,但是如果开关有【开】【关】【警告】三种情况之后呢?

命令式需要多加一个if判断,但是声明式只需要多传一个参数。

 function toggle(...actions){
   return function(...args){
     let action = actions.shift();
     actions.push(action);
     return action.apply(this, args);
   }
 }
 ​
 switcher.onclick = toggle(
   evt => evt.target.className = 'warn',
   evt => evt.target.className = 'off',
   evt => evt.target.className = 'on'
 );

参考博客: