Подтвердить что ты не робот

Javascript: задана функция пустая?

Пусть вызов функции

function doSomethingAndInvokeCallback(callback){
    // do something
    callback();
}

Я могу проверить, является ли данный аргумент функцией if(typeof callback == 'function')

Как я могу обнаружить, если заданная функция обратного вызова является функцией и не пуста?

как

doSomethingAndInvokeCallback(function(){
    //nothing here
})
4b9b3361

Ответ 1

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

Я бы предположил, что вы должны изменить контракт своих аргументов функции и передать вызывающий объект null или не передать ничего (что сделает аргумент undefined), а не пустую функцию. Тогда будет очень ясно, будут ли они называться функцией или нет. Если они затем передают пустую функцию вместо нуля или undefined, они получают поведение, которое указывает интерфейс функции. Вызывающий может выбрать желаемое поведение, и вы можете реализовать свою функцию более безопасным способом.

Кроме того, одно из ваших основных предположений в вашем вопросе не совсем правильно. Вы не можете безопасно использовать typeof x == "function", чтобы определить, является ли что-то функцией, поскольку это не будет надежно работать в некоторых старых версиях IE для некоторых типов функций. Если вы хотите узнать, как определить, является ли что-то вообще функцией, вы можете узнать здесь о jQuery (даже если вы его не используете). В jQuery есть функция, которую она использует внутренне все время под названием jQuery.isFunction(), которая возвращает bool. Он использует это в основном для тестирования аргументов, чтобы увидеть, была ли передана функция.

Внутри он вызывает:

Object.prototype.toString.call(o)

а затем проверяет результат. Если в результате в нем есть "Функция", тогда тестовый параметр теста является функцией.

Итак, используя тот же метод, который используется в jQuery, вы можете создать свою собственную небольшую процедуру isFunction, как это:

function isFunction(test) {
    return(Object.prototype.toString.call(test).indexOf("Function") > -1);
}

Конечно, если у вас есть jQuery, вы можете просто использовать его собственную версию:

jQuery.isFunction(o)

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

Здесь вы можете увидеть рабочий тестовый стенд: http://jsfiddle.net/jfriend00/PKcsM/

В современных браузерах typeof fn === "function", но в более старых версиях IE некоторые функции дают typeof === "object", что, вероятно, связано с тем, что jQuery использует этот другой метод, который работает в более старых версиях IE.

Ответ 2

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

http://jsfiddle.net/6qn5P/

Function.prototype.getBody =
  function() {
    // Get content between first { and last }
    var m = this.toString().match(/\{([\s\S]*)\}/m)[1];
    // Strip comments
    return m.replace(/^\s*\/\/.*$/mg,'');
  };

function foo() {
    var a = 1, b = "bar";
    alert(b + a);
    return null;
}

console.log(foo.getBody());
console.log(foo.getBody().length);

Ответ 3

Одна возможность сопоставляет результат .toString с regexp для получения тела функции, а затем обрезает, чтобы проверить, стала ли она пустой строкой:

var f = function foo()    { 


};

/^function [^(]*\(\)[ ]*{(.*)}$/.exec(
     f.toString().replace(/\n/g, "")
)[1].trim() === ""; // true

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

Ответ 4

Возможно, вы сможете получить это от .toString():

var blank = function(){};
var f = function(){};
var f2 = function() { return 1; };

f.toString() == blank.toString(); // true
f2.toString() == blank.toString(); // false

но это действительно подвержено ошибкам:

var blank = function(){};
var f = function(){ }; // extra space!
f.toString() == blank.toString(); // false

Вы можете немного изменить строки, чтобы попытаться преодолеть это, но я подозреваю, что это очень зависит от браузера. На самом деле я бы не попытался сделать это в производственной среде, если бы был вами. Даже если вы нормализуете пробелы, он все равно не поймает другие строки no-op, включая комментарии, бесполезные операторы var и т.д. Чтобы действительно решить эти проблемы, вам, вероятно, понадобится целая система токенизатора (или сумасшедшее регулярное выражение).

Ответ 5

Вы не можете сделать это для хост-функции, но для других вы можете достаточно надежно выполнить

function isEmpty(f) {
  return typeof f === "function" &&
      /^function[^{]*[{]\s*[}]\s*$/.test(
          Function.prototype.toString.call(f));
}

Это неэффективно, но основные интерпретаторы реализуют toString для функций таким образом, что он работает, хотя он не будет работать на некоторых интерпретаторах для некоторых пустых функций

function () { /* nothing here */ }
function () { ; }
function () { return; }

Ответ 6

В некоторой реализации вы можете просто сделать toString() для функции и получить ее содержимое. Хотя он содержит комментарии и т.д.

var foo = function(){ /* Comment */ };
alert(foo.toString());

Ответ 7

Как я уже сказал в другом дублированном вопросе:

Лучшее, что вы можете попробовать, чтобы соответствовать максимальным возможностям (так как это довольно сложно достичь), заключается в добавлении acorn или esprima (работает со стрелочными функциями) и обрабатывать функцию JavaScript. Он будет подделывать его для вас, чтобы вы могли разобрать его, чтобы вы могли обработать его по своему усмотрению, проверяя, действительно ли там нулевой код внутри, или есть только объявления переменных без каких-либо вычислений или возврата и т.д.

Достаточно просто реализовать:

function check(f) {
  console.log("");
  console.log("Checking", f);
  var syntax = esprima.parse(f);
  if (syntax.body.length != 1) {
    console.log("Weird process");
  } else {
    function processBody(body) {
      if (!body || body.length == 0) {
        console.log("Body seems empty. YAY!");
      } else {
        for (var i = 0, command; command = body[i]; i++) {
          if (command.type != "VariableDeclaration") {
            console.log("Body has a", command.type, ", so is not empty.");
            return;
          }
        }
        console.log("Body has only variable declarations, so is empty! (or not, whatever fit your needs)");
      }
    }
    function process(dec) {
      if (dec.type != "FunctionDeclaration") {
        console.log("Weird declaration");
      } else {
        console.log("Function", dec.id.name, "has", dec.params.length, "params");
        processBody(dec.body.body);
      }
    }
    process(syntax.body[0]);
  }
}

check("function hasReturn(arg1, arg2) {var a = arg1 + arg2;return a;}");
check("function hasArgumentsButNoBody(arg1, arg2) {}");
check("function isCompletelyEmptyWithSpacesAndTabsAndLinefeeds() {   \t    \t   \r\n \r\n  }");
function hasOnlyVariables() {var a, b = 2; var c = 1 + b;}
check(hasOnlyVariables);
<script src="https://cdnjs.cloudflare.com/ajax/libs/esprima/2.7.3/esprima.min.js"></script>