Кратко: как можно понять, была ли переопределена нативная JavaScript‑функция? Никак — или не совсем надежно. Способы есть, но полностью доверять им нельзя.
Нативные функции в JavaScript
В JavaScript «нативная функция» — это функция, исходный код которой был скомпилирован в машинный код. Нативные функции можно найти в стандартных встроенных объектах JavaScript (таких, как eval()
, parseInt()
и т. д.) и веб-API браузеров (таких, как fetch()
, localStorage.getItem()
и т. д.).
Из-за динамической природы JavaScript разработчики могут переопределять нативные функции, предоставляемые браузером. Этот метод известен как манкипатчинг.
Манкипатчинг
Манкипатчинг, в основном, используется для изменения поведения встроенных API и нативных функций браузера. Часто, это единственный способ добавить специфичную функциональность, полифиллы или «зацепиться» за API, который иначе изменить невозможно.
Например, инструменты мониторинга, такие как Bugsnag, переопределяют интерфейсы Fetch и XMLHttpRequest, чтобы получить представление о сетевых подключениях, запускаемых кодом JavaScript.
Манкипатчинг — это мощный, но опасный метод, потому что код, который вы переопределяете, не находится под вашим контролем: будущие обновления движка JavaScript могут нарушить предположения, сделанные в вашем патче, и вызвать серьезные ошибки.
Кроме того, делая манкипатчинг кода, которым не владеете, вы можете переопределить код, уже содержащий манкипатч другого разработчика, что приведет к потенциальному конфликту.
По этим (и многим другим) причинам может понадобиться проверить, является ли данная функция нативной или она имеет манкипатч… Но получится ли?
Использование toString() для проверки того, что у функции есть манкипатч
Самый распространенный способ проверить, является ли функция «чистой» (то есть, без манкипатча) — это проверить вывод ее toString()
.
По умолчанию, нативная функция toString()
возвращает что-то вроде строки "function fetch() { [native code] }"
:
Эта строка может немного отличаться в зависимости от того, какой движок JavaScript используется. Тем не менее в большинстве браузеров вы можете с уверенностью предположить, что эта строка будет включать подстроку "[native code]"
.
Если нативная функция будет иметь манкипатч, ее toString()
перестанет возвращать строку "[native code]"
в пользу возврата тела функции в виде строки.
Таким образом, простой способ проверить, является ли функция по-прежнему нативной — это проверить, содержит ли ее вывод toString()
строку "[native code]"
.
Элементарная проверка может выглядеть так:
function isNativeFunction(f) {
return f.toString().includes("[native code]");
}
isNativeFunction(window.fetch); // → true
// Манкипатч fetch API
(function () {
const { fetch: originalFetch } = window;
window.fetch = function fetch(...args) {
console.log("Вызов fetch перехвачен:", ...args);
return originalFetch(...args);
};
})();
window.fetch.toString(); // → "function fetch(...args) {\n console.log("Fetch...
isNativeFunction(window.fetch); // → false
Этот подход отлично работает в большинстве случаев. Однако, его легко обойти, заставив считать, что функция по-прежнему нативна, когда это не так. Будь то злой умысел (например, вредоносное изменение кода) или потому, что кто-то хочет скрыть факт переопределения. Есть несколько способов, которыми вы можете сделать функцию «нативной».
Например, вы можете добавить код (или даже комментарий!) в тело функции, содержащий строку "[native code]"
:
(function () {
const { fetch: originalFetch } = window;
window.fetch = function fetch(...args) {
// function fetch() { [native code] }
console.log("Вызов fetch перехвачен:", ...args);
return originalFetch(...args);
};
})();
window.fetch.toString(); // → "function fetch(...args) {\n // function fetch...
isNativeFunction(window.fetch); // → true
…Или вы можете переопределить метод toString()
, чтобы он возвращал строку, содержащую "[native code]"
:
(function () {
const { fetch: originalFetch } = window;
window.fetch = function fetch(...args) {
console.log("Вызов fetch перехвачен:", ...args);
return originalFetch(...args);
};
})();
window.fetch.toString = function toString() {
return `function fetch() { [native code] }`;
};
window.fetch.toString(); // → "function fetch() { [native code] }"
isNativeFunction(window.fetch); // → true
…Или вы можете создать функцию с манкипатчем, используя bind, которая генерирует нативную функцию:
(function () {
const { fetch: originalFetch } = window;
window.fetch = function fetch(...args) {
console.log("Вызов fetch перехвачен:", ...args);
return originalFetch(...args);
}.bind(window.fetch); //