You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.24 07:15:38