JavaScript中如何区分传递给函数的对象引用?自定义缓存等函数实现
区分含函数的对象并实现memoize/isEqual/deepCopy(无第三方库)
初始问题场景
定义了两个包含toString方法的对象,但直接打印无法区分它们。需要在函数内区分这类对象,同时实现memoize、isEqual、deepCopy功能,且不能使用lodash等第三方库:
var a = { toString: () => 'a' } var b = { toString: () => 'b' } function someFunc(...params) { params.forEach((p)=>{ console.log(p); // 输出均为 {toString: ƒ toString()} }) } someFunc(a,b);
尝试过JSON.parse(JSON.stringify()),但该方法在对象包含函数时完全失效。
尝试过程中的问题
尝试1:基于引用的缓存实现(不符合预期)
尝试用对象引用生成缓存键,但出现错误命中缓存的情况:
function someFunc() { let cache = {}; return function (...params) { var ObjectReference = []; let set = {}; params.forEach((p) => { ObjectReference.push(p); set["ObjectReference." + ObjectReference.indexOf(p)+p] = true; }); let key = JSON.parse(JSON.stringify(set)) console.log(key); if (cache[key]) { console.log("cached"); } else { cache[key] = true; console.log("Not cached"); } }; } const mem = someFunc(); mem(a, b); // 输出not cached mem(b, a); // 输出cached - 预期应为not cached
问题原因:普通对象的键只能是字符串或Symbol,当把对象作为键时,会被强制转为[object Object],导致所有对象键都等价,无法区分。
尝试2:Map/WeakMap实现(缓存失效)
改用Map/WeakMap后,出现重复调用无法命中缓存的情况:
// 示例尝试代码 mem(a, b); // 输出not cached mem(a, b); // 输出not cached
解决方案
1. 区分对象:直接用引用对比
JS中对象是引用类型,===运算符直接对比引用地址,这是最可靠的区分方式:
console.log(a === b); // false(不同引用) console.log(a === a); // true(相同引用)
不管对象内部属性如何,只要是不同的内存引用,===就会返回false。
2. 正确实现基于引用的memoize
普通对象无法存对象引用作为键,必须用Map(支持任意类型键)或WeakMap(适合存可被垃圾回收的对象)。针对多参数场景,可采用嵌套WeakMap的方式:
function createMemoizer() { const cache = new WeakMap(); return function(...params) { let currentLayer = cache; let isHit = true; // 遍历参数,逐层进入缓存嵌套 for (const param of params) { if (!currentLayer.has(param)) { currentLayer.set(param, new WeakMap()); isHit = false; } currentLayer = currentLayer.get(param); } // 最后一层标记是否缓存过 if (isHit && currentLayer.has('cached')) { console.log('cached'); return currentLayer.get('cached'); } else { console.log('Not cached'); currentLayer.set('cached', true); return true; } } } const mem = createMemoizer(); mem(a, b); // Not cached mem(a, b); // cached mem(b, a); // Not cached mem(b, a); // cached
3. isEqual:值对比(含函数)
如果需要判断两个对象是否“值相等”(而非引用相等),需递归对比所有属性,包括函数(函数只能对比引用,或转字符串对比逻辑):
function isEqual(obj1, obj2) { // 先判断引用是否相等 if (obj1 === obj2) return true; // 非对象/null直接对比值 if (typeof obj1 !== 'object' || obj1 === null || typeof obj2 !== 'object' || obj2 === null) { return obj1 === obj2; } // 构造函数不同则直接不等 if (obj1.constructor !== obj2.constructor) return false; // 获取所有键(包括不可枚举) const keys1 = Reflect.ownKeys(obj1); const keys2 = Reflect.ownKeys(obj2); if (keys1.length !== keys2.length) return false; // 递归对比每个属性 for (const key of keys1) { if (!isEqual(obj1[key], obj2[key])) return false; } return true; } // 测试 const c = { toString: () => 'a' }; console.log(isEqual(a, b)); // false console.log(isEqual(a, c)); // true(函数引用不同但逻辑一致,若要区分函数引用,此结果会是false)
4. deepCopy:复制含函数的对象
函数无法被深拷贝(只能复制引用),其他属性递归复制:
function deepCopy(obj) { if (typeof obj !== 'object' || obj === null) { return obj; } const copy = new obj.constructor(); // 复制所有属性(包括不可枚举) for (const key of Reflect.ownKeys(obj)) { const descriptor = Object.getOwnPropertyDescriptor(obj, key); if (typeof descriptor.value === 'object' && descriptor.value !== null) { descriptor.value = deepCopy(descriptor.value); } Object.defineProperty(copy, key, descriptor); } return copy; } // 测试 const aCopy = deepCopy(a); console.log(a === aCopy); // false console.log(a.toString() === aCopy.toString()); // true console.log(a.toString === aCopy.toString()); // true(函数为引用复制)
内容的提问来源于stack exchange,提问作者era s'q
相关产品推荐
相关产品推荐

