web-dev-qa-db-fra.com

"TypeError non capturé: invocation illégale" dans Chrome

Lorsque j'utilise requestAnimationFrame pour créer des animations natives 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

Appeler directement le support.animationFrame vous donnera ...

TypeError non capturé: invocation illégale

en chrome. Pourquoi?

127
stefan

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 actuel (c'est-à-dire, 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 arrive 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 du standard ES5 et est 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
};
180
Nemoy

Vous pouvez aussi utiliser:

var obj = {
    alert: alert.bind(window)
};
obj.alert('I´m an alert!!');
16
afmeva

Lorsque vous exécutez une méthode (c'est-à-dire une fonction affectée à un objet), vous pouvez y utiliser la variable this 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 variable this 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 méthode requestAnimationFrame de window à un autre objet. Les fonctions natives, telles que celle-ci, bénéficient d'une protection intégrée contre leur exécution dans un autre contexte.

Il existe une fonction Function.prototype.call() , qui vous permet d'appeler une fonction dans un autre contexte. Vous devez juste le passer (l'objet qui sera utilisé comme contexte) comme premier paramètre de cette méthode. Par exemple, alert.call({}) donne TypeError: Illegal invocation. Cependant, alert.call(window) fonctionne bien, car désormais alert est 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 requestAnimationFrame est exécuté dans le cadre de window à la place 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() . Son effet est similaire à celui de .call(), mais au lieu d'appeler la fonction, il crée une nouvelle fonction qui sera toujours appelée dans un 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 de Function.prototype.bind() est qu’il fait partie d’ECMAScript 5, qui n’est pas pris en charge dans IE <= 8 . Heureusement, il y a n polyfill sur MDN .

Comme vous l'avez probablement déjà compris, vous pouvez utiliser .bind() pour toujours exécuter requestAnimationFrame dans 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() {});.

10