"Uncaught TypeError: Illegal invocation" in Chrome


137

Quando utilizzo requestAnimationFrameuna animazione nativa supportata con il codice seguente:

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

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

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

Chiamando direttamente il support.animationFramesarà dato ...

Uncaught TypeError: invocazione illegale

in Chrome. Perché?

Risposte:


195

Nel tuo codice stai assegnando un metodo nativo a una proprietà di oggetto personalizzato. Quando chiami support.animationFrame(function () {}), viene eseguito nel contesto dell'oggetto corrente (cioè supporto). Perché la funzione native requestAnimationFrame funzioni correttamente, deve essere eseguita nel contesto diwindow .

Quindi l'uso corretto qui è support.animationFrame.call(window, function() {});.

Lo stesso accade anche con l'avviso:

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 

Un'altra opzione è quella di utilizzare Function.prototype.bind () che fa parte dello standard ES5 e disponibile in tutti i browser moderni.

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

var support = {
   animationFrame: _raf ? _raf.bind(window) : null
};

1
A partire da Chrome 33, anche la seconda chiamata ha esito negativo con "Invocazione illegale". Felice di rimuovere il downvote una volta aggiornata la risposta !
Dan Dascalescu,

@DanDascalescu: sto usando Chrome 33 e funziona per me.
Nemoy

1
Ho appena incollato il tuo codice e ho ricevuto l'errore di invocazione illegale. Ecco lo screencast.
Dan Dascalescu,

24
Avrai sicuramente un errore di invocazione illegale, perché il primo stamtement myObj.myAlert('this is an alert');è illegale. L'uso corretto è myObj.myAlert.call(window, 'this is an alert'). Si prega di leggere le risposte correttamente e provare a capirlo.
Nemoy,

3
Se non sono l'unico qui bloccato a provare a far funzionare console.log.apply allo stesso modo, "questo" dovrebbe essere la console, non la finestra: stackoverflow.com/questions/8159233/…
Alex

17

Puoi anche usare:

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

2
Questo non risponde completamente alla domanda. Penso che dovrebbe piuttosto essere un commento, non una risposta.
Michał Perłakowski,

2
Inoltre, è importante associarsi all'oggetto appropriato, ad esempio quando si lavora con history.replaceState, si dovrebbe usare: var realReplaceState = history.replaceState.bind(history);
DeeY

@DeeY: grazie per aver risposto alla mia domanda! Per le persone future, localStorage.clear richiede di .bind(localStorage)no .bind(window).
Samyok Nepal,

13

Quando si esegue un metodo (ovvero una funzione assegnata a un oggetto), al suo interno è possibile utilizzare la thisvariabile per fare riferimento a questo oggetto, ad esempio:

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

Se si assegna un metodo da un oggetto a un altro, la sua thisvariabile si riferisce al nuovo oggetto, ad esempio:

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

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

anotherObj.someMethod(); // logs false

La stessa cosa accade quando si assegna il requestAnimationFramemetodo windowa un altro oggetto. Funzioni native, come questa, hanno una protezione integrata dall'esecuzione in altri contesti.

C'è una Function.prototype.call()funzione che ti consente di chiamare una funzione in un altro contesto. Devi solo passarlo (l'oggetto che verrà utilizzato come contesto) come primo parametro di questo metodo. Ad esempio alert.call({})TypeError: Illegal invocation. Tuttavia, alert.call(window)funziona bene, perché ora alertviene eseguito nel suo ambito originale.

Se lo usi .call()con il tuo oggetto in questo modo:

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

funziona benissimo, perché requestAnimationFrameviene eseguito nell'ambito windowinvece del tuo oggetto.

Tuttavia, utilizzare .call()ogni volta che si desidera chiamare questo metodo, non è una soluzione molto elegante. Invece, puoi usare Function.prototype.bind(). Ha un effetto simile a .call(), ma invece di chiamare la funzione, crea una nuova funzione che sarà sempre chiamata nel contesto specificato. Per esempio:

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

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

L'unico aspetto negativo di Function.prototype.bind()è che fa parte di ECMAScript 5, che non è supportato in IE <= 8 . Fortunatamente c'è un polyfill su MDN .

Come probabilmente hai già capito, puoi usare .bind()per eseguire sempre requestAnimationFramenel contesto di window. Il tuo codice potrebbe apparire così:

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

Quindi puoi semplicemente usare support.animationFrame(function() {});.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.