检查一个变量是否为函数类型

1234

假设我有一个变量,其定义如下:

var a = function() {/* Statements */};

我希望有一个函数可以检查变量的类型是否类似于函数。例如:

function foo(v) {if (v is function type?) {/* do something */}};
foo(a);

我如何检查变量 a 是否为上述定义的 Function 类型?


3
这里是一个基准,用于检查最常见的方法:http://jsben.ch/#/e5Ogr - EscapeNetscape
一个内省方法,比如isES3Function,可以检测传入的value是否是在ES3中独有的函数类型,而isGenericFunction则可以检测传入的value是否是一个通用的(或者说不具体/非具体)函数,因此可以是一个传统的ES3函数或者一个非异步箭头函数。 - undefined
如今,功能特定的内省比人们首先想象的要复杂得多。一些情况可以直接解决。无法检测到的情况必须从配置中查找。大多数情况可以通过结合两种方法来推断,应用包括和排除策略。例如,检测一个良好的旧的ES3函数的方式是...它是一个函数类型,具有可写的原型,既不是生成器也不是类。 - undefined
22个回答

12

对于那些对函数式编程感兴趣,或者寻找更具表现力的方法来进行元编程(如类型检查),使用Ramda库完成这样的任务可能很有趣。

下面的代码仅包含纯函数和pointfree函数:

const R = require('ramda');

const isPrototypeEquals = R.pipe(Object.getPrototypeOf, R.equals);

const equalsSyncFunction = isPrototypeEquals(() => {});

const isSyncFunction = R.pipe(Object.getPrototypeOf, equalsSyncFunction);

从ES2017开始,async函数也可用,因此我们也可以对它们进行检查:

const equalsAsyncFunction = isPrototypeEquals(async () => {});

const isAsyncFunction = R.pipe(Object.getPrototypeOf, equalsAsyncFunction);

然后将它们结合在一起:

const isFunction = R.either(isSyncFunction, isAsyncFunction);

当然,函数应该受到保护以防止nullundefined值的出现,以使其“安全”:

const safeIsFunction = R.unless(R.isNil, isFunction);

然后,完整的片段总结如下:

const R = require('ramda');

const isPrototypeEquals = R.pipe(Object.getPrototypeOf, R.equals);

const equalsSyncFunction = isPrototypeEquals(() => {});
const equalsAsyncFunction = isPrototypeEquals(async () => {});

const isSyncFunction = R.pipe(Object.getPrototypeOf, equalsSyncFunction);
const isAsyncFunction = R.pipe(Object.getPrototypeOf, equalsAsyncFunction);

const isFunction = R.either(isSyncFunction, isAsyncFunction);

const safeIsFunction = R.unless(R.isNil, isFunction);

// ---

console.log(safeIsFunction( function () {} ));
console.log(safeIsFunction( () => {} ));
console.log(safeIsFunction( (async () => {}) ));
console.log(safeIsFunction( new class {} ));
console.log(safeIsFunction( {} ));
console.log(safeIsFunction( [] ));
console.log(safeIsFunction( 'a' ));
console.log(safeIsFunction( 1 ));
console.log(safeIsFunction( null ));
console.log(safeIsFunction( undefined ));

然而,请注意,由于广泛使用高阶函数,此解决方案可能显示出比其他可用选项更低的性能。


7

这是一个旧问题,但是在2022年有一些考虑因素:

首先,浏览器兼容性:所有现代浏览器以及Deno和NodeJS都支持instanceof。此外,它的语法可读性更强,比typeof更友好。最后,它提供了比字符串比较更好的性能,但比typeof慢。因此,对我来说这是一个不错的选择。

const fnc = () => {}
const isFunction = f => !!f && f instanceof Function
const isFunctionFaster = f => !!f && 'function' === typeof f

console.log({
  isFunction: isFunction(fnc),
  isFunctionFaster: isFunctionFaster(fnc),
})

注意

需要理解,这是一个为基准测试优化的函数。当进行基准测试时,您希望通过所有测试,例如 nullundefined 和一些可能接收的参数。使用 f && ... 过滤这些类似于 null 的参数以减少计算时间。

此操作符在对象的原型链中测试 constructor.prototype 的存在。这通常(但不总是)意味着对象是由构造函数构造的。因此,与 typeof 操作符相比,这个过程较慢。

typeof v === 'function'

此操作符返回指示操作数值类型的字符串。执行速度非常快。

  • instanceoftypeof运算符的注意事项:

请记住,class declaration在这些操作符中也被视为函数,正如您在此片段中所看到的那样:

// Class Declaration
class A {}

// Instances
const obj = {}
const arr = []
const fnc = () => {}
const a = new A()

console.log('typeof')
console.log(`Object[${typeof Object}], obj[${typeof obj}]`)
console.log(`Array[${typeof Array}], arr[${typeof arr}]`)
console.log(`Function[${typeof Function}], fnc[${typeof fnc}]`)
console.log(`A[${typeof A}], a[${typeof a}]`)

console.log('instanceof')
console.log(`Object[${Object instanceof Object}], obj[${obj instanceof Object}]`)
console.log(`Array[${Array instanceof Array}], arr[${arr instanceof Array}]`)
console.log(`Function[${Function instanceof Function}], fnc[${fnc instanceof Function}]`)
console.log(`A[${A instanceof A}], a[${a instanceof A}]`)

这是使用不同实例的isFunctionisFunctionFaster的基本示例:

// Functions
const isNil = o => o == null
const isFunction = f => !!f && f instanceof Function
const isFunctionFaster = f => !!f && 'function' === typeof f

class A {}

function basicFnc(){}
async function asyncFnc(){}

const arrowFnc = ()=> {}
const arrowRFnc = ()=> 1

// Not functions
const obj = {}
const arr = []
const str = 'function'
const bol = true
const num = 1
const a = new A()

const list = [
    isFunction,
    isFunctionFaster,
    basicFnc,
    arrowFnc,
    arrowRFnc,
    asyncFnc,
    Array,
    Date,
    Object,
    Number,
    String,
    Symbol,
    A,
    obj,
    arr,
    str,
    bol,
    num,
    a,
    null,
    undefined,
]

for (const arg of list) {
  console.log(`${arg} is a function: ${isFunction(arg)}`)
}

这里是这些函数的基本基准测试:

/**
 * Figure out how long it takes for a method to execute.
 * 
 * @param {Function} method to test 
 * @param {number} iterations number of executions.
 * @param {Array} args to pass in. 
 * @param {T} context the context to call the method in.
 * @return {number} the time it took, in milliseconds to execute.
 */
const bench = (method, list, iterations, context) => {
    let start = 0
    const timer = action => {
        const time = performance.now()
        switch (action) {
            case 'start':
                start = time
                return 0
            case 'stop':
                const elapsed = time - start
                start = 0
                return elapsed
            default:
                return time - start
        }
    };

    const result = []
    timer('start')
    list = [...list]
    for (let i = 0; i < iterations; i++) {
      for (const args of list) {
        result.push(method.apply(context, args))
      }
    }
    const elapsed = timer('stop')
    
    console.log(`Called method [${method.name}]`)
    console.log(`Mean: ${elapsed / iterations}`)
    console.log(`Exec. time: ${elapsed}`)

    return elapsed
}

const fnc = () => {}
const isFunction = (f) => f && f instanceof Function
const isFunctionFaster = (f) => f && 'function' === typeof f


class A {}

function basicFnc(){}
async function asyncFnc(){}

const arrowFnc = ()=> {}
const arrowRFnc = ()=> 1

// Not functions
const obj = {}
const arr = []
const str = 'function'
const bol = true
const num = 1
const a = new A()

const list = [
    [isFunction],
    [basicFnc],
    [arrowFnc],
    [arrowRFnc],
    [asyncFnc],
    [Array],
    [Date],
    [Object],
    [Number],
    [String],
    [Symbol],
    [A],
    [obj],
    [arr],
    [str],
    [bol],
    [num],
    [a],
    [null],
    [undefined],
]

const e1 = bench(isFunction, list, 10000)
const e2 = bench(isFunctionFaster, list, 10000)

const rate = e2/e1
const percent = Math.abs(1 - rate)*100

console.log(`[isFunctionFaster] is ${(percent).toFixed(2)}% ${rate < 1 ? 'faster' : 'slower'} than [isFunction]`)

结论

一般来说,isFunctionFasterisFunction快30%。


7
如果您使用 Lodash,您可以使用 _.isFunction 来实现。
_.isFunction(function(){});
// => true

_.isFunction(/abc/);
// => false

_.isFunction(true);
// => false

_.isFunction(null);
// => false

该方法返回true表示值为函数,否则返回false

5
以下内容对我也有效(从node.js测试):
var isFunction = function(o) {
     return Function.prototype.isPrototypeOf(o);
};

console.log(isFunction(function(){})); // true
console.log(isFunction({})); // false

4

我发现在IE8中测试本地浏览器功能时,使用toStringinstanceoftypeof无法正常工作。这是一个在IE8中运行良好的方法(据我所知):

function isFn(f){
    return !!(f && f.call && f.apply);
}
//Returns true in IE7/8
isFn(document.getElementById);

或者,您可以使用以下方法检查本地函数:

"getElementById" in document

我曾在某处读到,这种方法在IE7及以下版本中并不总是有效。


4

我认为你可以在 Function 原型上定义一个标志并检查你想要测试的实例是否继承了它。

定义一个标志:

Function.prototype.isFunction = true; 

然后检查它是否存在

var foo = function(){};
foo.isFunction; // will return true

缺点是另一个原型可以定义相同的标记,那么它就没有用了,但如果您完全控制包含的模块,则这是最简单的方法。

2
如果foo未定义,它将失败并出现错误。更糟糕的是,您修改了本地原型,这完全是错误的。 - Kamil Orzechowski

3
自Node v0.11版本起,您可以使用标准的util函数:
var util = require('util');
util.isFunction('foo');

3
util.isFunction已被弃用。 - kehers

3

在JavaScript中,你应该使用typeOf运算符

var a=function(){
    alert("fun a");
}
alert(typeof a);// alerts "function"

2
如果你正在寻找一个简单的解决方案:
 function isFunction(value) {
   return value instanceof Function
}

0
有一些函数的参数可以通过值或回调方式传递。为了实现它们,以下是一个利用类型检测的好帮助函数:
const toCallback = (v)=>(v instanceof Function)?v:()=>v

然后让我们假设你想要实现一个函数,它可以接收一个固定值或者返回该值的回调函数。请参考下面的示例:
let someFunction=(valueOrFunction)=>{
    let f = toCallback(valueOrFunction); 
    [1,2,3].forEach(v=>console.log(`value passed: ${f()}`))   
}

现在您可以以两种方式执行上述操作:

someFunction(5);
//value passed: 5
//value passed: 5
//value passed: 5

someFunction(()=>Math.random());
//value passed: 0.3218832537523002
//value passed: 0.2343617971814611
//value passed: 0.38216627030533656

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