"Error de tipo no detectado: invocación ilegal" en Chrome

137

Cuando solía requestAnimationFramehacer una animación nativa compatible con el siguiente código:

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

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

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

Llamar directamente a la support.animationFramevoluntad dará ...

TypeError no capturado: invocación ilegal

en cromo ¿Por qué?

stefan
fuente

Respuestas:

195

En su código, está asignando un método nativo a una propiedad de objeto personalizado. Cuando llama support.animationFrame(function () {}), se ejecuta en el contexto del objeto actual (es decir, soporte). Para que la función requestAnimationFrame nativa funcione correctamente, debe ejecutarse en el contexto de window.

Entonces el uso correcto aquí es support.animationFrame.call(window, function() {});.

Lo mismo sucede con la alerta también:

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 

Otra opción es utilizar Function.prototype.bind (), que forma parte del estándar ES5 y está disponible en todos los navegadores modernos.

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

var support = {
   animationFrame: _raf ? _raf.bind(window) : null
};
Nemoy
fuente
1
A partir de Chrome 33, la segunda llamada también falla con "Invocación ilegal". ¡Feliz de eliminar el voto negativo una vez que se actualiza la respuesta !
Dan Dascalescu
@DanDascalescu: estoy usando Chrome 33 y está funcionando para mí.
Nemoy
1
Acabo de copiar y pegar su código y aparece el error de invocación ilegal. Aquí está el screencast.
Dan Dascalescu
24
Definitivamente obtendrá un error de invocación ilegal, porque la primera declaración myObj.myAlert('this is an alert');es ilegal. El uso correcto es myObj.myAlert.call(window, 'this is an alert'). Lea las respuestas correctamente e intente comprenderlas.
Nemoy
3
Si no soy el único aquí atascado al tratar de hacer que console.log.apply funcione de la misma manera, "esto" debería ser la consola, no la ventana: stackoverflow.com/questions/8159233/…
Alex
17

También puedes usar:

var obj = {
    alert: alert.bind(window)
};
obj.alert('I´m an alert!!');
afmeva
fuente
2
Esto no responde completamente la pregunta. Creo que debería ser un comentario, no una respuesta.
Michał Perłakowski
2
Además, es importante unirse al objeto apropiado, por ejemplo, cuando se trabaja con history.replaceState, se debe usar: var realReplaceState = history.replaceState.bind(history);
DeeY
@DeeY: ¡gracias por responder mi pregunta! Para las personas futuras, localStorage.clear requiere que usted .bind(localStorage)no lo haga .bind(window).
Samyok Nepal
13

Cuando ejecuta un método (es decir, una función asignada a un objeto), dentro de él puede usar la thisvariable para referirse a este objeto, por ejemplo:

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

Si asigna un método de un objeto a otro, su thisvariable se refiere al nuevo objeto, por ejemplo:

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

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

anotherObj.someMethod(); // logs false

Lo mismo sucede cuando asigna un requestAnimationFramemétodo windowa otro objeto. Las funciones nativas, como esta, tienen protección incorporada contra su ejecución en otro contexto.

Hay una Function.prototype.call()función que le permite llamar a una función en otro contexto. Solo tiene que pasarlo (el objeto que se usará como contexto) como primer parámetro de este método. Por ejemplo alert.call({})da TypeError: Illegal invocation. Sin embargo, alert.call(window)funciona bien, porque ahora alertse ejecuta en su alcance original.

Si usa .call()con su objeto así:

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

funciona bien, porque requestAnimationFramese ejecuta en el alcance de en windowlugar de su objeto.

Sin embargo, usar .call()cada vez que quiera llamar a este método, no es una solución muy elegante. En cambio, puedes usar Function.prototype.bind(). Tiene un efecto similar .call(), pero en lugar de llamar a la función, crea una nueva función que siempre se llamará en el contexto especificado. Por ejemplo:

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

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

El único inconveniente Function.prototype.bind()es que es parte de ECMAScript 5, que no es compatible con IE <= 8 . Afortunadamente, hay un polyfill en MDN .

Como probablemente ya haya descubierto, puede usar .bind()para ejecutar siempre requestAnimationFrameen contexto de window. Su código podría verse así:

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

Entonces puedes simplemente usar support.animationFrame(function() {});.

Michał Perłakowski
fuente