"Nicht erfasster TypeError: Unzulässiger Aufruf" in Chrome

136

Wenn ich requestAnimationFrameeine native unterstützte Animation mit dem folgenden Code mache:

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

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

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

Direkt anrufen support.animationFramewird geben ...

Nicht erfasster TypeError: Unzulässiger Aufruf

in Chrome. Warum?

stefan
quelle

Antworten:

194

In Ihrem Code weisen Sie einer Eigenschaft eines benutzerdefinierten Objekts eine native Methode zu. Wenn Sie aufrufen support.animationFrame(function () {}), wird es im Kontext des aktuellen Objekts (dh der Unterstützung) ausgeführt. Damit die native requestAnimationFrame-Funktion ordnungsgemäß funktioniert, muss sie im Kontext von ausgeführt werden window.

Die richtige Verwendung hier ist also support.animationFrame.call(window, function() {});.

Das gleiche passiert auch mit Alarm:

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 

Eine weitere Option ist die Verwendung von Function.prototype.bind (), das Teil des ES5-Standards ist und in allen modernen Browsern verfügbar ist.

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

var support = {
   animationFrame: _raf ? _raf.bind(window) : null
};
Nemoy
quelle
1
Ab Chrome 33 schlägt der zweite Aufruf ebenfalls mit "Illegal Invocation" fehl. Gerne entfernen wir die Downvote, sobald die Antwort aktualisiert wurde !
Dan Dascalescu
@ DanDascalescu: Ich verwende Chrome 33 und es funktioniert für mich.
Nemoy
1
Ich habe gerade Ihren Code kopiert und erhalte den Fehler "Unzulässiger Aufruf". Hier ist der Screencast.
Dan Dascalescu
24
Sie werden definitiv einen illegalen Aufruffehler erhalten, da das erste Stamtement myObj.myAlert('this is an alert');illegal ist. Richtige Verwendung ist myObj.myAlert.call(window, 'this is an alert'). Bitte lesen Sie die Antworten richtig und versuchen Sie sie zu verstehen.
Nemoy
3
Wenn ich nicht der einzige hier bin, der versucht, console.log.apply auf die gleiche Weise zum Laufen zu bringen, sollte "dies" die Konsole sein, nicht das Fenster: stackoverflow.com/questions/8159233/…
Alex
17

Sie können auch verwenden:

var obj = {
    alert: alert.bind(window)
};
obj.alert('I´m an alert!!');
afmeva
quelle
2
Dies beantwortet die Frage nicht vollständig. Ich denke, es sollte eher ein Kommentar sein, keine Antwort.
Michał Perłakowski
2
Es ist auch wichtig, an ein geeignetes Objekt zu binden, z. B. wenn Sie mit history.replaceState arbeiten, sollten Sie var realReplaceState = history.replaceState.bind(history);
Folgendes
@DeeY: Danke für die Beantwortung meiner Frage! Für zukünftige Leute verlangt localStorage.clear, dass Sie dies .bind(localStorage)nicht tun .bind(window).
Samyok Nepal
13

Wenn Sie eine Methode ausführen (dh eine einem Objekt zugewiesene Funktion), können Sie darin eine thisVariable verwenden, um auf dieses Objekt zu verweisen, zum Beispiel:

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

Wenn Sie eine Methode von einem Objekt zu einem anderen zuweisen, thisbezieht sich ihre Variable auf das neue Objekt, zum Beispiel:

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

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

anotherObj.someMethod(); // logs false

Das gleiche passiert, wenn Sie einem anderen Objekt eine requestAnimationFrameMethode zuweisen window. Native Funktionen wie diese verfügen über einen integrierten Schutz vor der Ausführung in einem anderen Kontext.

Es gibt eine Function.prototype.call()Funktion, mit der Sie eine Funktion in einem anderen Kontext aufrufen können. Sie müssen es nur (das Objekt, das als Kontext verwendet wird) als ersten Parameter an diese Methode übergeben. Zum Beispiel alert.call({})gibt TypeError: Illegal invocation. Funktioniert jedoch alert.call(window)einwandfrei, da jetzt alertim ursprünglichen Bereich ausgeführt wird.

Wenn Sie .call()mit Ihrem Objekt so verwenden:

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

es funktioniert gut, weil requestAnimationFramees im Bereich von windowanstelle Ihres Objekts ausgeführt wird.

Es .call()ist jedoch keine sehr elegante Lösung , jedes Mal zu verwenden, wenn Sie diese Methode aufrufen möchten. Stattdessen können Sie verwenden Function.prototype.bind(). Es hat einen ähnlichen Effekt wie .call(), aber anstatt die Funktion aufzurufen, wird eine neue Funktion erstellt, die immer im angegebenen Kontext aufgerufen wird. Beispielsweise:

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

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

Der einzige Nachteil Function.prototype.bind()ist, dass es Teil von ECMAScript 5 ist, das in IE <= 8 nicht unterstützt wird . Glücklicherweise gibt es eine Polyfüllung auf MDN .

Wie Sie wahrscheinlich bereits herausgefunden haben, können Sie verwenden, .bind()um immer requestAnimationFrameim Kontext von auszuführen window. Ihr Code könnte folgendermaßen aussehen:

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

Dann können Sie einfach verwenden support.animationFrame(function() {});.

Michał Perłakowski
quelle