«Патч обезьяны - это способ программы для локального расширения или модификации поддерживающего системного программного обеспечения.

Термин «обезьяний патч», кажется, произошел от более раннего термина «партизанский патч», который относился к изменению кода незаметно - и, возможно, несовместимо с другими подобными патчами ».

(Википедия)

Введение

Как сторонний поставщик программного обеспечения JavaScript (JS), наш код работает в Wild Wide Web. Мы должны поддерживать веб-сайты, которые пытаются поддерживать Internet Explorer (IE) 6, теневые темы WordPress, сайты, созданные в Dreamweaver, и многие другие подобные ужасы первого мира.

Это действительно чуждое, а иногда и враждебное окружение. Неизвестные браузеры, неизвестные ОС и неизвестные разработчики, которые спешат создать самые причудливые обезьяньи патчи, какие только можно придумать. Ошибки могут быть внесены косвенно, настолько незаметно, что вы зададитесь вопросом, действительно ли вся эта интернет-штука того стоит (предупреждение о спойлере: это так).

Вот некоторые из наиболее странных (мягко говоря) модификаций, которые мы видели:

1. Нотация объектов JavaScript (JSON)

JSON - это универсальный, четко определенный и очень популярный стандарт. Несмотря на это, некоторые разработчики до сих пор обезьяны его исправляют.

«Как был изменен JSON», - спросите вы? Давайте рассмотрим следующий результат:

JSON.stringify({a: undefined})
 // {"a":undefined}

Видите что-нибудь странное? Да, правильный результат должен быть {}. Как упоминалось в документации, метод JSON.stringify() обычно пропускает неопределенные значения.
К сожалению, вышеупомянутая версия с пропатченной обезьяной этого не делает. Неисправный JSON был отправлен на наши серверы, наш JSON.parse() вышел из строя и сыграл главную роль в рекордах журнала того дня.

Но почему? Вероятно, небрежная попытка поддержать старые браузеры. IE 6 снова бьет из (заслуженной) могилы.

2. Function.prototype.bind ()

Function.prototype.bind() - важный метод, фундаментальный для функциональной природы JS. Неназванный разработчик темы WordPress взял на себя ответственность реализовать ее заново:

Function.prototype.bind = function(scope) {
  var self = this;
  return function() {
    return self.apply(scope, arguments);
   };
}

Вы можете заметить ошибку? Что ж, в этой реализации отсутствует важная функция bind: currying. Так не пойдет:

function add(a, b) {
  return a + b;
}
var add10 = add.bind(null, 10);
add10(1); // Should be 11, returns NaN

3. Array.prototype.map ()

Кто не любит Array.prototype.map()? Это элегантная техника функционального программирования. Он всегда должен работать ... верно? На этот раз виновник будет назван, поскольку это не что иное, как Prototype.js, хотя и довольно старая его версия.

Посмотрим на их код:

// map made into an alias of collect later
collect: function(iterator, context) {
  iterator = iterator || Prototype.K;
  var results = [];
  this.each(function(value, index) {
    results.push(iterator.call(context, value, index));
  });
 return results;
}

Профессионально, как и следовало ожидать. Они даже добавили для нас Array.prototype.each. К сожалению, важное свойство map было потеряно при переводе. Мы больше не можем использовать этот изящный трюк:

Array.prototype.map.call(arguments, fn);

Фактически, все уловки с объектами Array вылетели прямо в окно.

Prototype.js давно изменил реализацию. Но в Дикой Паутине, как вампиры, пьющие красное вино, ваши грехи живут вечно.

Защита

Как ответственный сторонний разработчик должен реагировать на такие махинации? Вы можете ничего не сделать и просто заявить, что не поддерживаете и не занимаетесь такими делами. Мы предлагаем вам пойти не по этому пути. Есть возможность защитить ваш код, по крайней мере, до некоторой степени, от суровой реальности Интернета.

1. Непосредственно вызываемые функциональные выражения (IIFE)

Распространенное заблуждение состоит в том, что лучше всего заключать код в IIFE. Функция получит ссылку на глобальные переменные, которые вы хотите защитить.

Например, чтобы убедиться, что у вас есть исходное окно, jQuery и неопределенные переменные, вы можете сделать следующее:

function(window, $, undefined) {
 // your code
}(window, $)

Теперь ваш код будет продолжать работать, даже если пользователь сделает что-то вроде:

window.$ = { ajax: function() {} };

Этот метод обеспечивает частичную защиту. Допустим, после загрузки вашего кода пользователь добавляет следующий код:

$.ajax = function() {}

Поскольку вы сохранили только ссылку на переменную jQuery, вы по-прежнему уязвимы для модификации самого объекта.

На самом деле у этого метода есть и другие преимущества, такие как сокращение списка областей видимости, которые JS должен искать, чтобы найти вашу переменную, или включение обфускаторов JS для переименования общих переменных, таких как окно или документ.

2. Подробнее о IIFE

Мы можем использовать конструкцию, аналогичную шаблону модуля UMDs. Здесь мы клонируем объекты, которые хотим защитить, а затем передаем их нашему IIFE:

function(factory) {
  var $ = clone($);
factory(window, $)
 }(function(window, $){
 // your code
})

Конечно, мы должны спросить себя: «Где же предел?» Должны ли мы просто клонировать окно и покончить с этим? Должны ли мы взять на себя бремя управления списком всех используемых нами языковых функций и клонировать их на индивидуальной основе?

Увы, даже этот метод не гарантирует целостности встроенных функций, поскольку пользователь может переопределить их перед загрузкой нашего кода. На самом деле создать такую ​​безопасность невозможно.

Обнаружение метода, пропатченного обезьяной?

Разве не было бы неплохо просто проверить, пропатчена ли функция как обезьяна? Если бы мы могли это сделать, мы, по крайней мере, смогли бы изящно потерпеть неудачу.

К сожалению, JS не упрощает нашу жизнь. Однако свойство, которое может пригодиться, - это результат метода toString()
для собственных функций:

Function.prototype.bind.toString();
 // function bind() { [native code] }

Используя это, мы можем создать метод обнаружения обезьяньих патчей:

function isMonkeyPatched(fn) {
  return !Function.prototype.toString.call(fn).match(/native code/);
}

К сожалению, это будет работать только для нативных функций и не сработает, если наши любимые пользователи обезьяны исправят toString().

В заключении

JS, столь же замечательный язык, как он есть, позволяет каждому отменять почти все. Мы столкнулись с некоторыми очень причудливыми ошибками, возникшими из-за странных патчей с обезьянами. Несмотря на то, что всегда приятно узнать, что ошибка не была вызвана вашим кодом, вам нужно спросить себя: «Что дальше?» Вы подходите к клиенту и говорите ему, что его код нарушает наш код? Вы просто справляетесь с этим сами? Понимание природы и преобладания пятнистости обезьян в дикой природе - это первый шаг к ответу на этот вопрос для себя.