如何判断两个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
相关产品推荐
相关产品推荐

