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

如何判断两个JavaScript函数是否来自源码中的同一声明位置?

判断两个函数是否来自同一源码声明位置

先看这个示例:

function counter() {
  var i = 0;
  return function() {
    return i++;
  }
}

var counter1 = counter();
var counter2 = counter();

这里counter1 !== counter2,因为它们是counter()两次调用生成的闭包函数,但这两个函数在源码里的声明位置完全一致。

问题是:不直接修改函数声明(允许原型操作)的前提下,有没有办法判断两个变量指向的函数是否来自同一源码声明?还是说这根本做不到?


常见方法的局限性

有人会想到用counter1.toString() === counter2.toString()来判断,但这种方法很容易失效,比如下面的例子:

function counterA() {
  var i = 0;
  return function() {
    return i++;
  }
}
function counterB() {
  var i = 5;
  return function() {
    return i++;
  }
}

var counter1 = counterA();
var counter2 = counterB();

counter1.toString() === counter2.toString(); // 返回true,但它们根本不是来自同一声明位置

两个内部函数的toString输出完全一致,但它们分别属于counterA和counterB,源码位置完全不同。

另外,Chrome调试器能识别函数的声明位置——运行上面的代码后,在控制台查看counter1或counter2,点击打印出的函数就能跳转到对应的源码行,说明浏览器内部是有这个信息的。


可行的解决方案

1. 利用浏览器的栈追踪获取函数位置(非标准)

浏览器环境中,函数对象有一个内部属性[[FunctionLocation]],虽然不能直接访问,但可以通过Error的栈信息间接提取:

function getFuncDeclPos(fn) {
  try {
    throw new Error();
  } catch (err) {
    // 提取栈的第三行(不同浏览器可能有差异)
    const stackLine = err.stack.split('\n')[2];
    // 适配Chrome格式
    const chromeMatch = stackLine.match(/at\s+(?:.*?)\s+\((.*?):(\d+):(\d+)\)/);
    if (chromeMatch) {
      return `${chromeMatch[1]}:${chromeMatch[2]}:${chromeMatch[3]}`;
    }
    // 适配Firefox格式
    const ffMatch = stackLine.match(/@(.*?):(\d+):(\d+)/);
    if (ffMatch) {
      return `${ffMatch[1]}:${ffMatch[2]}:${ffMatch[3]}`;
    }
    return null;
  }
}

// 测试第一个例子
console.log(getFuncDeclPos(counter1) === getFuncDeclPos(counter2)); // true
// 测试第二个例子
console.log(getFuncDeclPos(counterA()) === getFuncDeclPos(counterB())); // false

注意:这种方法依赖浏览器的栈输出格式,代码压缩后会失效(压缩会抹掉源码位置信息),而且不是标准JS API,只能在浏览器环境使用。

2. 给函数添加唯一标识(不修改原声明)

如果允许包装目标函数,可以通过闭包给内部函数打上唯一标记,不用修改原函数的声明代码:

// 包装已存在的counter函数
const originalCounter = counter;
counter = function() {
  const innerFn = originalCounter();
  // 用Symbol做唯一标识,避免冲突
  innerFn._declId = Symbol.for('counter-declaration');
  return innerFn;
};

// 同样包装counterA和counterB
const originalCounterA = counterA;
counterA = function() {
  const innerFn = originalCounterA();
  innerFn._declId = Symbol.for('counterA-declaration');
  return innerFn;
};

const originalCounterB = counterB;
counterB = function() {
  const innerFn = originalCounterB();
  innerFn._declId = Symbol.for('counterB-declaration');
  return innerFn;
};

// 判断逻辑
console.log(counter1._declId === counter2._declId); // true
console.log(counterA()._declId === counterB()._declId); // false

这种方法不修改原函数的声明代码,只是对函数进行包装,给返回的内部函数添加唯一标识,兼容性好,也不会有toString方法的误判问题。


结论

  • 标准JS中没有提供直接获取函数声明位置的API,所以没有完全通用的解决方案。
  • 浏览器环境下,未压缩的代码可以通过栈追踪方法判断,但依赖浏览器实现。
  • 通过包装函数添加唯一标识的方法更可靠,符合“不修改原函数声明”的要求,适合可控的代码环境。

内容的提问来源于stack exchange,提问作者Codesmith

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 20:55:55