一. 函数调度

所谓调度,就是写 函数 控制 其他函数的执行。

1. throttle 和 debounce 实现

lodash 源码解析好文参考:https://juejin.im/post/6844903536157786120#heading-14
这篇文章的行文安排有些臃肿,另外在源码解释部分太惜字,导致第一次阅读认为是低水平文章;自己实现了一遍之后,再去读此文感觉好多了。

下面是我自己写的:

  1. <div><button id="btn">点击我</button></div>
  1. var i = 0;
  2. var button = document.getElementById('btn');
  3. button.onclick = debounce(function tt(event) {
  4. console.log(`我被点了${++i}次`);
  5. }, 1000);
  6. // button.onclick = throttle(function tt(event) {
  7. // console.log(`我被点了${++i}次`);
  8. // }, 1000);

debounce 的实现:

  1. function debounce2(fn, time) { // time 是 毫秒 单位
  2. let preTimer = null;
  3. return function _wrap(...args) {
  4. let now = Date.now();
  5. preTimer && clearTimeout(preTimer);
  6. preTimer = setTimeout(() => {
  7. fn.apply(this, args); // 这里 为什么第一个参数是 this, 因为 wrap 函数才是最终被调用的,所以要用箭头函数保证 setTimeout 外边的(也就是wrap函数的)this 是相同的。
  8. }, time); // 时间也换成了 time
  9. };
  10. }

throttle 的实现 和 debounce 异曲同工:

  1. function throttle(fn, time) { // 不涉及 定时器,只是简单的执行过滤
  2. let pre;
  3. return function(...args) {
  4. let now = Date.now();
  5. if (!pre) {
  6. pre = now;
  7. fn.apply(this, args);
  8. } else if ((now - pre) >= time) {
  9. pre = now;
  10. fn.apply(this, args);
  11. }
  12. }
  13. }

小结:无论是 throttle 还是 debounce 其实都可以去理解为一种“调度”!

2. react 调度算法

主要依赖 一个数据结构,最小堆,每次执行任务,或是安排新任务都会涉及到 最小堆 的变化。另外,任务可以中断:可以先执行再恢复。

3. epress 中间件 [node]

知乎有一些说的很好玩的东西,比如说“流水线”的传说:https://www.zhihu.com/question/37693420
下面是根据人家的提示,写出来的:

  1. const app = {
  2. fns: [],
  3. use(fn) {
  4. this.fns.push(fn);
  5. },
  6. go(ctx) {
  7. let start = 0;
  8. const runQueue = this.fns.slice(0);
  9. next();
  10. function next() {
  11. let runUnit = runQueue[start];
  12. start++; // 位置很重要!
  13. if (runUnit) {
  14. runUnit(ctx, next);
  15. }
  16. // start++;
  17. }
  18. },
  19. };
  20. app.use((ctx, next) => {
  21. ctx.name = 'lucy';
  22. next();
  23. });
  24. app.use((ctx, next) => {
  25. ctx.age = '16';
  26. next();
  27. });
  28. app.use((ctx, next) => {
  29. console.log(`${ctx.name} is ${ctx.age} years old;`);
  30. next();
  31. });
  32. app.go({});

4. 执行队列的实现

一个操作分成很多步,如果按照正常写法,得把人写死了。
image.png
写法:

  1. var a = (data) => {
  2. return new Promise((resolve, reject) => {
  3. resolve(data + ' a');
  4. });
  5. };
  6. var b = (data) => {
  7. return new Promise((resolve, reject) => {
  8. resolve(data + ' b');
  9. });
  10. };
  11. var c = (data) => {
  12. return new Promise((resolve, reject) => {
  13. resolve(data+ ' c');
  14. });
  15. };
  16. var d = [a, b, c].reduce((pre, cur) => {
  17. return pre.then((args) => {
  18. return cur.call(this, args);
  19. });
  20. }, Promise.resolve());
  21. d.then(data => {
  22. console.log(data);
  23. });

结果:
image.png
reduce 的写法导致这段不太容易看懂,分解看下:

pre cur return
o = Promise.resolve(); a o.then(args => {
return a.call(this, args)
})
o.then(args => {
return a.call(this, args)
})
b o.then(args => {
return a.call(this, args)
}).then(args => {
retrun b.call(this, args)
})
o.then(args => {
return a.call(this, args)
}).then(args => {
retrun b.call(this, args)
})
c o.then(args => {
return a.call(this, args)
}).then(args => {
retrun b.call(this, args)
}).then(args => {
return c.call(this, args)
})
o.then(args => {
return a.call(this, args)
}).then(args => {
retrun b.call(this, args)
}).then(args => {
return c.call(this, args)
})

其实一上来看不懂 reduce:抽象能力差!

  1. js 插件系统

这个 和 node 中间件 有类似但不同,共同之处,都在于如何扩展和解耦。
参考https://juejin.im/post/6871077497044205575

二. 其他的变态想法

1. bind apply call 的实现

???(补上)

2. 柯力化

  1. function add(x, y, z) {
  2. return x + y + z;
  3. }
  4. const c_fn = cury(add);
  5. c_fn(1,2,3)
  6. c_fn(1)(2,3)

实现 cury 函数:(cury 可以认为是一个 入参收集器)

  1. function cury(fn) {
  2. let _arguments = [];
  3. return function tt(...args) { // 这个写法非常屌
  4. if (args.length >= (fn.length - _arguments.length)) { // function.length 是提取函数定义的入参的个数
  5. _arguments = _arguments.concat(args);
  6. _arguments = _arguments.slice(0, fn.length);
  7. const tmp_args = _arguments.slice(0);
  8. _arguments = [];
  9. return fn(...tmp_args); // 把数组转化为函数入参
  10. } else {
  11. _arguments = _arguments.concat(args);
  12. return tt;
  13. }
  14. };
  15. }

但是别人的版本,显然很高级:(参考

  1. function currying(fn, ...args) {
  2. if (args.length >= fn.length) {
  3. return fn(...args);
  4. } else {
  5. return (...args2) => currying(fn, ...args, ...args2);
  6. }
  7. }

柯力化并不陌生,在 react 源码 里 也看到 用 bind 实现的柯力化:在定义的地方预先传入几个参数,在使用的时候将剩下的参数传入,这样可以保证被封装的函数的复用性大大增强!