JS高阶函数小结 | 青训营笔记

93 阅读4分钟

这是我参与「第五届青训营」笔记创作活动的第6天。这里记录下JS阶段学到的高阶函数。在React中,高阶函数的应用是非常普遍的,于vue可以在事件绑定中直接绑定事件并传入参数不同,React要求事件绑定的必须是一个函数。因此在需要给函数传参时,必须要写成高阶函数的形式。

一、本堂课重点内容:

JS高阶函数

二、详细知识点介绍:

JS中的高阶函数——HOF。

HOF

高阶函数的特点主要有以下几点:

  • 以函数作为参数
  • 以函数作为返回值
  • 常作为函数的装饰器

下面这些代码是高阶函数的模板。返回的函数和作为参数的fn是等价的。

function HOF0(fn) {
    // 返回一个函数。当这个函数执行的时候,传入的参数就是这里的..args
    // 比如说当绑定给 onclick 事件时,得到的参数就是 MouseEvent 事件类型
    // 因为触发了事件调用,就好比是 HOF0(fn)(),那么事件自然就作为参数填入了第二个括号
    return function(...args) {
        return fn.apply(this, args);
    }
}

常见的高阶函数

事件只执行一次函数 Once

这个就是对高阶函数和JS函数闭包closure最基本的利用。利用函数闭包保存了作为参数传入once的真正函数func的词法环境,在执行一次func后立即将函数func置为null,确保其只会执行一次。

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

const foo = once(() => {
  console.log('bar');
});

节流阀函数 throttle

节流阀函数的核心就是 在数据一次变化/某事件一次触发后 进行一段时间的冷却,在冷却期间任何对于数据尝试性的变化/对事件尝试性的触发都不生效。其本质是:确保在一段时间内,数据只变化一次/事件只能被有效触发一次,可以理解为“冷却”。

节流阀函数的一个典型的应用是 防止按钮的多次点击——比如说B站发送弹幕的“冷却”时间,避免机器人恶意刷屏。

// 节流函数
function throttle(fn, time = 500){
  let timer;
  return function(...args){
    if(timer == null){
      fn.apply(this,  args);
      timer = setTimeout(() => {
        timer = null;
      }, time)
    }
  }
}
​
btn.onclick = throttle(function(e){
  circle.innerHTML = parseInt(circle.innerHTML) + 1;
  circle.className = 'fade';
  setTimeout(() => circle.className = '', 250);
});

防抖函数 debounce

防抖的核心思想就是 等持续变化的数据停止变化/频繁触发的事件停止再次被触发 一小段时间后,再执行函数真正的逻辑。其实现和防抖不同,本质上是 在每一次检测的数据发生改变时,新建一个计时器或者重置之前已有的计时器,除非计时器计时归零时,数据没有发生下一次的变化,此时才执行函数真正的逻辑。 可以理解为:允许不断触发事件,但是每次触发事件都会“刷新读条”,直到“读条结束”才会执行函数真正的逻辑。

防抖函数应用于诸如 输入框输入时、检测鼠标移动时 等情景。个人感觉其特点是:所监测的数据高速变化,而我们只对数据变化的阶段性结果感兴趣,不追求记录数据的瞬时变化。

var i = 0;
setInterval(function(){
    bird.className = "sprite " + 'bird' + ((i++) % 3);
}, 1000/10);
// 防抖函数
function debounce(fn, dur){
    dur = dur || 100;
    var timer;
    return function(){
        // 很明显的重置计时器的操作
        clearTimeout(timer);
        timer = setTimeout(() => {
            fn.apply(this, arguments);
        }, dur);
    }
}
​
document.addEventListener('mousemove', debounce(function(evt){
    var x = evt.clientX,
        y = evt.clientY,
        x0 = bird.offsetLeft,
        y0 = bird.offsetTop;
​
    console.log(x, y);
​
    var a1 = new Animator(1000, function(ep){
        bird.style.top = y0 + ep * (y - y0) + 'px';
        bird.style.left = x0 + ep * (x - x0) + 'px';
    }, p => p * p);
​
    a1.animate();
}, 100));

延时函数 Consumer

延时函数和防抖函数不同,防抖只关心在频繁变化后的阶段性结果,而延时函数如同“记账”一般,无论触发了多少次事件,都会以一定的时间间隔一次次执行完毕

function consumer(fn, time){
    let tasks = [], // 用于记录任务的“账本”
        timer;      // 执行一次函数主体逻辑的间隔
​
    return function(...args){
        // 利用bind()函数,将参数args作为fn的参数,并将这个拥有了参数的新函数存入数组
        tasks.push(fn.bind(this, ...args));
        // 每隔一个时间间隔执行一次
        if(timer == null){
            timer = setInterval(() => {
                tasks.shift().call(this)
                if(tasks.length <= 0){
                    clearInterval(timer);
                    timer = null;
                }
            }, time)
        }
    }
}
​
btn.onclick = consumer((evt)=>{
    let t = parseInt(count.innerHTML.slice(1)) + 1;
    count.innerHTML = `+${t}`;
    count.className = 'hit';
    let r = t * 7 % 256,
        g = t * 17 % 128,
        b = t * 31 % 128;
​
    count.style.color = `rgb(${r},${g},${b})`.trim();
    setTimeout(()=>{
        count.className = 'hide';
    }, 500);
}, 800)

迭代批量操作函数

这里主要是为了实现自定义的迭代操作。

const isIterable = obj => obj != null 
  && typeof obj[Symbol.iterator] === 'function';
​
function iterative(fn) {
  // 如果说传给函数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]);
  }
}
​
const setColor = iterative((el, color) => {
  el.style.color = color;
});
​
const els = document.querySelectorAll('li:nth-child(2n+1)');
setColor(els, 'red');

三、实践练习例子:

实践的例子在上面已经列出。

四、课后个人总结:

本章的知识点需要大量的实例和参考资料来辅助理解。

五、引用参考:

我主要是基于老师讲解提供的代码仓库进行理解和分析,并记录了自己的心得。