# 30s源码刨析系列之函数篇

1. 新手级
2. 普通级
3. 专家级

## 正文

### 新手级

#### checkProp

const checkProp = (predicate, prop) => obj => !!predicate(obj[prop]);

const lengthIs4 = checkProp(l => l === 4, 'length');
lengthIs4([]); // false
lengthIs4([1, 2, 3, 4]); // true
lengthIs4(new Set([1, 2, 3, 4])); // false (Set uses Size, not length)

const session = { user: {} };
const validUserSession = checkProp(u => u.active && !u.disabled, 'user');

validUserSession(session); // false

session.user.active = true;
validUserSession(session); // true

const noLength = checkProp(l => l === undefined, 'length');
noLength([]); // false
noLength({}); // true
noLength(new Set()); // true

#### functionName

const functionName = fn => (console.debug(fn.name), fn);

functionName(Math.max); // max (logged in debug channel of console)

API 和函数的 name

#### negate

const negate = func => (...args) => !func(...args);

[1, 2, 3, 4, 5, 6].filter(negate(n => n % 2 === 0)); // [ 1, 3, 5 ]

，我们想要反转其结果，便可对它的调用方式进行进一步的抽象，把反转结果的逻辑放置抽象中。

#### unary

const unary = fn => val => fn(val);

['6', '8', '10'].map(unary(parseInt)); // [6, 8, 10]

### 普通级

#### ary

const ary = (fn, n) => (...args) => fn(...args.slice(0, n));

const firstTwoMax = ary(Math.max, 2);
[[2, 6, 'a'], [6, 4, 8], [10]].map(x => firstTwoMax(...x)); // [6, 6, 1

，只要前n个。

#### attempt

const attempt = (fn, ...args) => {
try {
return fn(...args);
} catch (e) {
return e instanceof Error ? e : new Error(e);
}
};

var elements = attempt(function(selector) {
return document.querySelectorAll(selector);
}, '>_>');
if (elements instanceof Error) elements = []; // elements = []

，即捕获参数函数的异常。

js 实现代码如下:

const attempt = (fn, ...args, count, bound) => {
try {
return fn(...args);
} catch (e) {
if(count == bound){
return e instanceof Error ? e : new Error(e);
}
return attempt(fn, ...args, count + 1, bound)
}
};

#### bind

const bind = (fn, context, ...boundArgs) => (...args) => fn.apply(context, [...boundArgs, ...args]);

function greet(greeting, punctuation) {
return greeting + ' ' + this.user + punctuation;
}
const freddy = { user: 'fred' };
const freddyBound = bind(greet, freddy);
console.log(freddyBound('hi', '!')); // 'hi fred!'

fn.bind(context,...args)
=> bind(fn,context,...args)

bind()

#### bindKey

const bindKey = (context, fn, ...boundArgs) => (...args) =>
context[fn].apply(context, [...boundArgs, ...args]);

const freddy = {
user: 'fred',
greet: function(greeting, punctuation) {
return greeting + ' ' + this.user + punctuation;
}
};
const freddyBound = bindKey(freddy, 'greet');
console.log(freddyBound('hi', '!')); // 'hi fred!'

#### call

const call = (key, ...args) => context => context[key](...args);

Promise.resolve([1, 2, 3])
.then(call('map', x => 2 * x))
.then(console.log); // [ 2, 4, 6 ]
const map = call.bind(null, 'map');
Promise.resolve([1, 2, 3])
.then(map(x => 2 * x))
.then(console.log); // [ 2, 4, 6 ]

const filterMen = call('filter', person => person.sex === 'man')

filterMen([{sex:'woman',...},{sex:'man',...},...])
// 如果有其他 上下文对象，本例中也就是数组 需要相同的 逻辑过滤呢？

#### chainAsync

const chainAsync = fns => {
let curr = 0;
const last = fns[fns.length - 1];
const next = () => {
const fn = fns[curr++];
fn === last ? fn() : fn(next);
};
next();
};

chainAsync([
next => {
console.log('0 seconds');
setTimeout(next, 1000);
},
next => {
console.log('1 second');
setTimeout(next, 1000);
},
() => {
console.log('2 second');
}
]);

；第二个是结束标志，最后一个函数: last

const next = () => {
const fn = fns[curr++];
fn === last ? fn() : fn(next);
};

#### collectInto

const collectInto = fn => (...args) => fn(args);

const Pall = collectInto(Promise.all.bind(Promise));
let p1 = Promise.resolve(1);
let p2 = Promise.resolve(2);
let p3 = new Promise(resolve => setTimeout(resolve, 2000, 3));
Pall(p1, p2, p3).then(console.log); // [1, 2, 3] (after about 2 seconds)

#### compose

const compose = (...fns) => fns.reduce((f, g) => (...args) => f(g(...args)));

const substract3 = x => x - 3;
const add5 = x => x + 5;
const multiply = (x, y) => x * y;
substract3,
multiply
);
multiplyAndAdd5AndSubstract3(5, 2); // 12

compose

reduce 第一次循环:
f: substract3;

reduce 第二次循环：
g: multiply;

(...args) => 第一个函数(第二个函数(第三个函数(...最后一个函数(...args))))

PS: 说实话，我并不喜欢 compose，在上例中就可以很明显的看到缺点。

#### composeRight

const composeRight = (...fns) => fns.reduce((f, g) => (...args) => g(f(...args)));

const add = (x, y) => x + y;
const square = x => x * x;
const substract3 = x => x - 3;
addAndSquareAndSubstract3(1, 2); // 6

#### converge

const converge = (converger, fns) => (...args) => converger(...fns.map(fn => fn.apply(null, args)));

const average = converge((a, b) => a / b, [
arr => arr.reduce((a, v) => a + v, 0),
arr => arr.length
]);
average([1, 2, 3, 4, 5, 6, 7]); // 4

apply

#### curry

const curry = (fn, arity = fn.length, ...args) =>
arity <= args.length ? fn(...args) : curry.bind(null, fn, arity, ...args);

curry(Math.pow)(2)(10); // 1024
curry(Math.min, 3)(10)(50)(2); // 2

)的 函数

#### debounce

const debounce = (fn, ms = 0) => {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => fn.apply(this, args), ms);
};
};

'resize',
debounce(() => {
console.log(window.innerWidth);
console.log(window.innerHeight);
}, 250)
); // Will log the window dimensions at most every 250ms

function + apply

，保证重新计算调用时间。

#### defer

const defer = (fn, ...args) => setTimeout(fn, 1, ...args);

// Example A:
defer(console.log, 'a'), console.log('b'); // logs 'b' then 'a'

// Example B:
document.querySelector('#someElement').innerHTML = 'Hello';
longRunningFunction(); // Browser will not update the HTML until this has finished
defer(longRunningFunction); // Browser will update the HTML then run the function

（超时时间为1ms）将 函数参数 添加到浏览器事件队列末尾。

#### delay

const delay = (fn, wait, ...args) => setTimeout(fn, wait, ...args);

delay(
function(text) {
console.log(text);
},
1000,
'later'
); // Logs 'later' after one second.

defer 的目的是将占据主线程时间长的函数推迟到事件队列。

#### flip

const flip = fn => (first, ...rest) => fn(...rest, first);

let a = { name: 'John Smith' };
let b = {};
const mergeFrom = flip(Object.assign);
let mergePerson = mergeFrom.bind(null, a);
mergePerson(b); // == b
b = {};
Object.assign(b, a); // == b

#### hz

const hz = (fn, iterations = 100) => {
const before = performance.now();
for (let i = 0; i  i);

// Test functions with the same goal: sum up the elements in the array
const sumReduce = () => numbers.reduce((acc, n) => acc + n, 0);
const sumForLoop = () => {
let sum = 0;
for (let i = 0; i < numbers.length; i++) sum += numbers[i];
return sum;
};

// sumForLoop is nearly 10 times faster
Math.round(hz(sumReduce)); // 572
Math.round(hz(sumForLoop)); // 4784

hz是赫兹的单位（频率的单位）定义为每秒一个周期。

PS: 此处，并没有太好的个人理解，翻译自 官方

#### once

const once = fn => {
let called = false;
return function(...args) {
if (called) return;
called = true;
return fn.apply(this, args);
};
};

const startApp = function(event) {
console.log(this, event); // document.body, MouseEvent
};
document.body.addEventListener('click', once(startApp)); // only runs startApp once upon click

package sync

import (
"sync/atomic"
)

type Once struct {
m    Mutex
done uint32
}

func (o *Once) Do(f func()) {
return
}
o.m.Lock()
defer o.m.Unlock()
if o.done == 0 {
defer atomic.StoreUint32(&o.done, 1)
f()
}
}

#### over

const over = (...fns) => (...args) => fns.map(fn => fn.apply(null, args));

const minMax = over(Math.min, Math.max);
minMax(1, 2, 3, 4, 5); // [1,5]

apply

#### overArgs

const overArgs = (fn, transforms) => (...args) => fn(...args.map((val, i) => transforms[i](val)));

const square = n => n * n;
const double = n => n * 2;
const fn = overArgs((x, y) => [x, y], [square, double]);
fn(9, 3); // [81, 6]

#### partial

const partial = (fn, ...partials) => (...args) => fn(...partials, ...args);

const greet = (greeting, name) => greeting + ' ' + name + '!';
const greetHello = partial(greet, 'Hello');
greetHello('John'); // 'Hello John!'

#### partialRight

const partialRight = (fn, ...partials) => (...args) => fn(...args, ...partials);

const greet = (greeting, name) => greeting + ' ' + name + '!';
const greetJohn = partialRight(greet, 'John');
greetJohn('Hello'); // 'Hello John!'

#### pipeAsyncFunctions

const pipeAsyncFunctions = (...fns) => arg => fns.reduce((p, f) => p.then(f), Promise.resolve(arg));

const sum = pipeAsyncFunctions(
x => x + 1,
x => new Promise(resolve => setTimeout(() => resolve(x + 2), 1000)),
x => x + 3,
async x => (await x) + 4
);
(async () => {
console.log(await sum(5)); // 15 (after one second)
})();

Promise.then
，将数据按照正序传递到每个[异步]函数,进行处理，处理的结果又传给下一个[异步]函数，以此类推。

#### promisify

const promisify = func => (...args) =>
new Promise((resolve, reject) =>
func(...args, (err, result) => (err ? reject(err) : resolve(result)))
);

const delay = promisify((d, cb) => setTimeout(cb, d));
delay(2000).then(() => console.log('Hi!')); // // Promise resolves after 2s

util.promisify

#### rearg

const rearg = (fn, indexes) => (...args) => fn(...indexes.map(i => args[i]));

var rearged = rearg(
function(a, b, c) {
return [a, b, c];
},
[2, 0, 1]
);
rearged('b', 'c', 'a'); // ['a', 'b', 'c']

#### runPromisesInSeries

const runPromisesInSeries = ps => ps.reduce((p, next) => p.then(next), Promise.resolve());

const delay = d => new Promise(r => setTimeout(r, d));
runPromisesInSeries([() => delay(1000), () => delay(2000)]);
// Executes each promise sequentially, taking a total of 3 seconds to complete

，从而进行下一次调用。

#### sleep

const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));

async function sleepyWork() {
console.log("I'm going to sleep for 1 second.");
await sleep(1000);
console.log('I woke up after 1 second.');
}

，在给定的毫秒数后，返回一个 resolve

const spreadOver = fn => argsArr => fn(...argsArr);

arrayMax([1, 2, 3]); // 3

#### times

const times = (n, fn, context = undefined) => {
let i = 0;
while (fn.call(context, i) !== false && ++i  (output += i));
console.log(output); // 01234

#### uncurry

const uncurry = (fn, n = 1) => (...args) => {
const next = acc => args => args.reduce((x, y) => x(y), acc);
if (n > args.length) throw new RangeError('Arguments too few!');
return next(fn)(args.slice(0, n));
};

const add = x => y => z => x + y + z;
uncurriedAdd(1, 2, 3); // 6

)的 函数

在上例中,
args: [1,2,3]
acc: x => y => z => x + y + z

x：x => y => z => x + y + z
y：1

x: y => z => 1 + y + z
y: 2

#### unfold

const unfold = (fn, seed) => {
let result = [],
val = [null, seed];
while ((val = fn(val[1]))) result.push(val[0]);
return result;
};

var f = n => (n > 50 ? false : [-n, n + 10]);
unfold(f, 10); // [-10, -20, -30, -40, -50]

#### when

const when = (pred, whenTrue) => x => (pred(x) ? whenTrue(x) : x);

const doubleEvenNumbers = when(x => x % 2 === 0, x => x * 2);
doubleEvenNumbers(2); // 4
doubleEvenNumbers(1); // 1

### 专家级

#### memoize

const memoize = fn => {
const cache = new Map();
const cached = function(val) {
return cache.has(val) ? cache.get(val) : cache.set(val, fn.call(this, val)) && cache.get(val);
};
cached.cache = cache;
return cached;
};

// See the anagrams snippet.
const anagramsCached = memoize(anagrams);
anagramsCached('javascript'); // takes a long time
anagramsCached('javascript'); // returns virtually instantly since it's now cached
console.log(anagramsCached.cache); // The cached anagrams map

#### throttle

const throttle = (fn, wait) => {
let inThrottle, lastFn, lastTime;
return function() {
const context = this,
args = arguments;
if (!inThrottle) {
fn.apply(context, args);
lastTime = Date.now();
inThrottle = true;
} else {
clearTimeout(lastFn);
lastFn = setTimeout(function() {
if (Date.now() - lastTime >= wait) {
fn.apply(context, args);
lastTime = Date.now();
}
}, Math.max(wait - (Date.now() - lastTime), 0));
}
};
};

'resize',
throttle(function(evt) {
console.log(window.innerWidth);
console.log(window.innerHeight);
}, 250)
); // Will log the window dimensions at most every 250ms

，请勿用于任何商业用途。