【青训营】写好JS——过程抽象

简介: 【青训营】写好JS——过程抽象

过程抽象是⽤来处理局部细节控制的⼀些⽅法,是函数式编程思想的基础应⽤。


image.png


一个例子:Todo List



实际业务中我们经常需要限制用户的操作次数,比如一次性的HTTP请求,以及一些异步交互。


image.png



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);
  });
});
复制代码


如图,我们的todo list在点击完成时会有一个2秒钟的淡出动画,但是如果用户在动画未结束时,又去点击该按钮,就会报一个错:


image.png


所以我们要让函数只执行一次,同时为了让这个只执行一次的需求覆盖不同的事件处理,我们可以将这个需求剥离出来,也就是过程抽象


function once(fn) {
  return function (...args) {
    if (fn) {
      const ret = fn.apply(this, args);
      fn = null;
      return ret;
    }
  };
}
复制代码


我们向once()中传入一个函数,在返回值中运行并把它置为null,这样我们就用once本身的闭包实现了该功能,在button中调用即可:


buttons.forEach((button) => {
  button.addEventListener('click', once((evt) => {
    const target = evt.target;
    target.parentNode.className = 'completed';
    setTimeout(() => {
      list.removeChild(target.parentNode);
    }, 2000);
  }));
});
复制代码


之后任何只能执行一次的函数都可以在外面包一层once()来实现,这样的函数也叫做高阶函数。


高阶函数



once()一样以函数作为参数,而且返回值也是函数的函数叫做高阶函数,也常作为函数装饰器使用。


Higher-Order Function中有一个等价范式HOF0,调用fnHOF0(fn)是完全等价的,其他的高阶函数都是基于这个范式做了一些拓展:


function HOF0(fn) {
  return function(...args) {
    return fn.apply(this, args);
  }
}
复制代码


下面会介绍一些常见的高阶函数:


节流 Throttle


当持续触发事件时,保证一定时间段内只调用一次事件处理函数。


function throttle(fn, time = 500) {
  let timer;
  return function (...args) {
    if (timer == null) {
      fn.apply(this, args);
      timer = setTimeout(() => {
        timer = null;
      }, time)
    }
  }
}
复制代码


防抖 Debounce


当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定的时间到来之前,又一次触发了事件,就重新开始延时。


function debounce(fn, dur) {
  dur = dur || 100;
  var timer;
  return function () {
    clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(this, arguments);
    }, dur);
  }
}
复制代码


Consumer


function consumer(fn, time) {
  let tasks = [],
      timer;
  return function (...args) {
    tasks.push(fn.bind(this, ...args));
    if (timer == null) {
      timer = setInterval(() => {
        tasks.shift().call(this)
        if (tasks.length <= 0) {
          clearInterval(timer);
          timer = null;
        }
      }, time)
    }
  }
}
复制代码


Iterative


function iterative(fn) {
  return function (subject, ...rest) {
    if (isIterable(subject)) {
      const ret = [];
      for (let obj of subject) {
        ret.push(fn.apply(this, [obj, ...rest]));
      }
      return ret;
    }
    return fn.apply(this, [subject, ...rest]);
  }
}
复制代码


纯函数



上文一直在说高阶函数,但是我们为什么要使用高阶函数呢?这里就需要知道纯函数的概念。


纯函数需要满足以下三点:


  • 相同输入返回相同输出
  • 无副作用
  • 不依赖于外部状态


也就是说【一个函数不依赖于上下文,不管什么时候调用,调用多少次,只要输入相同,输出就是相同的,这样的函数就是纯函数】。从这就可以看出,高阶函数都是纯函数

举个例子:


// 纯函数
function add(a, b) {
  return a + b;
}
// 非纯函数
let a = 6;
function add(b) {
  return a + b;
}
复制代码


可以看出第二个函数,a改变时,输出就改变了,所以它不是纯函数。


纯函数的优势在于我们不需要上下文就可以直接进行单元测试,如果非纯函数,我们还需要构建上下文环境,所以我们要多写纯函数,多写高阶函数


编程范式



主要的编程范式分为两种:命令式和声明式,其中进一步细分面向过程,面向对象,逻辑式以及函数式编程。

命令式编程的主要思想是关注计算机执行的步骤,一步一步告诉计算机先做什么再做什么,就是关注怎么做(How)。

声明式编程是以数据结构的形式来表达程序执行的逻辑,它的主要思想是关注做什么(What),但不指定具体要怎么做。

JS既可以写命令式的代码,也可以写声明式的代码,处理复杂逻辑时,推荐使用声明式。


image.png


一个例子:Toggle


画一个开关,点击切换开关状态。


image.png


命令式


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'
);
复制代码

三态


声明式非常利于扩展,如果有新的需求只需要再加一个状态即可:


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'
);


目录
相关文章
|
2月前
|
存储 前端开发 JavaScript
【面试题】前端 js那些需要注意的小细节
【面试题】前端 js那些需要注意的小细节
|
JavaScript
js基础笔记学习27-逻辑非
js基础笔记学习27-逻辑非
53 0
js基础笔记学习27-逻辑非
|
JavaScript
js基础笔记学习109-封装2
js基础笔记学习109-封装2
51 0
js基础笔记学习109-封装2
|
JavaScript
js基础笔记学习108-封装1
js基础笔记学习108-封装1
59 0
js基础笔记学习108-封装1
|
JavaScript
js基础笔记学习150-封装函数2
js基础笔记学习150-封装函数2
36 0
js基础笔记学习150-封装函数2
|
JavaScript
js基础笔记学习149-封装函数1
js基础笔记学习149-封装函数1
49 0
js基础笔记学习149-封装函数1
|
JavaScript
js基础笔记学习297核心函数3
js基础笔记学习297核心函数3
40 0
js基础笔记学习297核心函数3
|
JavaScript
js基础笔记学习295核心函数1
js基础笔记学习295核心函数1
54 0
js基础笔记学习295核心函数1
|
JavaScript
js基础笔记学习296核心函数2
js基础笔记学习296核心函数2
67 0
js基础笔记学习296核心函数2
|
JavaScript
JS之用面向对象和传统过程式编程
改写成面向对象时,可先将普通函数变型,再改成面向对象。 普通方法变型 普通方法变型,变型有3个原则 1)尽量不要出现函数嵌套函数 2)可以用全局变量 3)把onload中不是赋值的语句放到单独函数中 如下代码中,完成了普通函数的变型。
85 0
JS之用面向对象和传统过程式编程