Frontender Magazine

Как в JavaScript определить, является ли функция нативной

Время от времени мне приходится проверять, является ли та или иная функция нативной — это важная часть проверки, была ли функция предоставлена браузером или это порождение постороннего шима, замаскированное под встроенный компонент. Лучший способ выполнения такой проверки — это, конечно же, оценка значения toString, возвращённого функцией.

JavaScript

Код, требуемый для этого, довольно прост:

function isNative(fn) {
    return (/\{\s*\[native code\]\s*\}/).test('' + fn);
}

Вся суть состоит в том, чтобы конвертировать функцию в строчное представление и выполнить сопоставление строки с регулярными выражениями. Лучшего способа подтвердить, что это нативная функция, не существует!

Обновление!

Создатель библиотеки lodash, Джон-Дэвид Далтон (John-David Dalton), предложил лучшее решение:

;(function() {

  // Используется для разложения на составляющие внутреннего `[[Class]]` значений
  var toString = Object.prototype.toString;

  // Используется для разложения на составляющие декомпилированного 
  // исходного кода функции 
  var fnToString = Function.prototype.toString;

  // Используется для определения конструкторов среды (Safari > 4; 
  // по сути, предназначено специально для типизированных массивов)
  var reHostCtor = /^\[object .+?Constructor\]$/;

  // Составление регулярного выражения на основе часто употребляемого 
  // нативного метода в качестве шаблона.
  // Выбираем `Object#toString`, так как вполне вероятно, что он ещё не задействован.
  var reNative = RegExp('^' +
    // Применяем `Object#toString` к строке
    String(toString)
    // Избавляемся от любых специальных символов регулярных выражений 
    .replace(/[.*+?^${}()|[\]\/\\]/g, '\\$&')
    // Заменяем упоминания `toString` на `.*?`, чтобы сохранить обобщённый вид шаблона.
    // Заменяем `for ...` и тому подобное для поддержки окружений вроде Rhino,  
    // которые добавляют дополнительную информацию, такую как арность метода.
    .replace(/toString|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$'
  );

  function isNative(value) {
    var type = typeof value;
    return type == 'function'
      // Используем `Function#toString`, чтобы обойти собственный метод 
      // `toString` самого значения и избежать ложного результата.
      ? reNative.test(fnToString.call(value))
      // На всякий случай выполняем проверку на наличие объектов среды, так 
      // как некоторые окружения могут представлять компоненты вроде 
      // типизированных массивов как методы DOM, что может не соответствовать
      // нормальному нативному паттерну.
      : (value && type == 'object' && reHostCtor.test(toString.call(value))) || false;
  }

  // экспортируем в удобном для вас виде
  module.exports = isNative;
}());

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

Если вы заметили ошибку, вы всегда можете отредактировать статью, создать issue или просто написать об этом Антону Немцеву в skype ravencry.

David Walsh
Автор:
David Walsh
GitHub:
darkwing
Twitter:
@davidwalshblog
Сaйт:
http://davidwalsh.name/
LinkedIn:
davidjameswalsh
Наталья Фадеева
Переводчик:
Наталья Фадеева
вКонтакте:
natatik_l
Twitter:
@very_busy_girl
GitHub:
NatalieF

Комментарии (9 комментариев, если быть точным)

Автар пользователя
serhiidmytruk

А подскажите, зачем ; перед (function() {?

Автар пользователя
iamstarkov

Добавлть ; перед своим кодом помогает избегать сайд-эффектов от холивара «ставить точку с запятой или нет»

Автар пользователя
LeusMaximus

Вот подробнее описано, зачем в начале ; http://learn.javascript.ru/closures-module http://prntscr.com/722ixt

Автар пользователя
subzey

У этого поста определённо есть хакерская ценность, но пользоваться чем-то таким в продакшне — боже упаси. Какая разница, как реализован Function.prototype.bind, если он ходит как bind и крякает как bind?

Автар пользователя
SilentImp

Если ты собрался написать свой полифил Function.prototype.bind то хорошо бы знать крякает на тебя настоящий чужой полифил или, возможно, все же нативная функция.

Автар пользователя
iamstarkov

А зачем его писать, если он есть?

Автар пользователя
iamstarkov

в плане, если bind не undefined значит уже норм на проекте

Автар пользователя
subzey

Я придерживаюсь того мнения, то автор библиотеки вообще не должен ничего полифиллить (если это не библиотека полифиллов, конечно).

Если у нас есть bind, используем его. Если нет — ой. Лучше одно подключение es5shim (при необходимости) из зависимостей, чем куча мелких библиотечек с реализациями разной степени урезанности одного и того же bind.

Автар пользователя
Jabher

А нахрена это все если

javascript isNative(isNative.bind(this)) // => true

?