如何确定一个JavaScript函数是否已定义?

3

我想要检查一个函数是否被定义(不考虑怎样,只关心它是否可以被调用)

示例代码:

var functions = {
    'alert':'alert',
    'undefinedFunction':'undefinedFunction',
    'ff.showAlert':'ff.showAlert'
};

var ff = {
    showAlert: function() {
        alert('the function works');
    }
};

for (i in functions) {
    console.log(i+' : '+typeof window[functions [i]]);
}

这将返回:

alert : function
undefinedFunction : undefined
ff.showAlert : undefined

console.log(typeof window.ff.showAlert); return function

演示
有没有一种程序化的方式来检查函数是否存在?


可能是重复的问题:测试 JavaScript 函数是否存在 - Phil H
不,那种情况下他知道函数名,而在我的情况下我不知道。 - ilyes kooli
5个回答

5
代码如下:
window[functions [i]]

代码中检查了 window['ff.showAlert'],但实际上你想要检查的是:

window['ff']['showAlert']

或者

window.ff.showAlert

为此,您需要遍历命名空间(window->ff->...):
function methodIsDefined(fn, obj) {
  var ns = fn.split('.');
  fn = ns.pop();
  do {
    if (!ns[0]) {
      return typeof obj[fn] === 'function';
    }
  } while(obj = obj[ns.shift()]);
  return false;
}

例如:

methodIsDefined('ff.showAlert', window); // => true
methodIsDefined('ff.foo', window); // => false

2
你的问题在于命名空间。字符串"ff.showAlert"并不是指向函数window['ff']['showAlert'],而是指向window['ff.showAlert'],这是一个重要的区别。你声明的函数实际上是由window['ff']['showAlert']引用的。所以你检查它是否存在的地方是错误的。
如果你想检查这样的命名空间函数是否存在,你首先需要拆分字符串,然后遍历DOM来找到正确的属性。代码应该类似于以下内容(未经测试!):
function checkFunction( name ) {

  var path = "ff.showAlert".split( '.' ),
      runner = window;

  for( var i=0; i<path.length; i++ ) {
    if( path[i] in runner ) {
      runner = runner[ path[i] ];
    } else {
      return false;
    }
  }
  return runner;
}

编辑

正如评论区@VirtualBlackFox所指出的那样:上述解决方案仅在函数在window作用域下声明时有效。如果它在另一个函数的作用域内,您需要将该作用域作为附加参数传递并在其中搜索。

即使在这种情况下,您也无法检查例如在某些闭包结构中定义的函数是否存在。


如果代码在任何函数作用域中执行,它不会在 window 下,因为它是用 var 声明的。 - Julien Roncaglia
@VirtualBlackFox 我只是想指出正确方向,并不想为此提供每种情况下的完整解决方案。在函数范围内,他必须将实际范围作为额外参数传递,runner可以从中开始。当然,即使这样也有一些闭包情况没有得到解决。 - Sirko
当然会。您的函数声明为window['ff']['showAlert']。我只是想指出,标识符中的字符串与“.”无关。 - Sirko
@Sirko 是的,我只是在指出一个细节,但我认为他应该强制使用窗口使代码正常工作。唯一其他可行的解决方案(eval)是丑陋的。 - Julien Roncaglia

0
你需要拆分多部分的函数名,例如'ff.showAlert'。另外,因为你将ff指定为一个变量,除非它在任何函数范围之外,否则它不会是window的成员。从你的代码示例中并不清楚它是否在函数范围之外。
无论如何,下面的函数允许你传入一个基础对象,以防需要指定除了window之外的对象,并且它会拆分多部分函数名:
function isFnDefined(fnName, baseObj) {
    try {
        var parts = fnName.split('.'),
            ii;

        // If no baseObj was provided, default to 'window'.
        baseObj = baseObj || window;

        for (ii in parts) {
            baseObj = base[parts[ii]];
        }

        return typeof baseObj === 'function';
    }
    catch (e) {
        return false;
    }
}​

isFnDefined('alert');          // returns true
isFnDefined('undefinedFunc');  // returns false
isFnDefined('ff.showAlert');   // returns true, if ff is a member of window 
isFnDefined('showAlert', ff);  // returns true

-1

不需要使用 Eval。 - James

-2
你有一个字符串,代表当前作用域中的某个东西,你想知道它是什么,解决方案是对该字符串进行 eval。
var f = undefined;
try
{
    f = eval(functions[i]);
}
catch(ReferenceError) {}
console.log(typeof f);

但是为什么你要在输入对象中存储字符串而不是函数本身呢?

另外,如果你可以强制每个字符串都是相对于 window 的引用(当前范围中没有'var'或来自另一个范围闭包的东西),那么@Sirko的解决方案可能是最好的。


我页面上有很多按钮,其中有几个onclick动作,我想要做的是禁用那些试图调用未定义函数的按钮。 - ilyes kooli

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接