本篇按顺序看R
开头的几个零散的小方法,包括reject
、remove
、repeat
、replace
、result
、round
。先分析 lodash 中对变参函数的处理,最后给出几个方法的原生实现。
对变参的处理
这几个方法中random
实现比较有意思,分以下几种情况:
- 当不传参数时,返回
[ 0, 1 ]
之间的整数;
- 当传
1
个参数时,
- 数字,返回
[ 0, arguments[0] ]
;
- 布尔值,根据真假返回
[ 0, 1 ]
之间的整数或浮点数;
- 传
2
个参数时,
- 两个数字,返回
[arguments[0], arguments[1] ]
之间的随机整数;
- 一个数字和一个布尔值,根据真假返回
[ 0, arguments[0] ]
之间的整数或浮点数;
- 当传
3
个参数时,前两个数字,后一个布尔值,根据arguments[2]
决定返回[arguments[0], arguments[1] ]
之间的整数或者浮点数。
lodash
实现时的思路如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| function random(lower, upper, floating) { if (floating === undefined) { if (typeof upper === 'boolean') { floating = upper; upper = undefined; } else if (typeof lower === 'boolean') { floating = lower; lower = undefined; } }
if (lower === undefined && upper === undefined) { lower = 0; upper = 1; } else { lower = toFinite(lower); if (upper === undefined) { upper = lower; lower = 0; } else { upper = toFinite(upper); } } if (lower > upper) { const temp = lower; lower = upper; upper = temp; }
if (floating || lower % 1 || upper % 1) { const rand = Math.random(); const randLength = `${rand}`.length - 1; return Math.min( lower + rand * (upper - lower + freeParseFloat(`1e-${randLength}`)), upper ); } return lower + Math.floor(Math.random() * (upper - lower + 1)); }
|
上述的 lodash
参数处理百转千回,如果是我处理可能按照之前分类的情况写,用参数长度(length
)和类型(typeof
)来区分不同的情况:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| function random(...args) { let lower, upper, floating; const length = args.length; if (length === 0) { lower = 0; upper = 1; floating = false; } else if (length === 1) { lower = 0; if (typeof args[0] === 'boolean') { upper = 1; floating = args[0]; } else { upper = args[0]; floating = false; } } else if (length === 2) { if (typeof args[1] === 'boolean') { lower = 0; upper = args[0]; floating = args[1]; } else { lower = args[0]; upper = args[1]; floating = false; } } else if (length >= 3) { lower = args[0]; upper = args[1]; floating = args[2]; } lower = toFinite(lower); upper = toFinite(upper); if (lower > upper) { const temp = lower; lower = upper; upper = temp; }
if (floating || lower % 1 || upper % 1) { const rand = Math.random(); const randLength = `${rand}`.length - 1; return Math.min( lower + rand * (upper - lower + freeParseFloat(`1e-${randLength}`)), upper ); } return lower + Math.floor(Math.random() * (upper - lower + 1)); }
|
试验了下,和lodash
的random
执行结果一致
依赖的 lodash 方法
negate
negate
是一个参数类型为function
,返回结果类型也是function
的函数,最终目的就是对参数返回的结果取反。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
function negate(predicate) { if (typeof predicate !== 'function') { throw new TypeError('Expected a function'); } return function (...args) { return !predicate.apply(this, args); }; }
export default negate;
|
filter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
|
function filter(array, predicate) { let index = -1; let resIndex = 0; const length = array == null ? 0 : array.length; const result = [];
while (++index < length) { const value = array[index]; if (predicate(value, index, array)) { result[resIndex++] = value; } } return result; }
export default filter;
|
filterObject
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
|
function filterObject(object, predicate) { object = Object(object); const result = [];
Object.keys(object).forEach((key) => { const value = object[key]; if (predicate(value, key, object)) { result.push(value); } }); return result; }
export default filterObject;
|
lodash 方法
random
random
方法实际上是对Math.random
的封装,可以返回固定范围内的整数或小数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
| import toFinite from './toFinite.js';
const freeParseFloat = parseFloat;
function random(lower, upper, floating) { if (floating === undefined) { if (typeof upper === 'boolean') { floating = upper; upper = undefined; } else if (typeof lower === 'boolean') { floating = lower; lower = undefined; } } if (lower === undefined && upper === undefined) { lower = 0; upper = 1; } else { lower = toFinite(lower); if (upper === undefined) { upper = lower; lower = 0; } else { upper = toFinite(upper); } } if (lower > upper) { const temp = lower; lower = upper; upper = temp; } if (floating || lower % 1 || upper % 1) { const rand = Math.random(); const randLength = `${rand}`.length - 1; return Math.min( lower + rand * (upper - lower + freeParseFloat(`1e-${randLength}`)), upper ); } return lower + Math.floor(Math.random() * (upper - lower + 1)); }
export default random;
|
reject
reject
是利用了negate
实现的filter
的相反方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| import filter from './filter.js'; import filterObject from './filterObject.js'; import negate from './negate.js';
function reject(collection, predicate) { const func = Array.isArray(collection) ? filter : filterObject; return func(collection, negate(predicate)); }
export default reject;
|
remove
remove
方法在原数组中删除元素,并返回被删掉的元素组成的数组。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| import basePullAt from './.internal/basePullAt.js';
function remove(array, predicate) { const result = []; if (!(array != null && array.length)) { return result; } let index = -1; const indexes = []; const { length } = array;
while (++index < length) { const value = array[index]; if (predicate(value, index, array)) { result.push(value); indexes.push(index); } } basePullAt(array, indexes); return result; }
export default remove;
|
repeat
repeat
方法利用了平方求幂算法进行了快速重复。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
|
function repeat(string, n) { let result = ''; if (!string || n < 1 || n > Number.MAX_SAFE_INTEGER) { return result; } do { if (n % 2) { result += string; } n = Math.floor(n / 2); if (n) { string += string; } } while (n);
return result; }
export default repeat;
|
replace
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
|
function replace(...args) { const string = `${args[0]}`; return args.length < 3 ? string : string.replace(args[1], args[2]); }
export default replace;
|
result
result
是一个类似get
的方法,实现的方式很有意思,尽量少的定义变量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| import castPath from './.internal/castPath.js'; import toKey from './.internal/toKey.js';
function result(object, path, defaultValue) { path = castPath(path, object);
let index = -1; let length = path.length;
if (!length) { length = 1; object = undefined; } while (++index < length) { let value = object == null ? undefined : object[toKey(path[index])]; if (value === undefined) { index = length; value = defaultValue; } object = typeof value === 'function' ? value.call(object) : value; } return object; }
export default result;
|
round
round
方法是实现带精度的四舍五入,本质上利用的是createRound
这个方法实现的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| import createRound from './.internal/createRound.js';
const round = createRound('round');
export default round;
|
createRound()
方法,参数可以为floor
,ceil
,round
,返回不同的近似函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
|
function createRound(methodName) { const func = Math[methodName]; return (number, precision) => { precision = precision == null ? 0 : precision >= 0 ? Math.min(precision, 292) : Math.max(precision, -292); if (precision) {
let pair = `${number}e`.split('e'); const value = func(`${pair[0]}e${+pair[1] + precision}`); pair = `${value}e`.split('e'); return +`${pair[0]}e${+pair[1] - precision}`; } return func(number); }; }
export default createRound;
|
上面的情况说明的是带 e 显示的浮点数,下面再看看不带 e 的处理过程。
1 2 3 4 5 6
| let pair = `${number}e`.split('e'); const value = func(`${pair[0]}e${+pair[1] + precision}`); pair = `${value}e`.split('e');
return +`${pair[0]}e${+pair[1] - precision}`;
|
原生实现
repeat
、replace
和round
是ECMAScript
中 String.prototype
原生已经实现了的,直接使用即可。remove
和result
原生实现时也得和lodash
差不多的思路。下面直接分析下 reject
的原生实现。
reject
reject
方法是一个类似 filter
的方法,只不过结果与 filter
完全相反。所以可以通过创建一个 complement
函数,该函数接收一个函数 f
作为参数,返回一个新的函数
。新的函数
执行时返回的结果其实是!f(...args)
,实现了功能。
1 2 3 4
| const reject = function (arr, predicate) { const complement = (f) => (...args) => !f(...args); return arr.filter(complement(predicate)); };
|