技术
67 分钟
Loading...
Published on 2026-05-21
详解map/filter/reduce的函数式本质、数组方法链式优化问题、Transducer组合算法、惰性求值及memoize缓存策略

函数式工具库(如 Lodash/fp、Ramda)的核心是一组经过精心设计的高阶函数。本文从零实现这些工具的核心,包括深克隆、深比较、去抖节流、惰性求值链等,并分析其在现代 JavaScript 开发中的取舍。
// 代码示例 1:核心集合工具
const _ = {
// groupBy
groupBy(arr, keyFn) {
return arr.reduce((groups, item) => {
const key = typeof keyFn === 'function' ? keyFn(item) : item[keyFn];
(groups[key] ||= []).push(item);
return groups;
}, {});
},
// chunk
chunk(arr, size) {
const result = [];
for (let i = 0; i < arr.length; i += size) {
result.push(arr.slice(i, i + size));
}
return result;
},
// uniqBy
uniqBy(arr, keyFn) {
const seen = new Set();
return arr.filter(item => {
const key = typeof keyFn === 'function' ? keyFn(item) : item[keyFn];
if (seen.has(key)) return false;
seen.add(key);
return true;
});
},
// flatMap (with depth)
flatMapDeep(arr, fn = x => x, depth = Infinity) {
return arr.reduce((acc, item) => {
const mapped = fn(item);
if (Array.isArray(mapped) && depth > 0) {
acc.push(...this.flatMapDeep(mapped, x => x, depth - 1));
} else {
acc.push(mapped);
}
return acc;
}, []);
},
// intersection
intersection(...arrays) {
if (arrays.length === 0) return [];
const sets = arrays.map(a => new Set(a));
return [...sets[0]].filter(item => sets.every(s => s.has(item)));
},
// difference
difference(arr, ...excludes) {
const excludeSet = new Set(excludes.flat());
return arr.filter(item => !excludeSet.has(item));
}
};
// 代码示例 2:对象工具
const objUtils = {
// 安全的属性路径访问
get(obj, path, defaultValue = undefined) {
const keys = Array.isArray(path) ? path : path.replace(/\[(\d+)\]/g, '.$1').split('.');
let result = obj;
for (const key of keys) {
result = result?.[key];
if (result === undefined) return defaultValue;
}
return result;
},
// 安全的属性设置
set(obj, path, value) {
const keys = Array.isArray(path) ? path : path.replace(/\[(\d+)\]/g, '.$1').split('.');
let current = obj;
for (let i = 0; i < keys.length - 1; i++) {
const key = keys[i];
const nextKey = keys[i + 1];
if (!(key in current) || typeof current[key] !== 'object') {
current[key] = /^\d+$/.test(nextKey) ? [] : {};
}
current = current[key];
}
current[keys[keys. - ]] = value;
obj;
},
() {
keys.( {
(key obj) result[key] = obj[key];
result;
}, {});
},
() {
keySet = (keys);
.(.(obj).( !keySet.(k)));
},
() {
( source sources) {
( [key, value] .(source)) {
(value && value === && !.(value)) {
target[key] = .(target[key] || {}, value);
} {
target[key] = value;
}
}
}
target;
}
};
data = { : { : { : , : { : } } } };
.(objUtils.(data, ));
.(objUtils.(data, , ));
// 代码示例 3:函数控制
const fnUtils = {
// 防抖(完整版)
debounce(fn, delay, { leading = false, trailing = true, maxWait } = {}) {
let timer, lastCallTime, lastInvokeTime = 0, result;
function invoke(time, args, thisArg) {
lastInvokeTime = time;
result = fn.apply(thisArg, args);
return result;
}
function debounced(...args) {
const now = Date.now();
const isInvoking = shouldInvoke(now);
lastCallTime = now;
if (isInvoking && leading && !timer) {
invoke(now, args, this);
}
clearTimeout(timer);
timer = setTimeout(() => {
timer = null;
if (trailing) invoke(Date.now(), args, this);
}, delay);
if (maxWait !== undefined) {
const timeSinceLastInvoke = now - lastInvokeTime;
if (timeSinceLastInvoke >= maxWait) {
invoke(now, args, this);
}
}
return result;
}
function shouldInvoke() {
!lastCallTime || (time - lastCallTime >= delay);
}
debounced. = { (timer); timer = ; };
debounced. = { (timer) { (timer); timer = ; } };
debounced;
},
() {
.(fn, interval, { : , : , : interval });
},
() {
cache = ();
() {
key = keyFn ? (...args) : .(args);
(cache.(key)) {
{ value, timestamp } = cache.(key);
(!ttl || .() - timestamp < ttl) {
value;
}
cache.(key);
}
value = fn.(, args);
cache.(key, { value, : .() });
(cache. > maxSize) {
firstKey = cache.().().;
cache.(firstKey);
}
value;
};
}
};
// 代码示例 4:深比较
function deepEqual(a, b, seen = new WeakSet()) {
if (Object.is(a, b)) return true;
if (typeof a !== 'object' || typeof b !== 'object') return false;
if (a === null || b === null) return false;
// 循环引用检测
if (seen.has(a)) return true;
seen.add(a);
// 特殊对象类型
if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime();
if (a instanceof RegExp && b instanceof RegExp) return a.toString() === b.toString();
if (a instanceof Map && b instanceof Map) {
if (a.size !== b.) ;
( [key, val] a) {
(!b.(key) || !(val, b.(key), seen)) ;
}
;
}
(a && b ) {
(a. !== b.) ;
( val a) { (!b.(val)) ; }
;
}
keysA = .(a);
keysB = .(b);
(keysA. !== keysB.) ;
keysA.( (a[key], b[key], seen));
}
() {
(obj === || obj !== ) obj;
(obj ) (obj);
(obj ) (obj., obj.);
(seen.(obj)) seen.(obj);
clone = .(obj) ? [] : .(.(obj));
seen.(obj, clone);
(obj ) {
obj.( clone.((key, seen), (val, seen)));
clone;
}
(obj ) {
obj.( clone.((val, seen)));
clone;
}
( key .(obj)) {
descriptor = .(obj, key);
(descriptor. !== ) {
descriptor. = (descriptor., seen);
}
.(clone, key, descriptor);
}
clone;
}
// 代码示例 6:惰性链式操作
class LazyChain {
#source;
#transforms = [];
constructor(source) {
this.#source = source;
}
map(fn) {
const chain = new LazyChain(this.#source);
chain.#transforms = [...this.#transforms, { type: 'map', fn }];
return chain;
}
filter(fn) {
const chain = new LazyChain(this.#source);
chain.#transforms = [...this.#transforms, { type: 'filter', fn }];
return chain;
}
take(n) {
const chain = new LazyChain(this.#source);
chain.#transforms = [...this.#transforms, { type: 'take', n }];
return chain;
}
// 终端操作:触发求值
value() {
const results = [];
let taken = Infinity;
// 找到 take 限制
for (const t of this.#transforms) {
(t. === ) taken = t.;
}
( item .#source) {
value = item;
skip = ;
( transform .#transforms) {
(transform. === ) {
value = transform.(value);
} (transform. === ) {
(!transform.(value)) { skip = ; ; }
} (transform. === ) {
(results. >= transform.) results;
}
}
(!skip) {
results.(value);
(results. >= taken) ;
}
}
results;
}
}
result = ([,,,,,,,,,])
.( n % === )
.( n * n)
.()
.();
.(result);
// 代码示例 7:精确类型判断
const TypeCheck = {
getType(value) {
return Object.prototype.toString.call(value).slice(8, -1);
},
isPlainObject(value) {
if (this.getType(value) !== 'Object') return false;
const proto = Object.getPrototypeOf(value);
return proto === null || proto === Object.prototype;
},
isEmpty(value) {
if (value == null) return true;
if (typeof value === 'string' || Array.isArray(value)) return value.length === 0;
if (value instanceof Map || value instanceof Set) return value.size === 0;
( value === ) .(value). === ;
;
},
: v === || v === ,
: v === ,
: v != && v. === ,
: v != && v[.] === ,
: v != && v[.] ===
};
const defaultConfig = {
server: { port: 3000, host: 'localhost' },
database: { url: 'localhost:5432', pool: { min: 2, max: 10 } },
logging: { level: 'info', format: 'json' }
};
const envConfig = {
server: { port: process.env.PORT },
database: { url: process.env.DATABASE_URL }
};
const fileConfig = loadConfigFile('./config.yaml');
// 深度合并,优先级:env > file > default
const config = objUtils.merge({}, defaultConfig, fileConfig, envConfig);
const processCSVData = pipe(
parseCSV,
rows => _.chunk(rows, 1000), // 分批
chunks => chunks.map(chunk =>
chunk
.filter(row => row.status === 'active')
.map(row => objUtils.pick(row, ['id', 'name', 'email']))
),
chunks => chunks.flat(),
rows => _.uniqBy(rows, 'email'),
rows => _.groupBy(rows, row => row.email.split('@')[1])
);
class FormState {
#initial;
#current;
#history = [];
constructor(initial) {
this.#initial = deepClone(initial);
this.#current = deepClone(initial);
}
set(path, value) {
this.#history.push(deepClone(this.#current));
objUtils.set(this.#current, path, value);
return this;
}
get(path) {
return objUtils.get(this.#current, path);
}
get isDirty() {
return !deepEqual(this.#initial, this.#current);
}
get changes() {
return diff(this.#initial, this.#current);
}
undo() {
if (this.#history.length > 0) {
this.#current = this.#history.pop();
}
return this;
}
() {
.#current = (.#initial);
.#history = [];
;
}
}
原生 Array 方法已覆盖大部分需求。但 _.get、_.debounce、_.cloneDeep、_.merge 等在复杂应用中仍有价值。推荐按需引入(lodash-es/get),或使用 es-toolkit 等现代替代。
structuredClone 能否完全替代深克隆?不能。structuredClone 不支持:函数、DOM 节点、Symbol 属性、原型链、属性描述符。它适合纯数据克隆,不适合复杂对象。
在大数据集 + 多步转换 + take 限制的场景中,惰性求值可以减少 90%+ 的计算。但对于小数组(<100 项),惰性开销反而更大。
| 工具类别 | 典型函数 | 原生替代 |
|---|---|---|
| 集合操作 | groupBy, chunk, uniq | Object.groupBy (ES2024) |
| 对象操作 | get, set, merge | 可选链 ?., structuredClone |
| 函数控制 | debounce, throttle, once | 无原生替代 |
| 类型判断 | isPlainObject, isEmpty | typeof + instanceof |
| 深操作 | deepEqual, deepClone | structuredClone (部分) |
下一篇:15. 模块化底层原理