2009-06-17 17 views
76

Właśnie czytałem this question i chciałem wypróbować metodę aliasową, a nie metodę z funkcją-wrapper, ale nie mogłem jej uruchomić w Firefoksie 3 ani 3.5beta4 lub Google Chrome, zarówno w ich oknach debugowania, jak i na testowej stronie internetowej.Aliasing funkcji JavaScript wydaje się nie działać

Firebug:

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

Jeśli I umieścić go na stronie internetowej, wezwanie 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 wstawiony 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 otrzymuję to samo "Nielegalne wezwanie".

Czy robię coś nie tak? Czy ktokolwiek może to powtórzyć?

Również, o dziwo, po prostu próbowałem i działa w IE8.

+0

w IE8 - może to być jakiś rodzaj magicznej funkcji lub coś. –

+0

Dodałem komentarze do niepoprawnych odpowiedzi na http://stackoverflow.com/questions/954417 i skierowałem je tutaj. –

+1

Dla przeglądarek obsługujących metodę Function.prototype.bind: window.myAlias ​​= document.getElementById.bind (document); Przeszukaj dokumenty MDN, pojawi się podkładka wiążąca. –

Odpowiedz

33

Musisz powiązać tę metodę z obiektem dokumentu. 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();"> 

Podczas wykonywania prostego aliasu funkcja jest wywoływana w obiekcie globalnym, a nie w 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 tracisz odniesienia do oryginalnego obiektu.

W 2012 roku, nie jest nowy bind metoda z ES5 która pozwala nam robić to w sposób bardziej wyszukane:

>>> $ = document.getElementById.bind(document) 
>>> $('bn_home') 
<body id="bn_home" onload="init();"> 
+3

Co jest naprawdę opakowaniem, ponieważ zwraca nową funkcję. Myślę, że odpowiedź na pytanie Keva jest taka, że ​​nie jest to możliwe z powodów opisanych powyżej. Metoda, którą tu opisujesz, jest prawdopodobnie najlepszą opcją. – jiggy

+0

Dzięki za komentarz, nie wyglądałem wystarczająco dokładnie, aby to zrozumieć. Tak więc składnia w odpowiedziach na drugie pytanie jest całkowicie fałszywa? Interesujące ... – Kev

+0

Właściwie należy to zgłosić jako odpowiedź ... – Kev

-6

rzeczywiście nie może „czysty alias” funkcja na predefiniowanym obiektu . Dlatego najbliżej aliasingu można dostać bez owijania pozostając w obrębie tego samego obiektu:

>>> document.s = document.getElementById; 
>>> document.s('myid'); 
<div id="myid"> 
+5

Jest to nieco niepoprawne. Możesz "czystego aliasu" funkcji pod warunkiem, że funkcja realizacji używa "tego". Więc to zależy. – SolutionYogi

+0

Przepraszamy, miałem na myśli w kontekście getElementById. Masz rację. Zmieniłem nieco odpowiedź, aby to odzwierciedlić ... – Kev

+0

Oh Kev, to naprawdę jest OP lolzzzz –

181

I wykopano głębokie zrozumienie tego konkretnego zachowania i myślę, że znalazłem dobre wyjaśnienie.

Zanim przejdę do tego, dlaczego nie jesteś w stanie podać pseudonimu 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 zasięg 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 zakresie okien i kiedy ją wywołać wartość this wewnątrz funkcji suma będzie globalny Window przedmiot.

Dla funkcji "suma" nie ma znaczenia wartość "to", ponieważ nie jest używana.


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. 

Po wywołaniu dave.getAge funkcji, interpreter JavaScript widzi, że dzwonisz funkcję getAge na obiekcie dave, więc ustawia this do dave i wywołuje Funkcja getAge. getAge() zwróci poprawnie 100.

Być może w języku JavaScript można określić zakres przy użyciu metody apply. Spróbujmy to.

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ć zakres, są przechodzącą zakres ręcznie jako przedmiot bob. getAge zwróci teraz 200, mimo że "pomyślałeś", że zadzwoniłeś pod numer getAge na obiekcie dave.


Jaki jest sens wszystkich powyższych? Funkcje są "luźno" dołączane do twoich obiektów JavaScript. Na przykład. można zrobić

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 

Weźmy następny krok.

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

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

ageMethod wykonanie powoduje błąd! Co się stało?

Jeśli uważnie czytać moje powyższe punkty, należy pamiętać, że dave.getAge metoda została wywołana z dave jako this obiektu natomiast JavaScript nie może określić zakres „” dla ageMethod wykonania. Więc przekazał globalne "Okno" jako "to". Teraz jako window nie ma właściwości birthDate, wykonanie ageMethod zakończy się niepowodzeniem.

Jak to naprawić? Prosty,

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

Czy wszystkie powyższe ma sens? Jeśli tak, to będzie w stanie wyjaśnić, dlaczego nie jesteś w stanie alias document.getElementById:

var $ = document.getElementById; 

$('someElement'); 

$ nazywa się window jako this a jeśli getElementById realizacja spodziewa this być document, zakończy się niepowodzeniem.

Znowu aby rozwiązać ten problem, można to zrobić

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

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

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

Dla zilustrowania tego dalej, stworzyłem złożony przykład. Rzuć okiem na poniższą funkcję Person.

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 funkcji Person powyżej, oto jak różne getAge metody będą zachowywać.

Utwó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. 

prosta metoda getAge dostaje yogi obiekt jako this i wyjść 100.


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

JavaScript interepreter ustawia window przedmiot this a nasz getAge metoda zwróci -1.


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

Gdybyśmy ustawić właściwy zakres, można użyć ageAlias metody.


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

Jeśli mijamy w innej osobie obiektu, będzie on nadal obliczyć wiek prawidłowo.

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

Funkcja ageSmarter schwytany oryginalny this obiekt więc teraz nie trzeba się martwić o dostarczanie prawidłowego zakresu.


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

Problem z ageSmarter jest to, że nigdy nie można ustawić zakres do innego obiektu.


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

Funkcja ageSmartest użyje pierwotnego zakresu Jeżeli nieważny zakres jest dostarczany.


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

Będziesz nadal będą mogli przechodzić kolejną Person obiekt do getAgeSmartest. :)

+7

+1, dlaczego IE działa. Reszta jest nieco nie na temat, ale nadal pomocna w ogóle.:) – Kev

+42

Doskonała odpowiedź. Nie rozumiem, jak to wszystko jest poza tematem. –

+0

Bardzo ładna odpowiedź. Nieco krótsza wersja jest napisana [tutaj] (http://stackoverflow.com/questions/585840/implement-map-in-javascript-that-supports-object-methods-as-mapped-functions/585918#585918). –

3

To jest krótka odpowiedź.

Poniżej przedstawiono kopię (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 

Alternatywy są

  • użyć osłony (wspomniany już przez Fabien Menager)
  • lub można użyć dwóch aliasów.

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

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

Jest to przydatne do owijania console.log z niektórymi awariami, na wypadek gdyby Ty lub Twoi użytkownicy wpadli w kłopoty, gdy using a browser that doesn't (always) support it. Nie jest to jednak pełne rozwiązanie tego problemu, ponieważ musi zostać rozszerzone kontrole i awarie - twój przebieg może się różnić.

przykład stosując ostrzeżenia

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

następnie użyć go jako zwykłego

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

Jeśli wolisz, aby zobaczyć swoje argumenty rejestrowania owinięty w tablicy (ja, przez większość czasu), zastąpił .apply(...) z .call(...).

Powinna działać z console.log(), console.debug(), console.info(), console.warn(), console.error(). Zobacz także console on MDN.

1

Oprócz innych interesujących odpowiedzi, istnieje prosta metoda jQuery $.proxy.

Można alias takie jak ten:

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

Albo

myAlias = $.proxy(document.getElementById, document); 
+0

+1, ponieważ jest to wykonalne rozwiązanie. Ale, ściśle rzecz ujmując, za kulisami '$ .proxy' jest również naprawdę opakowaniem. – pimvdb

Powiązane problemy