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

Есть ли способ определить, является ли функция 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

Обе связанные функции и функции стрелки не имеют свойства 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, код и ссылка любезно предоставлены Дмитрием Павлутиным.

Это, конечно, не будет работать со стрелочными функциями, поскольку они не могут использоваться как конструкторы.

К сожалению, я не знаю, есть ли способ отличить связанную функцию (используемую как конструктор) от функции стрелки (не используемой в качестве конструктора) без try с помощью new и проверки, если она throws (new (() => {}) вызывает ошибку "не является конструктором" ).

Ответ 2

В средах, поддерживающих ES6, вы можете проверить, начинается ли имя функции с "bound " (слово "bound", за которым следует пробел).

Из спецификации:

19.2.3.2 Function.prototype.bind(thisArg,... args)

[...]

15. Выполните SetFunctionName (F, targetName, "bound" ).

Конечно, это может привести к ложным срабатываниям, если имя функции было изменено вручную.

Ответ 3

Можно переопределить существующее связывание прототипа, привязанные функции тегов.

Простое решение. Это, скорее всего, убьет некоторые оптимизации в 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));

Ответ 4

Нет.

bind - это просто функция, которая возвращает новую функцию. Нет ничего особенного в этом вопросе, который указывает, что bind использовался для его создания.

Ответ 5

На основе предыдущих ответов я создаю функцию для определения:

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

Эта функция определяет три типа функций: 1. оригинальная функция, прототипом которой является объект, 2. функция стрелки, которая не может использоваться как конструктор, 3. связанная функция.

Ответ 6

Вам нужно будет написать свою собственную функцию bind на прототипе. Эта функция создаст индекс того, что было связано.

Затем у вас может быть другая функция для выполнения поиска по объекту, в котором хранится этот индекс.

Ответ 7

Я новичок здесь и не имею достаточной репутации для публикации комментариев, просто ответов. Извините, но это не отвечает OP, потому что я понятия не имею, как это сделать. Хотел бы я этого сделать.

Однако очень общие ответы, связанные с отсутствующим свойством prototype, не будут работать. Методы класса, определения методов в объектах и ​​функции async также не имеют свойств prototype, но они не связаны естественным образом.

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

const bind=(fn,obj)=>{
    return (...args)=>{
        return fn.apply(obj,args)
    }
}