“TypeError não capturado: invocação ilegal” no Chrome

136

Quando uso requestAnimationFramepara fazer alguma animação nativa suportada com o código abaixo:

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

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

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

Diretamente chamando o support.animationFramedará ...

Tipo não detectadoErro: invocação ilegal

no Chrome. Por quê?

Stefan
fonte

Respostas:

194

No seu código, você está atribuindo um método nativo a uma propriedade do objeto personalizado. Quando você chama support.animationFrame(function () {}), é executado no contexto do objeto atual (ou seja, suporte). Para que a função requestAnimationFrame nativa funcione corretamente, ela deve ser executada no contexto de window.

Portanto, o uso correto aqui é support.animationFrame.call(window, function() {});.

O mesmo acontece com o alerta também:

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 

Outra opção é usar Function.prototype.bind (), que faz parte do padrão ES5 e está disponível em todos os navegadores modernos.

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

var support = {
   animationFrame: _raf ? _raf.bind(window) : null
};
Nemoy
fonte
1
No Chrome 33, a segunda chamada também falha com "Invocação ilegal". É um prazer remover o voto negativo assim que a resposta for atualizada !
Dan Dascalescu
@ DanDascalescu: Estou usando o chrome 33 e está funcionando para mim.
Nemoy
1
Acabei de copiar o seu código e recebo o erro de invocação ilegal. Aqui está o screencast.
Dan Dascalescu 10/03/2014
24
Você definitivamente receberá um erro de invocação ilegal, porque a primeira declaração myObj.myAlert('this is an alert');é ilegal. O uso correto é myObj.myAlert.call(window, 'this is an alert'). Leia as respostas corretamente e tente entendê-las.
Nemoy
3
Se eu não sou o único aqui a tentar impedir que console.log.apply funcione da mesma maneira, "this" deve ser o console, não a janela: stackoverflow.com/questions/8159233/…
Alex
17

Você também pode usar:

var obj = {
    alert: alert.bind(window)
};
obj.alert('I´m an alert!!');
afmeva
fonte
2
Isso não responde totalmente à pergunta. Eu acho que deveria ser um comentário, não uma resposta.
Michał Perłakowski
2
Além disso, é importante vincular ao objeto apropriado, por exemplo, ao trabalhar com history.replaceState, deve-se usar: var realReplaceState = history.replaceState.bind(history);
DeeY 12/16
@ DeeY: obrigado por responder à minha pergunta! Para pessoas futuras, localStorage.clear exige que você .bind(localStorage)não .bind(window).
Samyok Nepal 08/12/19
13

Quando você executa um método (ou seja, função atribuída a um objeto), dentro dele, você pode usar a thisvariável para se referir a esse objeto, por exemplo:

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

Se você atribuir um método de um objeto para outro, sua thisvariável se refere ao novo objeto, por exemplo:

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

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

anotherObj.someMethod(); // logs false

O mesmo acontece quando você atribui o requestAnimationFramemétodo de windowa outro objeto. Funções nativas, como essa, têm proteção interna contra execução em outro contexto.

Existe uma Function.prototype.call()função que permite chamar uma função em outro contexto. Você apenas precisa passá-lo (o objeto que será usado como contexto) como um primeiro parâmetro para esse método. Por exemplo, alert.call({})TypeError: Illegal invocation. No entanto, alert.call(window)funciona bem, porque agora alerté executado em seu escopo original.

Se você usar .call()com seu objeto assim:

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

funciona bem, porque requestAnimationFrameé executado no escopo e windownão no seu objeto.

No entanto, usar .call()sempre que quiser chamar esse método não é uma solução muito elegante. Em vez disso, você pode usar Function.prototype.bind(). Ele tem efeito semelhante ao .call(), mas, em vez de chamar a função, cria uma nova função que sempre será chamada no contexto especificado. Por exemplo:

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

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

A única desvantagem Function.prototype.bind()disso é que ele faz parte do ECMAScript 5, que não é suportado no IE <= 8 . Felizmente, há um polyfill no MDN .

Como você provavelmente já descobriu, você pode usar .bind()para executar sempre requestAnimationFrameno contexto de window. Seu código pode ficar assim:

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

Então você pode simplesmente usar support.animationFrame(function() {});.

Michał Perłakowski
fonte