【第二届字节青训营】写好 JS 原则 - 过程抽象

105 阅读3分钟

写好 JS 的一些原则

  • 各司其责
  • 组件封装
  • 过程抽象

过程抽象

过程抽象用来处理局部细节控制的一些方法,是函数式编程思想的基础应用

如下图:

操作次数限制

对操作次数进行限制一般会在 一些异步交互一次性 HTTP 请求 中使用

例如下面这个例子:

这里就会出现一种情况,当点击第一个 completed 的时候,被点击的组件并没有消失,若快速点击两次,就会报错

当然,你也可以设置 addEventListener 其中的 once 参数,让监听只响应一次

现在自己实现一个 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) => {
  // 这里使用 once 函数
  button.addEventListener('click', once((evt) => {
    const target = evt.target;
    target.parentNode.className = 'completed';
    setTimeout(() => {
      list.removeChild(target.parentNode);
    }, 2000);
  }));
});

现在就不会报错了

为了能够让 “只执行一次” 的需求覆盖不同的事件处理,我们可以将这个需求剥离出来。这个过程称为过程抽象。

高阶函数

高阶函数就是以函数作为参数、以函数作为返回值、常用于作为函数装饰器

// 比如这样的
function HOFO(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)
    }
  }
}

const btn = document.querySelector('#btn')
const circle = document.querySelector('#circle')

btn.onclick = throttle(function (e) {
  circle.innerHTML = parseInt(circle.innerHTML) + 1;
});

防抖 Debounce

这里重点看 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

核心代码:

const isIterable = obj => obj != null
  && typeof obj[Symbol.iterator] === 'function';

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]);
  }
}

纯函数

什么是纯函数?

A pure function is a magic box that always gives you back the same output for a given input.

纯函数就是是一个魔术盒,它总是为给定的输入返回相同的输出

例如这个函数就是一个纯函数:

function add(a, b) {
  return a + b;
}

add(1, 2) // 3
add(1, 2) // 3

所以那些 HOF0 等价范式拓展出来的高阶函数都是纯函数

编程范式

编程范式可以分成 命令式声明式

例如这两段代码

// 命令式
let list = [1, 2, 3, 4];
let map1 = [];
for (let i = 0; i < list.length; i++) {
  map1.push(list[i] * 2)
}

// 声明式
let list = [1, 2, 3, 4]
const double = x => x * 2;
list.map(double)

Toggle 切换状态案例

// 命令式
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'
);

最后

到此,月影老师所说的写好 JS 的三个原则就介绍完了,但是目前看来还得多多练习🤣

之前的文章:

若有不当之处,欢迎评论指出