„Uncaught TypeError: Illegal invocation” w Chrome

137

Kiedy używam requestAnimationFramedo tworzenia natywnych obsługiwanych animacji z poniższym kodem:

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

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

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

Bezpośrednio dzwoniąc do support.animationFrametestamentu ...

Uncaught TypeError: Niedozwolone wywołanie

w przeglądarce Chrome. Czemu?

stefan
źródło

Odpowiedzi:

195

W swoim kodzie przypisujesz natywną metodę do właściwości obiektu niestandardowego. Kiedy dzwonisz support.animationFrame(function () {}), jest to wykonywane w kontekście bieżącego obiektu (tj. Wsparcia). Aby natywna funkcja requestAnimationFrame działała poprawnie, musi zostać wykonana w kontekście window.

Więc prawidłowe użycie jest tutaj support.animationFrame.call(window, function() {});.

To samo dzieje się z alertem:

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 

Inną opcją jest użycie funkcji Function.prototype.bind (), która jest częścią standardu ES5 i jest dostępna we wszystkich nowoczesnych przeglądarkach.

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

var support = {
   animationFrame: _raf ? _raf.bind(window) : null
};
Nemoy
źródło
1
Od Chrome 33 drugie wywołanie również kończy się niepowodzeniem z powodu „Nielegalnego wywołania”. Z przyjemnością usuniemy głos przeciwny, gdy odpowiedź zostanie zaktualizowana !
Dan Dascalescu
@DanDascalescu: Używam chrome 33 i to działa dla mnie.
Nemoy
1
Właśnie skopiowałem i wkleiłem Twój kod i otrzymałem błąd nielegalnego wywołania. Oto screencast.
Dan Dascalescu
24
Na pewno wystąpi błąd nielegalnego wywołania, ponieważ pierwsze stamtowanie myObj.myAlert('this is an alert');jest nielegalne. Prawidłowe użycie jest myObj.myAlert.call(window, 'this is an alert'). Przeczytaj odpowiedzi poprawnie i spróbuj je zrozumieć.
Nemoy
3
Jeśli nie jestem jedyną osobą, która utknęła na próbie uzyskania aplikacji console.log.apply do pracy w ten sam sposób, „ta” powinna być konsolą, a nie oknem: stackoverflow.com/questions/8159233/ ...
Alex
17

Możesz także użyć:

var obj = {
    alert: alert.bind(window)
};
obj.alert('I´m an alert!!');
afmeva
źródło
2
To nie w pełni odpowiada na pytanie. Myślę, że powinien to być raczej komentarz, a nie odpowiedź.
Michał Perłakowski
2
Ważne jest również, aby powiązać się z odpowiednim obiektem, np. Podczas pracy z var realReplaceState = history.replaceState.bind(history);
historią.replaceState
@DeeY: dziękuję za odpowiedź na moje pytanie! Dla przyszłych ludzi, localStorage.clear wymaga .bind(localStorage), nie .bind(window).
Samyok Nepal
13

Kiedy wykonujesz metodę (tj. Funkcję przypisaną do obiektu), wewnątrz niej możesz użyć thiszmiennej, aby odwołać się do tego obiektu, na przykład:

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

Jeśli przypiszesz metodę z jednego obiektu do innego, jej thiszmienna odwołuje się do nowego obiektu, na przykład:

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

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

anotherObj.someMethod(); // logs false

To samo dzieje się, gdy przypiszesz requestAnimationFramemetodę windowdo innego obiektu. Funkcje natywne, takie jak ta, mają wbudowaną ochronę przed wykonywaniem ich w innym kontekście.

Istnieje Function.prototype.call()funkcja, która umożliwia wywołanie funkcji w innym kontekście. Musisz tylko przekazać go (obiekt, który będzie używany jako kontekst) jako pierwszy parametr tej metody. Na przykład alert.call({})daje TypeError: Illegal invocation. Jednak alert.call(window)działa dobrze, ponieważ teraz alertjest wykonywany w swoim oryginalnym zakresie.

Jeśli używasz .call()ze swoim obiektem w ten sposób:

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

działa dobrze, ponieważ requestAnimationFramejest wykonywany w zakresie windowzamiast obiektu.

Jednak używanie za .call()każdym razem, gdy chcesz wywołać tę metodę, nie jest zbyt eleganckim rozwiązaniem. Zamiast tego możesz użyć Function.prototype.bind(). Ma podobny efekt .call(), ale zamiast wywoływać funkcję, tworzy nową funkcję, która zawsze będzie wywoływana w określonym kontekście. Na przykład:

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

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

Jedynym minusem Function.prototype.bind()jest to, że jest częścią ECMAScript 5, który nie jest obsługiwany w IE <= 8 . Na szczęście na MDN jest polyfill .

Jak prawdopodobnie już się zorientowałeś, możesz użyć, .bind()aby zawsze wykonywać requestAnimationFramew kontekście window. Twój kod może wyglądać następująco:

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

Następnie możesz po prostu użyć support.animationFrame(function() {});.

Michał Perłakowski
źródło