一. 函数调度
1. throttle 和 debounce 实现
lodash 源码解析好文参考:https://juejin.im/post/6844903536157786120#heading-14
这篇文章的行文安排有些臃肿,另外在源码解释部分太惜字,导致第一次阅读认为是低水平文章;自己实现了一遍之后,再去读此文感觉好多了。
下面是我自己写的:
<div><button id="btn">点击我</button></div>
var i = 0;var button = document.getElementById('btn');button.onclick = debounce(function tt(event) {console.log(`我被点了${++i}次`);}, 1000);// button.onclick = throttle(function tt(event) {// console.log(`我被点了${++i}次`);// }, 1000);
debounce 的实现:
function debounce2(fn, time) { // time 是 毫秒 单位let preTimer = null;return function _wrap(...args) {let now = Date.now();preTimer && clearTimeout(preTimer);preTimer = setTimeout(() => {fn.apply(this, args); // 这里 为什么第一个参数是 this, 因为 wrap 函数才是最终被调用的,所以要用箭头函数保证 setTimeout 外边的(也就是wrap函数的)this 是相同的。}, time); // 时间也换成了 time};}
throttle 的实现 和 debounce 异曲同工:
function throttle(fn, time) { // 不涉及 定时器,只是简单的执行过滤let pre;return function(...args) {let now = Date.now();if (!pre) {pre = now;fn.apply(this, args);} else if ((now - pre) >= time) {pre = now;fn.apply(this, args);}}}
小结:无论是 throttle 还是 debounce 其实都可以去理解为一种“调度”!
2. react 调度算法
主要依赖 一个数据结构,最小堆,每次执行任务,或是安排新任务都会涉及到 最小堆 的变化。另外,任务可以中断:可以先执行再恢复。
3. epress 中间件 [node]
知乎有一些说的很好玩的东西,比如说“流水线”的传说:https://www.zhihu.com/question/37693420
下面是根据人家的提示,写出来的:
const app = {fns: [],use(fn) {this.fns.push(fn);},go(ctx) {let start = 0;const runQueue = this.fns.slice(0);next();function next() {let runUnit = runQueue[start];start++; // 位置很重要!if (runUnit) {runUnit(ctx, next);}// start++;}},};app.use((ctx, next) => {ctx.name = 'lucy';next();});app.use((ctx, next) => {ctx.age = '16';next();});app.use((ctx, next) => {console.log(`${ctx.name} is ${ctx.age} years old;`);next();});app.go({});
4. 执行队列的实现
一个操作分成很多步,如果按照正常写法,得把人写死了。
写法:
var a = (data) => {return new Promise((resolve, reject) => {resolve(data + ' a');});};var b = (data) => {return new Promise((resolve, reject) => {resolve(data + ' b');});};var c = (data) => {return new Promise((resolve, reject) => {resolve(data+ ' c');});};var d = [a, b, c].reduce((pre, cur) => {return pre.then((args) => {return cur.call(this, args);});}, Promise.resolve());d.then(data => {console.log(data);});
结果:
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:抽象能力差!
- js 插件系统
这个 和 node 中间件 有类似但不同,共同之处,都在于如何扩展和解耦。
参考https://juejin.im/post/6871077497044205575
二. 其他的变态想法
1. bind apply call 的实现
2. 柯力化
function add(x, y, z) {return x + y + z;}const c_fn = cury(add);c_fn(1,2,3)c_fn(1)(2,3)
实现 cury 函数:(cury 可以认为是一个 入参收集器)
function cury(fn) {let _arguments = [];return function tt(...args) { // 这个写法非常屌if (args.length >= (fn.length - _arguments.length)) { // function.length 是提取函数定义的入参的个数_arguments = _arguments.concat(args);_arguments = _arguments.slice(0, fn.length);const tmp_args = _arguments.slice(0);_arguments = [];return fn(...tmp_args); // 把数组转化为函数入参} else {_arguments = _arguments.concat(args);return tt;}};}
但是别人的版本,显然很高级:(参考)
function currying(fn, ...args) {if (args.length >= fn.length) {return fn(...args);} else {return (...args2) => currying(fn, ...args, ...args2);}}
柯力化并不陌生,在 react 源码 里 也看到 用 bind 实现的柯力化:在定义的地方预先传入几个参数,在使用的时候将剩下的参数传入,这样可以保证被封装的函数的复用性大大增强!
