Aliasing funkcji JavaScript nie działa

Właśnie czytałem to pytanie i chciałem spróbować metody alias zamiast metody wrapper funkcji, ale nie mogłem go uruchomić ani w Firefoksie 3, ani 3. 5beta4, ani w Google Chrome, zarówno w ich oknach debugowania, jak i na testowej stronie internetowej.

Firebug:

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

Jeśli umieszczę go na stronie internetowej, wywołanie do myAlias daje mi ten błąd:

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 (z > > > ' S wstawione dla jasności):

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

I na stronie testowej dostaję ta sama "nielegalna inwokacja".

Czy robię coś nie tak? Czy ktoś jeszcze może to odtworzyć? Poza tym, co dziwne, po prostu próbowałem i działa w IE8.
Author: Community, 2009-06-17

6 answers

Musisz powiązać tę metodę z obiektem document. Wygląd:

>>> $ = 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();">

Gdy wykonujesz prosty alias, funkcja jest wywoływana na obiekcie globalnym, a nie na obiekcie dokumentu. Aby to naprawić, użyj techniki zwanej zamknięciami:

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();">

W ten sposób nie stracisz odniesienia do oryginalnego obiektu.

W 2012 roku pojawiła się nowa metoda bind od ES5, która pozwala nam to zrobić w bardziej fantazyjny sposób:

>>> $ = document.getElementById.bind(document)
>>> $('bn_home')
<body id="bn_home" onload="init();">
 40
Author: Maciej Łebkowski,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-09-28 09:28:19

Kopałem głęboko, aby zrozumieć to zachowanie i myślę, że znalazłem dobre wyjaśnienie.

Zanim przejdę do tego, dlaczego nie jesteś w stanie alias document.getElementById, postaram się wyjaśnić, jak działają funkcje/obiekty JavaScript.

Za każdym razem, gdy wywołujesz funkcję JavaScript, interpreter JavaScript określa zakres i przekazuje go do funkcji.

Rozważmy następującą funkcję:

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

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

Ta funkcja jest zadeklarowana w obszarze okna i gdy ją wywołujesz wartość this wewnątrz funkcji sum będzie globalnym obiektem Window.

Dla funkcji' sum 'nie ma znaczenia, jaka jest wartość' this', ponieważ jej nie używa.


Rozważmy następującą funkcję:

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.
Kiedy zadzwonisz do dave ' a.funkcja getAge, interpreter JavaScript widzi, że wywołujesz funkcję getAge w obiekcie dave, więc ustawia this na dave i wywołuje funkcję getAge. getAge() poprawnie powróci 100.

Możesz wiedzieć, że w JavaScript możesz określić zakres za pomocą metody apply. Spróbujmy.

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.

W powyższej linii, zamiast pozwolić JavaScript decydować o zakresie, przekazujesz go ręcznie jako obiekt bob. getAge powróci teraz 200, nawet jeśli "myślałeś", że zadzwoniłeś getAge na dave obiekt.


Jaki jest sens tego wszystkiego? Funkcje są "luźno" dołączone do obiektów JavaScript. Np. można do

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

Zróbmy kolejny krok.
var dave = new Person(new Date(1909, 1, 1));
var ageMethod = dave.getAge;

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

ageMethod egzekucja wyrzuca błąd! Co się stało?

Jeśli uważnie przeczytasz moje powyższe punkty, zauważysz, że metoda dave.getAge została wywołana z dave jako obiekt this, podczas gdy JavaScript nie mógł określić "zakresu" dla ageMethod wykonania. Więc przeszło globalne "okno" jako "to". Teraz, gdy window nie ma właściwości birthDate, ageMethod egzekucja zakończy się niepowodzeniem.

Jak to naprawić? Proste,
ageMethod.apply(dave); //returns 100.

Czy to wszystko miało sens? Jeśli tak, to będziesz w stanie wyjaśnić, dlaczego nie jesteś w stanie alias document.getElementById:
var $ = document.getElementById;

$('someElement'); 

$ jest wywoływana z window jako this i jeśli getElementById implementacja oczekuje {[19] } na document, to się nie powiedzie.

Ponownie, aby to naprawić, możesz zrobić

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

Więc dlaczego to działa w Internet Explorer?

Nie znam wewnętrznej implementacji getElementById W IE, ale komentarz w jQuery source (inArray implementacja metody) mówi, że w IE, window == document. W takim przypadku aliasing document.getElementById powinien działać w IE.

Aby zilustrować to dalej, stworzyłem rozbudowany przykład. Spójrz na funkcję Person poniżej.

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();
    };

}

Dla powyższej funkcji Person, oto jak będą się zachowywać różne metody getAge.

Stwórzmy dwa obiekty za pomocą funkcji Person.

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.

Prosto do przodu, metoda getAge otrzymuje yogi obiekt jako this i wyjścia 100.


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

JavaScript interepreter ustawia obiekt window jako this, a nasza metoda getAge zwróci -1.


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

Jeśli ustawimy poprawny zakres, możesz użyć metody ageAlias.


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

Jeśli przejdziemy do jakiegoś obiektu innej osoby, to nadal poprawnie obliczy wiek.

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

Funkcja ageSmarter przechwyciła oryginalny obiekt this, więc teraz nie musisz się martwić o podanie poprawnego zakres.


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

Problem z ageSmarter polega na tym, że nigdy nie można ustawić zakresu na inny obiekt.


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

Funkcja ageSmartest użyje oryginalnego zakresu, jeśli zostanie podany nieprawidłowy zakres.


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

Nadal będziesz mógł przekazać inny Person obiekt do getAgeSmartest. :)

 188
Author: SolutionYogi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-04-30 19:45:18

To krótka odpowiedź.

Poniżej znajduje się Kopia (odniesienie do) funkcji. Problem polega na tym, że teraz funkcja znajduje się na obiekcie window, gdy została zaprojektowana do życia na obiekcie document.

window.myAlias = document.getElementById

Alternatywami są

    W tym celu należy użyć wrappera (już wspomnianego przez Fabiena Ménagera)
  • Lub możesz użyć dwóch aliasów.

    window.d = document // A renamed reference to the object
    window.d.myAlias = window.d.getElementById
    
 3
Author: 700 Software,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-02-03 17:55:28

Kolejna krótka odpowiedź, tylko dla owijania / aliasingu console.log i podobnych metod logowania. Wszyscy oczekują, że będą w kontekście console.

Jest to przydatne podczas owijania console.log z pewnymi zapasami, na wypadek, gdybyś ty lub Twoi użytkownicy wpadli w kłopoty, gdy używasz przeglądarki, która nie (zawsze) ją obsługuje. Nie jest to jednak pełne rozwiązanie tego problemu, ponieważ należy go rozszerzyć i cofnąć-przebieg może się różnić.

Przykład użycia warnings

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

Następnie użyj go jak zwykle

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

Jeśli wolisz, aby twoje argumenty logowania były zawinięte w tablicę (robię to najczęściej), zastąp .apply(...) z .call(...).

Powinien pracować z console.log(), console.debug(), console.info(), console.warn(), console.error(). console na MDN .

Konsola firebug

 2
Author: Joel Purra,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 10:31:09

Oprócz innych świetnych odpowiedzi, istnieje prosta metoda jQuery $.proxy .

Możesz nazwać tak:

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

Lub

myAlias = $.proxy(document.getElementById, document);
 1
Author: Sanghyun Lee,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-08-17 04:23:40

W rzeczywistości nie można "czystego aliasu" funkcji na predefiniowanym obiekcie. Dlatego najbliżej aliasingu można uzyskać bez zawijania, pozostając w tym samym obiekcie:

>>> document.s = document.getElementById;
>>> document.s('myid');
<div id="myid">
 -6
Author: Kev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-08-04 17:52:24