web-dev-qa-db-fra.com

Déterminer si une fonction JavaScript est une fonction liée

Existe-t-il un moyen de déterminer si une fonction JavaScript est une fonction liée ?

Exemple:

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

C'est peut-être un point important. Je ne demande pas pourquoi le deuxième appel imprime non défini.

19
robbmj

Les fonctions liées et les fonctions de flèche ne possèdent pas de propriété prototype:

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

Ce n'est pas sûr à 100% puisque vous pouvez toujours assigner cette propriété manuellement (bien que ce soit bizarre).
En tant que tel, un moyen simple de vérifier la possibilité de rattachement serait le suivant:

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

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

Usage:

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

De cette façon, vous pouvez vous assurer que la fonction qui a été passée peut gérer une variable dynamique this.

Voici un exemple d'utilisation pour lequel ce qui précède échoue:

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

isBindable(arrowFunc); // true :(

Fait intéressant, bien que les fonctions liées ne possèdent pas de propriété prototype, elles peuvent quand même être utilisées en tant que constructeurs (avec 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"

Dans ce cas, la fonction d'origine prototype (Animal) est utilisée à la place.
Voir JS Bin , code et lien courtoisie de Dmitri Pavlutin .

Bien entendu, cela ne fonctionnera pas avec les fonctions de flèche car elles ne peuvent pas être utilisées en tant que constructeurs.

Malheureusement, je ne sais pas s’il existe un moyen de distinguer une fonction liée (utilisable en tant que constructeur) d’une fonction de flèche (non utilisable en tant que constructeur) sans que try les élimine avec new et vérifie si elle jette (new (() => {}) jette un "n'est pas un constructeur" erreur).

30
Paul Stenne

Dans les environnements prenant en charge ES6, vous pouvez vérifier si le nom de la fonction commence par "bound " (le mot "lié" suivi d'un espace).

De la spec:

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

[...] 

15. Exécutez SetFunctionName (F, targetName , "lié").

Bien entendu, cela pourrait entraîner de faux positifs si le nom de la fonction était modifié manuellement.

10
Felix Kling

On pourrait remplacer la liaison prototype existante, en balisant les fonctions liées.

Une solution simple Cela va probablement tuer certaines optimisations de la V8 (et éventuellement d’autres runtimes) à cause des classes cachées.

(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));

En mouvement:

(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();

3
Oka

Sur la base des réponses précédentes, je crée une fonction pour déterminer:

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

Cette fonction détermine trois types de fonctions: 1. la fonction d'origine, dont le prototype est un objet, 2. la fonction de flèche, qui ne peut pas être utilisée en tant que constructeur, 3. la fonction liée.

1
frustigor

Vous devrez écrire votre propre fonction bind sur le prototype. Cette fonction créerait un index de ce qui a été lié. 

Vous pourriez alors avoir une autre fonction pour effectuer une recherche sur l'objet où cet index est stocké.

0
New Alexandria

Je suis nouveau ici et je n'ai pas assez de réputation pour poster des commentaires, juste des réponses. Désolé, cela ne répond pas au PO, car je ne sais pas comment le faire. Je voudrais avoir fait.

Cependant, les réponses très courantes reposant sur une propriété prototype manquante ne fonctionneront pas. Les méthodes de classe, les définitions de méthode dans les objets et les fonctions async n'ont pas non plus de propriétés prototype, mais elles ne sont pas naturellement liées.

De plus, gardez à l'esprit qu'il est toujours possible de lier manuellement une fonction par fermeture, ce qui empêcherait toute tentative de détecter son état lié:

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