"Uncaught TypeError: invocation illégale" dans Chrome

137

Quand j'utilise requestAnimationFramepour faire une animation native prise en charge avec le code ci-dessous:

var support = {
    animationFrame: window.requestAnimationFrame ||
        window.mozRequestAnimationFrame ||
        window.webkitRequestAnimationFrame ||
        window.msRequestAnimationFrame ||
        window.oRequestAnimationFrame
};

support.animationFrame(function() {}); //error

support.animationFrame.call(window, function() {}); //right

Appelez directement le support.animationFrametestament ...

Uncaught TypeError: invocation illégale

dans Chrome. Pourquoi?

Stefan
la source

Réponses:

195

Dans votre code, vous affectez une méthode native à une propriété d'objet personnalisé. Lorsque vous appelez support.animationFrame(function () {}), il est exécuté dans le contexte de l'objet courant (ie support). Pour que la fonction native requestAnimationFrame fonctionne correctement, elle doit être exécutée dans le contexte de window.

Donc, l'utilisation correcte ici est support.animationFrame.call(window, function() {});.

La même chose se produit avec alert aussi:

var myObj = {
  myAlert : alert //copying native alert to an object
};

myObj.myAlert('this is an alert'); //is illegal
myObj.myAlert.call(window, 'this is an alert'); // executing in context of window 

Une autre option consiste à utiliser Function.prototype.bind () qui fait partie de la norme ES5 et disponible dans tous les navigateurs modernes.

var _raf = window.requestAnimationFrame ||
        window.mozRequestAnimationFrame ||
        window.webkitRequestAnimationFrame ||
        window.msRequestAnimationFrame ||
        window.oRequestAnimationFrame;

var support = {
   animationFrame: _raf ? _raf.bind(window) : null
};
Nemoy
la source
1
À partir de Chrome 33, le deuxième appel échoue également avec "Invocation illégale". Heureux de supprimer le vote défavorable une fois la réponse mise à jour !
Dan Dascalescu
@DanDascalescu: J'utilise chrome 33 et cela fonctionne pour moi.
Nemoy
1
Je viens de copier-coller votre code et d'obtenir l'erreur d'invocation illégale. Voici le screencast.
Dan Dascalescu
24
Vous obtiendrez certainement une erreur d'invocation illégale, car le premier stamtement myObj.myAlert('this is an alert');est illégal. L'utilisation correcte est myObj.myAlert.call(window, 'this is an alert'). Veuillez lire les réponses correctement et essayez de les comprendre.
Nemoy
3
Si je ne suis pas le seul ici à essayer de faire fonctionner console.log.apply de la même manière, "ceci" devrait être la console, pas la fenêtre: stackoverflow.com/questions/8159233/…
Alex
17

Vous pouvez aussi utiliser:

var obj = {
    alert: alert.bind(window)
};
obj.alert('I´m an alert!!');
Afmeva
la source
2
Cela ne répond pas entièrement à la question. Je pense que ce devrait être plutôt un commentaire, pas une réponse.
Michał Perłakowski
2
En outre, il est important de se lier à un objet approprié, par exemple lorsque vous travaillez avec history.replaceState, il faut utiliser: var realReplaceState = history.replaceState.bind(history);
DeeY
@DeeY: merci d'avoir répondu à ma question! Pour les futurs utilisateurs, localStorage.clear vous oblige à le faire .bind(localStorage), non .bind(window).
Samyok Nepal
13

Lorsque vous exécutez une méthode (c'est-à-dire une fonction affectée à un objet), à l'intérieur, vous pouvez utiliser une thisvariable pour faire référence à cet objet, par exemple:

var obj = {
  someProperty: true,
  someMethod: function() {
    console.log(this.someProperty);
  }
};
obj.someMethod(); // logs true

Si vous affectez une méthode d'un objet à un autre, sa thisvariable fait référence au nouvel objet, par exemple:

var obj = {
  someProperty: true,
  someMethod: function() {
    console.log(this.someProperty);
  }
};

var anotherObj = {
  someProperty: false,
  someMethod: obj.someMethod
};

anotherObj.someMethod(); // logs false

La même chose se produit lorsque vous affectez la requestAnimationFrameméthode de windowà un autre objet. Les fonctions natives, comme celle-ci, ont une protection intégrée contre son exécution dans un autre contexte.

Il existe une Function.prototype.call()fonction qui vous permet d'appeler une fonction dans un autre contexte. Il suffit de le passer (l'objet qui servira de contexte) comme premier paramètre à cette méthode. Par exemple alert.call({})donne TypeError: Illegal invocation. Cependant, alert.call(window)fonctionne bien, car maintenant alertest exécuté dans sa portée d'origine.

Si vous utilisez .call()avec votre objet comme ça:

support.animationFrame.call(window, function() {});

cela fonctionne bien, car il requestAnimationFrameest exécuté dans la portée de la windowplace de votre objet.

Cependant, utiliser .call()chaque fois que vous souhaitez appeler cette méthode n'est pas une solution très élégante. Au lieu de cela, vous pouvez utiliser Function.prototype.bind(). Il a un effet similaire à .call(), mais au lieu d'appeler la fonction, il crée une nouvelle fonction qui sera toujours appelée dans le contexte spécifié. Par exemple:

window.someProperty = true;
var obj = {
  someProperty: false,
  someMethod: function() {
    console.log(this.someProperty);
  }
};

var someMethodInWindowContext = obj.someMethod.bind(window);
someMethodInWindowContext(); // logs true

Le seul inconvénient Function.prototype.bind()est qu'il fait partie d'ECMAScript 5, qui n'est pas pris en charge dans IE <= 8 . Heureusement, il existe un polyfill sur MDN .

Comme vous l'avez probablement déjà compris, vous pouvez utiliser .bind()pour toujours exécuter requestAnimationFramedans le contexte de window. Votre code pourrait ressembler à ceci:

var support = {
    animationFrame: (window.requestAnimationFrame ||
        window.mozRequestAnimationFrame ||
        window.webkitRequestAnimationFrame ||
        window.msRequestAnimationFrame ||
        window.oRequestAnimationFrame).bind(window)
};

Ensuite, vous pouvez simplement utiliser support.animationFrame(function() {});.

Michał Perłakowski
la source