确定JavaScript函数是否为绑定函数。

31

有没有办法确定一个JavaScript函数是绑定函数

例如:

var obj = {
  x:1  
};

function printX() {
    document.write(this.x);
}

function takesACallback(cb) {
  // how can one determine if this is a bounded function
  // not just a function?
  if (typeof cb === 'function') {
    cb();  
  }
}

takesACallback(printX.bind(obj)); // 1
takesACallback(printX);           // undefined

也许这是一个重要的观点。我不是在问第二个调用为什么打印未定义。

1
每个JS函数都绑定到其作用域,那么这里的重点是什么?您想如何使用此信息? - crackmigg
这不是我想让它做的事情,我只是好奇是否有一种方法可以实现。 - robbmj
1
@Kyll:你仍然不应该这样做(太多魔法)。而且无论如何都没有区别,只需始终使用“call”,当函数被绑定时,您就不需要关心了。 - Bergi
1
@Kyll:对我来说,这听起来一点也不有用。调用者检查回调函数对传递值(API)的操作并不是他们的责任,即使只是一个简单的警告,也会导致大量的误报。 - Bergi
6个回答

38

绑定函数和箭头函数均没有 prototype 属性:

typeof (function() {}).prototype // 'object' as usual
typeof (function() {}).bind(null).prototype // 'undefined'!
typeof (() => {}).prototype // 'undefined'!

虽然您仍然可以手动分配此属性(尽管这很奇怪),但这并不是100%安全的。

因此,检查可绑定性的简单方法如下:

// ES5
function isBindable(func) {
  return func.hasOwnProperty('prototype');
}

// ES6
const isBindable = func => func.hasOwnProperty('prototype');

使用方法:

isBindable(function () {}); // true
isBindable(() => {}); // false
isBindable(
  (function () {}).bind(null)
); // false

通过这种方式,您可以确保已传递的函数能够处理动态的this

以下是一个示例用法,上述方法将失败:

const arrowFunc = () => {};
arrowFunc.prototype = 42;

isBindable(arrowFunc); // true :(
有趣的是,尽管绑定函数没有prototype属性,它们仍然可以作为构造函数使用(使用new):
var Animal = function(name) {
   this.name = name;
};

Animal.prototype.getName = function() {
  return this.name;
};

var squirrel = new Animal('squirrel');
console.log(squirrel.getName()); // prints "squirrel"

var MutatedAnimal = Animal.bind({}); // Radiation :)
console.log(MutatedAnimal.hasOwnProperty('prototype')); // prints "false"

var mutatedSquirrel = new MutatedAnimal('squirrel with two heads');
console.log(mutatedSquirrel.getName()); // prints "squirrel with two heads"

在这种情况下,原始函数prototype (Animal) 被使用。
请参见JS Bin,代码和链接由Dmitri Pavlutin提供。

当然,箭头函数无法用作构造函数,因此这种方法不适用于箭头函数。

不幸的是,我不知道有没有办法区分可用作构造函数的绑定函数和不可用作构造函数的箭头函数,而不需要通过try尝试使用它们,并检查是否抛出(new (() => {})会抛出一个"is not a constructor"错误)。


“这样你就可以确保传递的函数能够处理动态的this值” - 每个函数都可以处理动态的this值。有些函数(如绑定函数)只是不会使用你传递的那个。 - Bergi
它无法识别旧的顽固的 function() { method.apply(self, arguments) },它也不使用不同的 this,但被检测为另一种“函数类型”而不是 method.bind(self)。在任何地方都没有区分它们的意义;只需始终提供API即可。 - Bergi
你能提供一些关于你是如何发现bind函数后面缺少prototype的参考资料吗?谢谢。 - Alan Dong
@Kyll,FYI,我也做了实验。除了prototype之外,callerarguments也缺失于绑定函数的自有属性中。请参阅我的博客这里。我只是想看看是否有官方文件表明这种方式是真实可靠的。到目前为止,我什么都没找到。 - Alan Dong
@AlanDong,很遗憾我不在那个网络上,所以我看不到它。等我有时间时,我会深入研究规范,看看是否有相关文章。 - Kyll
非常好的了解。感谢有趣的见解。还有关于《扎克·麦克拉肯和外星人心灵》的提及。 - Kamafeather

20

4
这个测试比被接受的答案更好,因为原生函数通常也有一个空原型。例如,[].slice.prototype是未定义的。 - Lea Verou
3
检查这两个条件可能是最安全的方式:function isBound(func) { return func.name.startsWith('bound ') && !func.hasOwnProperty('prototype'); } - Tey'
错误的正面 const b = { ["绑定或者可能不是"]: _=>_ }["绑定或者可能不是"] - kigiri

5

可以覆盖现有的原型绑定,标记已经绑定的函数。

一个简单的解决方案。这可能会因为隐藏类而破坏V8(和其他运行时)中的某些优化。

(function (bind) {
  Object.defineProperties(Function.prototype, {
    'bind': {
      value: function (context) {
        var newf = bind.apply(this, arguments);
        newf.context = context;

        return newf;
      }
    },
    'isBound': {
      value: function () {
        return this.hasOwnProperty('context');
      }
    }
  });
}(Function.prototype.bind));

在运动中:

(function (bind) {
  Object.defineProperties(Function.prototype, {
    'bind': {
      value: function (context) {
        var newf = bind.apply(this, arguments);
        newf.context = context;

        return newf;
      }
    },
    'isBound': {
      value: function () {
        return this.hasOwnProperty('context');
      }
    }
  });
}(Function.prototype.bind));

var a = function () {
  console.log(this);
};
var b = {
  b: true
};
var c = a.bind(b);

console.log(a.isBound())
console.log(c.isBound())
console.log(c.context === b);
a();
c();


我已经进行了一些测试,似乎并没有太大的性能影响!https://jsperf.com/bind-override/1 - Peter

0
你需要在原型上编写自己的bind函数。该函数将构建一个已绑定内容的索引。
然后,你可以编写另一个函数来对存储了该索引的对象进行查找。

这是正确的,我知道如果我要自己实现绑定,我该怎么做。也许你可以分享一个实现方法给未来的读者。 - robbmj
1
但这确实是您唯一的选择,因为printX()printX.bind(this)相同,其中this是您当前的作用域。 - crackmigg
我需要仔细阅读最佳答案并决定是否要完全删除它。 - New Alexandria
请不要因为这实际上是唯一的技术方法而对此进行负面评价。尽管如此,在绝大多数情况下,这可能是一个坏主意。 - freeeman

0

根据之前的答案,我创建了一个函数来确定:

function isBoundFunction(func) {
    if(typeof func.prototype === 'object') return false
    try {
        new func()
    }
    catch(e) {
        return false
    }
    return true
}

这个函数确定三种类型的函数:1. 原始函数,其原型是对象,2. 箭头函数,不能用作构造函数,3. 绑定函数。


2
是的,尽管new func()可能会产生重大副作用(例如,如果实现了对象池,那么就可能出现内存泄漏问题)。 - Kyll

-1

有一个模块可以帮助您解决这个问题:bind2

这是一个使用案例:

const bind2 = require('bind2');

function testFunc() {
  return this.hello;
}
const context = { hello: 'world' };

const boundFunc = bind2(testFunc, context);
console.log(boundFunc.bound); // true

完全透露:我编写了这个模块。


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