Das Aliasing von JavaScript-Funktionen scheint nicht zu funktionieren

85

Ich habe gerade diese Frage gelesen und wollte die Alias-Methode anstelle der Function-Wrapper-Methode ausprobieren, aber ich konnte sie weder in Firefox 3 oder 3.5beta4 noch in Google Chrome in ihren Debug-Fenstern und in Google Chrome zum Laufen bringen in einer Test-Webseite.

Firebug:

>>> window.myAlias = document.getElementById
function()
>>> myAlias('item1')
>>> window.myAlias('item1')
>>> document.getElementById('item1')
<div id="item1">

Wenn ich es auf eine Webseite stelle, gibt mir der Aufruf von myAlias ​​den folgenden Fehler:

uncaught exception: [Exception... "Illegal operation on WrappedNative prototype object" nsresult: "0x8057000c (NS_ERROR_XPC_BAD_OP_ON_WN_PROTO)" location: "JS frame :: file:///[...snip...]/test.html :: <TOP_LEVEL> :: line 7" data: no]

Chrome (mit >>> zur Verdeutlichung eingefügt):

>>> window.myAlias = document.getElementById
function getElementById() { [native code] }
>>> window.myAlias('item1')
TypeError: Illegal invocation
>>> document.getElementById('item1')
<div id=?"item1">?

Und auf der Testseite erhalte ich den gleichen "Illegalen Aufruf".

Mache ich etwas falsch? Kann jemand anderes dies reproduzieren?

Seltsamerweise habe ich es auch gerade versucht und es funktioniert in IE8.

Kev
quelle
1
in IE8 - es könnte eine Art magische Funktion sein oder so.
Maciej Łebkowski
Ich habe Kommentare zu den falschen Antworten unter stackoverflow.com/questions/954417 hinzugefügt und sie hier weitergeleitet.
Grant Wagner
1
Für Browser, die die Function.prototype.bind-Methode unterstützen: window.myAlias ​​= document.getElementById.bind (document); Durchsuchen Sie die MDN-Dokumente. Es wird ein Bind Shim angezeigt.
Ben Fleming

Antworten:

39

Sie müssen diese Methode an das Dokumentobjekt binden. Aussehen:

>>> $ = document.getElementById
getElementById()
>>> $('bn_home')
[Exception... "Cannot modify properties of a WrappedNative" ... anonymous :: line 72 data: no]
>>> $.call(document, 'bn_home')
<body id="bn_home" onload="init();">

Wenn Sie einen einfachen Alias ​​ausführen, wird die Funktion für das globale Objekt und nicht für das Dokumentobjekt aufgerufen. Verwenden Sie eine Technik namens Verschlüsse, um dies zu beheben:

function makeAlias(object, name) {
    var fn = object ? object[name] : null;
    if (typeof fn == 'undefined') return function () {}
    return function () {
        return fn.apply(object, arguments)
    }
}
$ = makeAlias(document, 'getElementById');

>>> $('bn_home')
<body id="bn_home" onload="init();">

Auf diese Weise verlieren Sie nicht den Verweis auf das Originalobjekt.

2012 gibt es die neue bindMethode von ES5, mit der wir dies auf schickere Weise tun können:

>>> $ = document.getElementById.bind(document)
>>> $('bn_home')
<body id="bn_home" onload="init();">
Maciej Łebkowski
quelle
4
Welches ist wirklich ein Wrapper, weil es eine neue Funktion zurückgibt. Ich denke, die Antwort auf Kevs Frage ist, dass es aus den oben beschriebenen Gründen nicht möglich ist. Die hier beschriebene Methode ist wahrscheinlich die beste Option.
Jiggy
Vielen Dank für Ihren Kommentar, ich hatte nicht genau genug hingeschaut, um das zu realisieren. Die Syntax in den Antworten auf die andere Frage ist also völlig falsch? Interessant ...
Kev
188

Ich habe tief gegraben, um dieses spezielle Verhalten zu verstehen, und ich glaube, ich habe eine gute Erklärung gefunden.

Bevor ich document.getElementByIderkläre , warum Sie keinen Alias ​​verwenden können , werde ich versuchen zu erklären, wie JavaScript-Funktionen / -Objekte funktionieren.

Wenn Sie eine JavaScript-Funktion aufrufen, bestimmt der JavaScript-Interpreter einen Bereich und übergibt ihn an die Funktion.

Betrachten Sie folgende Funktion:

function sum(a, b)
{
    return a + b;
}

sum(10, 20); // returns 30;

Diese Funktion wird im Fensterbereich deklariert. Wenn Sie sie aufrufen, ist der Wert thisinnerhalb der Summenfunktion das globale WindowObjekt.

Für die 'Summen'-Funktion spielt es keine Rolle, welchen Wert' dies 'hat, da sie nicht verwendet wird.


Betrachten Sie folgende Funktion:

function Person(birthDate)
{
    this.birthDate = birthDate;    
    this.getAge = function() { return new Date().getFullYear() - this.birthDate.getFullYear(); };
}

var dave = new Person(new Date(1909, 1, 1)); 
dave.getAge(); //returns 100.

Wenn Sie dave.getAge Funktion aufrufen, sieht der JavaScript - Interpreter , dass Sie getAge Funktion auf dem fordern daveObjekt, so setzt sie thisauf daveund ruft die getAgeFunktion. getAge()wird korrekt zurückkehren 100.


Möglicherweise wissen Sie, dass Sie in JavaScript den Bereich mithilfe der applyMethode angeben können . Lass es uns versuchen.

var dave = new Person(new Date(1909, 1, 1)); //Age 100 in 2009
var bob = new Person(new Date(1809, 1, 1)); //Age 200 in 2009

dave.getAge.apply(bob); //returns 200.

In der obigen Zeile übergeben Sie den Bereich manuell als bobObjekt , anstatt JavaScript den Bereich bestimmen zu lassen . getAgewird jetzt zurückkehren 200, obwohl Sie dachten, Sie hätten getAgedas daveObjekt aufgerufen .


Was ist der Sinn all dieser Dinge? Funktionen werden "lose" an Ihre JavaScript-Objekte angehängt. ZB kannst du tun

var dave = new Person(new Date(1909, 1, 1));
var bob = new Person(new Date(1809, 1, 1));

bob.getAge = function() { return -1; };

bob.getAge(); //returns -1
dave.getAge(); //returns 100

Machen wir den nächsten Schritt.

var dave = new Person(new Date(1909, 1, 1));
var ageMethod = dave.getAge;

dave.getAge(); //returns 100;
ageMethod(); //returns ?????

ageMethodAusführung wirft einen Fehler! Was ist passiert?

Wenn Sie meine obigen Punkte sorgfältig lesen, werden Sie feststellen, dass die dave.getAgeMethode mit daveals thisObjekt aufgerufen wurde, während JavaScript den 'Umfang' für die ageMethodAusführung nicht bestimmen konnte . Also hat es das globale "Fenster" als "dies" übergeben. Da windowkeine birthDateEigenschaft vorhanden ist, schlägt die ageMethodAusführung fehl.

Wie kann ich das beheben? Einfach,

ageMethod.apply(dave); //returns 100.

Hat all das Sinn gemacht? Wenn dies der Fall ist, können Sie erklären, warum Sie keinen Alias ​​verwenden können document.getElementById:

var $ = document.getElementById;

$('someElement'); 

$heißt mit windowasthis und wenn die getElementByIdImplementierung dies erwartet this, schlägt documentsie fehl.

Um dies zu beheben, können Sie dies erneut tun

$.apply(document, ['someElement']);

Warum funktioniert es in Internet Explorer?

Ich kenne die interne Implementierung getElementByIdin IE nicht, aber ein Kommentar in der jQuery-Quelle ( inArrayMethodenimplementierung) besagt, dass in IEwindow == document . Wenn dies der Fall ist, document.getElementByIdsollte Aliasing im IE funktionieren.

Um dies weiter zu veranschaulichen, habe ich ein ausführliches Beispiel erstellt. Schauen Sie sich die PersonFunktion unten an.

function Person(birthDate)
{
    var self = this;

    this.birthDate = birthDate;

    this.getAge = function()
    {
        //Let's make sure that getAge method was invoked 
        //with an object which was constructed from our Person function.
        if(this.constructor == Person)
            return new Date().getFullYear() - this.birthDate.getFullYear();
        else
            return -1;
    };

    //Smarter version of getAge function, it will always refer to the object
    //it was created with.
    this.getAgeSmarter = function()
    {
        return self.getAge();
    };

    //Smartest version of getAge function.
    //It will try to use the most appropriate scope.
    this.getAgeSmartest = function()
    {
        var scope = this.constructor == Person ? this : self;
        return scope.getAge();
    };

}

Für die Person obige Funktion getAgeverhalten sich die verschiedenen Methoden wie folgt.

Lassen Sie uns zwei Objekte mit der PersonFunktion erstellen .

var yogi = new Person(new Date(1909, 1,1)); //Age is 100
var anotherYogi = new Person(new Date(1809, 1, 1)); //Age is 200

console.log(yogi.getAge()); //Output: 100.

Die getAge-Methode ruft direkt das yogiObjekt ab thisund gibt es aus 100.


var ageAlias = yogi.getAge;
console.log(ageAlias()); //Output: -1

Der JavaScript-Interepreter setzt das windowObjekt auf thisund unsere getAgeMethode gibt zurück -1.


console.log(ageAlias.apply(yogi)); //Output: 100

Wenn wir den richtigen Bereich festlegen, können Sie die ageAliasMethode verwenden.


console.log(ageAlias.apply(anotherYogi)); //Output: 200

Wenn wir ein Objekt einer anderen Person übergeben, wird das Alter trotzdem korrekt berechnet.

var ageSmarterAlias = yogi.getAgeSmarter;    
console.log(ageSmarterAlias()); //Output: 100

Die ageSmarterFunktion hat das ursprüngliche thisObjekt erfasst, sodass Sie sich jetzt nicht mehr um die Bereitstellung des richtigen Bereichs kümmern müssen.


console.log(ageSmarterAlias.apply(anotherYogi)); //Output: 100 !!!

Das Problem dabei ageSmarterist, dass Sie den Bereich niemals auf ein anderes Objekt festlegen können.


var ageSmartestAlias = yogi.getAgeSmartest;
console.log(ageSmartestAlias()); //Output: 100
console.log(ageSmartestAlias.apply(document)); //Output: 100

Die ageSmartestFunktion verwendet den ursprünglichen Bereich, wenn ein ungültiger Bereich angegeben wird.


console.log(ageSmartestAlias.apply(anotherYogi)); //Output: 200

Sie können weiterhin ein anderes PersonObjekt übergeben getAgeSmartest. :) :)

SolutionYogi
quelle
7
+1 für warum IE funktioniert. Der Rest ist etwas abseits des Themas, aber im Allgemeinen immer noch hilfreich. :)
Kev
44
Hervorragende Antwort. Ich sehe jedoch nicht, wie etwas davon vom Thema abweicht.
Justin Johnson
Sehr schöne Antwort. Eine etwas kürzere Version ist hier geschrieben .
Marcel Korpel
8
Eine der besten Antworten, die ich beim Stackoverflow gesehen habe.
Warbaker
Warum funktioniert es im IE? weil: stackoverflow.com/questions/6994139/… - so kann diese Methodenimplementierung tatsächlich {return window [id]} sein, so dass es in jedem Kontext funktionieren würde
Maciej Łebkowski
3

Dies ist eine kurze Antwort.

Im Folgenden wird eine Kopie (ein Verweis auf) der Funktion erstellt. Das Problem ist, dass sich die Funktion jetzt auf dem windowObjekt befindet, als sie auf dem documentObjekt leben soll.

window.myAlias = document.getElementById

Die Alternativen sind

  • einen Wrapper verwenden (bereits von Fabien Ménager erwähnt)
  • oder Sie können zwei Aliase verwenden.

    window.d = document // A renamed reference to the object
    window.d.myAlias = window.d.getElementById
Bryan Field
quelle
2

Eine weitere kurze Antwort, nur für Wrapping / Aliasing console.logund ähnliche Protokollierungsmethoden. Sie alle erwarten, im consoleKontext zu sein.

Dies ist nützlich, wenn Sie console.logmit einigen Fallbacks umbrechen, falls Sie oder Ihre Benutzer Probleme haben, wenn Sie einen Browser verwenden, der dies (immer) nicht unterstützt . Dies ist jedoch keine vollständige Lösung für dieses Problem, da erweiterte Überprüfungen und ein Fallback erforderlich sind - Ihr Kilometerstand kann variieren.

Beispiel mit Warnungen

var warn = function(){ console.warn.apply(console, arguments); }

Dann benutze es wie gewohnt

warn("I need to debug a number and an object", 9999, { "user" : "Joel" });

Wenn Sie es vorziehen, dass Ihre Protokollierungsargumente in ein Array eingeschlossen werden (das tue ich meistens), ersetzen Sie .apply(...)durch .call(...).

Sollte die Arbeit mit console.log(), console.debug(), console.info(), console.warn(), console.error(). Siehe auch consoleauf MDN .

Joel Purra
quelle
1

Neben anderen guten Antworten gibt es die einfache jQuery-Methode $ .proxy .

Sie können Alias ​​wie folgt:

myAlias = $.proxy(document, 'getElementById');

Oder

myAlias = $.proxy(document.getElementById, document);
Sanghyun Lee
quelle
+1, weil es eine praktikable Lösung ist. Aber genau genommen ist hinter den Kulissen $.proxyauch wirklich ein Wrapper.
Pimvdb
-6

Sie können eine Funktion für ein vordefiniertes Objekt nicht als "reinen Alias" festlegen. Daher ist das Aliasing, das Sie ohne Umbruch erhalten können, am nächsten, wenn Sie im selben Objekt bleiben:

>>> document.s = document.getElementById;
>>> document.s('myid');
<div id="myid">
Kev
quelle
5
Das ist etwas falsch. Sie können eine Funktion als "reinen Alias" festlegen, vorausgesetzt, die Funktionsimplementierung verwendet "this". Es kommt also darauf an.
SolutionYogi
Sorry, ich meinte im Kontext von getElementById. Du hast recht. Ich habe die Antwort leicht geändert, um dies widerzuspiegeln ...
Kev