JavaScript中的原生函数
在JavaScript中,“原生函数”指的是那些源代码已被编译为本地机器代码的函数。这些函数存在于JavaScript的标准内建对象中(如eval()
、parseInt()
等),也可以在浏览器的Web API中找到(如fetch()
、localStorage.getItem()
等)。
由于JavaScript的动态特性,开发者可以重写浏览器提供的原生函数,这种技术称为“Monkey Patching”。
Monkey Patching
Monkey Patching主要用于修改浏览器内置API和原生函数的默认行为。通常,这是添加特定功能、填补功能空缺或挂接无法直接访问的API的唯一方式。例如,监控工具(如Bugsnag)会重写Fetch和XMLHttpRequest API,以便监控JavaScript代码触发的网络连接。
Monkey Patching是一种强大但危险的技术,因为重写的代码并不在你的控制之下:JavaScript引擎的未来更新可能会破坏Monkey Patching的某些假设,从而引发严重的Bug。此外,通过Monkey Patching覆盖不属于自己的代码时,可能会覆盖其他开发者已经Monkey Patching过的代码,产生潜在的冲突。
出于这些原因,有时需要测试一个函数是否是原生函数,还是被Monkey Patching过的……但我们可以做到吗?
使用toString()
检测函数是否被猴子补丁
检测函数是否“干净”最常见的方法是检查其toString()
的输出。原生函数的toString()
通常返回类似于"function fetch() { [native code] }"
的内容。
此字符串可能会随JavaScript引擎的不同而略有变化,但在大多数浏览器中,可以安全地假设此字符串包含"[native code]"
子字符串。
Monkey Patching一个原生函数后,其toString()
将不再返回"[native code]"
字符串,而是返回函数体的字符串形式。因此,检测函数是否仍然是原生的一个简单方法是检查其toString()
输出中是否包含"[native code]"
字符串。
function isNativeFunction(f) {
return f.toString().includes("[native code]");
}
isNativeFunction(window.fetch); // → true
// Monkey Patching fetch API
(function () {
const { fetch: originalFetch } = window;
window.fetch = function fetch(...args) {
console.log("拦截的Fetch调用:", ...args);
return originalFetch(...args);
};
})();
isNativeFunction(window.fetch); // → false
此方法在大多数情况下都适用。然而,有一些方式可以让被Monkey Patching的函数看起来仍然是原生的(如在函数体中包含"[native code]"
字符串或重写toString()
方法)。
使用严格引用相等性检测Monkey Patching
如果安全性是最重要的,可以考虑另一种方法:保存“干净”原生函数的引用,稍后将潜在的Monkey Patching函数与之进行比较:
<html>
<head>
<script>
(function () {
const { fetch: originalFetch } = window;
window.__isFetchMonkeyPatched = function () {
return window.fetch !== originalFetch;
};
})();
window.fetch = new Proxy(window.fetch, {
apply: function (target, thisArg, argumentsList) {
console.log("拦截的Fetch调用:", ...argumentsList);
Reflect.apply(...arguments);
},
});
window.__isFetchMonkeyPatched(); // → true
</script>
</head>
</html>
通过严格引用比较,我们避免了toString()
的各种漏洞。这种方法即使在代理情况下也有效,因为代理无法拦截相等性比较。
如何判断JavaScript原生函数是否被覆盖?
根据使用场景,可能没有万无一失的方法来检测原生函数是否被Monkey Patching。如果你掌控整个网页,可以提前存储原生函数的引用,之后再与其比较;如果可以使用iframe,可以创建隐藏的iframe并从中获取“干净”的函数;否则,考虑toString().includes("[native code]")
检查,或加入额外的安全检查来覆盖大多数情况(但并非所有边界情况)。