Co jest nie tak z rozszerzeniem DOM


Original: http://perfectionkills.com/category/annoyances/

05 kwietnia 2010 przez kangax

Byłem niedawno zaskoczony, aby dowiedzieć się, jak mało tematem DOM rozszerzeń są uwzględnione w internecie. Co niepokojące jest to, że downsides tej praktyki pozornie przydatnych nie wydają się być dobrze znane, z wyjątkiem niektórych zaciszne kręgach. Brak informacji może również wyjaśnić, dlaczego nie są to skrypty i biblioteki zbudowane dzisiaj, że nadal należą do tej pułapki. Chciałbym wyjaśnić, dlaczego rozszerzenie DOM jest ogólnie zły pomysł, pokazując niektóre z problemów z nim związanych. Będziemy też patrzeć na możliwe alternatywy dla tego szkodliwego ćwiczenia.

Ale przede wszystkim, co to dokładnie jest rozszerzenie DOM? I jak to wszystko działa?
Jak działa rozszerzenie DOM

Rozszerzenie DOM jest po prostu proces dodawania niestandardowych metod / właściwości do obiektów DOM. Właściwości niestandardowe są te, które nie występują w konkretnej realizacji. A jakie są obiekty DOM? Są to obiekty hostów wykonawcze Element, zdarzenie, dokument, ani żadnego z dziesiątkami innych interfejsów DOM. Podczas wydłużania, metody / właściwości mogą być dodawane bezpośrednio do obiektów, lub ich prototypów (tylko w środowiskach, w których odpowiednie do jego obsługi).

Najczęściej rozszerzone przedmioty są prawdopodobnie DOM elementy (te, które wdrożyć interfejs Element), spopularyzowana przez bibliotek JavaScript takich jak Prototype i Mootools. Obiekty zdarzeń (tych, które wykonują interfejs zdarzeń) i dokumentów (Document Interface) są często rozszerzane również.

W środowisku, które naraża prototyp obiektów elementu, przykładem rozszerzenia DOM będzie wyglądać mniej więcej tak:

Element.prototype.hide = function () {
this.style.display = ‘none';
};

var element = document.createElement (‘p’);

element.style.display / /”
element.hide ();
element.style.display / / ‘none’

Jak widać, “hide” funkcja jest najpierw przypisany do właściwości skórą Element.prototype. Następnie jest wywoływana bezpośrednio na element i elementu “display” styl jest ustawiony na “none”.

Powodem tego “dzieła”, ponieważ obiekt określone przez Element.prototype jest rzeczywiście jednym z obiektów w łańcuchu prototypu elementu P. Kiedy właściwość hide został rozwiązany na nim, to przeszukiwane całym łańcuchu prototypu aż znalazłem na tym obiekcie Element.prototype.

W rzeczywistości, jeśli mielibyśmy badać łańcuch prototypów elementu P w niektórych z nowoczesnych przeglądarek, to zwykle wygląda tak:

/ / “^” Oznacza połączenie między obiektami w łańcuchu prototypów

document.createElement (‘p’);
^
HTMLParagraphElement.prototype
^
HTMLElement.prototype
^
Element.prototype
^
Node.prototype
^
Object.prototype
^
wartość null

Zauważ, jak najbliższy przodek w łańcuchu prototypu elementu P jest obiektem określone przez HTMLParagraphElement.prototype. To jest celem dla danego typu elementu. Dla elementu P, to HTMLParagraphElement.prototype, dla elementu div HTMLDivElement.prototype to, dla elementu, to HTMLAnchorElement.prototype, i tak dalej.

Ale dlaczego takie dziwne nazwy, można zapytać?

Nazwy te rzeczywiście odpowiadają interfejsów zdefiniowanych w Level 2 DOM HTML Specyfikacji. Ta sama specyfikacja definiuje także dziedziczenie między tymi interfejsami. Mówi, na przykład, że: “… interfejs HTMLParagraphElement wszystkie mają właściwości i funkcje interfejsu HTMLElement …” (źródło), i że “… interfejsu HTMLElement wszystkie mają właściwości i funkcje interfejsu element …” (źródło), i tak dalej.

Dość oczywiste, gdybyśmy mieli stworzyć obiekt na “obiekt” prototypu elementu akapitu, że obiekt nie będzie dostępny na, powiedzmy, elementu kotwicy:

HTMLParagraphElement.prototype.hide = function () {
this.style.display = ‘none';
};

typeof document.createElement (‘a’) hide;. / / “undefined”
typeof document.createElement (‘p’) hide;. / / “funkcja”

To dlatego, że nigdy nie elementu anchor chain prototyp zawiera obiekt skieruję do przez HTMLParagraphElement.prototype, lecz obejmuje określona przez HTMLAnchorElement.prototype. Aby “naprawić” to, możemy przypisać do własności obiektu umieszczonego w dalszej części łańcucha prototypów, takich jak ten, określony przez HTMLElement.prototype, Element.prototype lub Node.prototype.

Podobnie, tworząc obiekt na Element.prototype nie udostępnia go na wszystkich węzłach, ale tylko na węzłach typu elementu. Jeśli chcemy mieć obiekt na wszystkich węzłach (np. węzły tekstowe, węzły komentarz, itp.), musielibyśmy przypisać do majątku Node.prototype zamiast. A mówiąc o tekst i komentarz węzłów, to jest jak dziedziczenie interfejsu zazwyczaj wygląda dla nich:

document.createTextNode (‘foo’) / /document.createComment (‘bar’) / /

Teraz ważne jest, aby zrozumieć, że narażenie tych prototypów DOM obiekt nie jest gwarantowane. DOM Level 2 specyfikacja jedynie definiuje interfejsy i dziedziczenie między tymi interfejsami. Nie stwierdzić, że powinny istnieć globalne własności elementu, odwołując obiekt to prototyp wszystkich obiektów wykonawczych interfejs Element. Ani też nie stwierdzono, że powinny istnieć globalny obiekt Node, odwołującego się obiektu, który jest prototypem wszystkich obiektów wykonawczych Node interfejs.

Internet Explorer 7 (i poniżej) jest przykładem takiego środowiska, nie wystawiać globalnych węzeł, Element, HTMLElement, HTMLParagraphElement lub inne właściwości. Innym takim przeglądarki Safari 2.x (i najprawdopodobniej 1.x Safari).

Więc co możemy zrobić w środowisku, które nie narażają te globalne “prototyp” obiekty? Rozwiązaniem jest rozszerzenie obiektów DOM bezpośrednio:

var element = document.createElement (‘p’);

element.hide = function () {
this.style.display = ‘none';
};

element.style.display / /”
element.hide ();
element.style.display / / ‘none’

Co poszło źle?

Będąc w stanie rozszerzyć przez obiekty DOM elementów prototypowych brzmi niesamowicie. Korzystamy z Javascript prototypal natury i skryptów DOM staje się bardzo zorientowany obiektowo. W rzeczywistości rozszerzenie DOM wydawało się tak kusząco użyteczne, że kilka lat temu, Prototype JavaScript library sprawiło, że istotną częścią jego architektury. Ale to, co kryje się za pozornie niegroźne praktyka jest ogromny ładunek kłopoty. Jak zobaczymy za chwilę, jeśli chodzi o cross-browser skryptów Wady tego rozwiązania znacznie przewyższają jakiekolwiek korzyści. DOM rozszerzenie jest jednym z największych błędów Prototype.js kiedykolwiek zrobiłem.

Więc jakie są te problemy?
Brak specyfikacji

Jak już wspomniano, ekspozycja “obiektów prototypowych” nie jest częścią żadnej specyfikacji. DOM Level 2 jedynie definiuje interfejsy i ich stosunki spadkowe. W celu wykonania spełniania DOM Level 2 w pełni, nie ma potrzeby, aby odsłonić te globalnego węzła, element, HTMLElement itp. obiektów. I nie ma wymogu, aby odsłonić ich w jakikolwiek inny sposób. Biorąc pod uwagę, że nie zawsze jest możliwość rozszerzenia obiektów DOM ręcznie, to nie wydaje się jak duży problem. Ale prawda jest taka, że ​​firmy rozszerzenie jest raczej powolny i niewygodny proces (jak zobaczymy wkrótce). A fakt, że szybka, “obiekt prototyp” oparta jest jedynie rozszerzenie nieco z de-facto standardem wśród kilku przeglądarkach, sprawia, że ​​ta praktyka zawodna, jeśli chodzi o przyszłe przyjęcie lub przenieść na nie convential platformy (np. telefony komórkowe).

Obiekty hostów nie ma zasad

Kolejny problem z rozszerzeniem DOM jest to, że obiekty są obiektami DOM goszczące i obiekty gospodarza są najgorsze grono. Wg specyfikacji (ECMA-262 3rd. ed), obiekty hostów mogą robić rzeczy, żadne inne obiekty mogą nawet marzyć. Zacytować odpowiedni rozdział [8.6.2]:

Obiekty gospodarza mogą wdrożyć te wewnętrzne metody z jakiegokolwiek zachowania zależne od implementacji, lub może być, że gospodarz obiektu realizuje tylko niektóre wewnętrzne metody a nie innych.

Rozmowy wewnętrzne metody specyfikacji o to [[Pobierz]], [[Umieść]], [[Delete]], itp. Uwaga, jak to mówi, że wewnętrzne zachowanie metody jest zależne od implementacji. Oznacza to, że to jest całkowicie normalne dla obiektu przyjmującego rzucać błąd na pw, powiedzmy, [[Pobierz]] metoda. I unfortunatey, to nie tylko teoria. W programie Internet Explorer, możemy łatwo obserwować dokładnie ten-przykład obiektu przyjmującego [[GET]] Błąd rzucania:

. document.createElement (‘p’) offsetParent / / “Nieokreślony błąd”.
. new ActiveXObject (“Msxml2.XMLHTTP”) wyślij / / “Obiekt nie obsługuje tej właściwości lub metody”

Rozszerzanie obiektów DOM jest trochę jak chodzenie w pole minowe. Z definicji pracy z czegoś, co jest dozwolone, aby zachowywać się w sposób nieprzewidywalny i całkowicie błędne. I to nie tylko rzeczy, można wysadzić, istnieje również możliwość milczących porażek, co jest jeszcze gorsze scenariusze. Przykład nieprawidłowego zachowania jest applet, object i embed elementy, które w niektórych przypadkach wyrzucają błędy na przypisanie właściwości. Podobne katastrofy dzieje z węzłów XML:

var xmldoc = new ActiveXObject (“Microsoft.XMLDOM”);
xmlDoc.loadXML (‘bar’);
xmlDoc.firstChild.foo = ‘bar’, / / ​​”Obiekt nie obsługuje tej właściwości lub metody”

Istnieją inne przypadki awarii w IE, jak document.styleSheets [99999] rzucanie “Nieprawidłowe wywołanie procedury lub argumentu” lub document.createElement (“p”). Filtry rzucając “nie państwa znaleziono.” wyjątki. Ale nie tylko MSHTML DOM jest problem. Próba nadpisania “docelową” właściwości obiektu zdarzenia w Mozilli Zgłasza TypeError, twierdząc, że nieruchomość ma tylko getter (co oznacza, że ​​to jest tylko do odczytu i nie można ustawić). Robi to samo w WebKit, powoduje cichą awarii, gdzie “target” w dalszym ciągu odnoszą się do oryginalnego obiektu po przydziale.

Podczas tworzenia API do pracy z obiektami zdarzeń, istnieje obecnie potrzeba rozważenia wszystkich tych ReadOnly właściwości, zamiast skupiać się na nazwach zwięzłych i opisowych.

Dobrą zasadą jest unikanie dotykania przedmiotów hostów jak najwięcej. Próbując architektury bazowej na coś, co-z definicji-może zachowywać się tak sporadycznie nie jest to dobra idea.
Prawdopodobieństwo kolizji

API na podstawie rozszerzeń DOM elementu trudno skalę. Trudno do skalowania dla deweloperów biblioteka-podczas dodawania nowych lub zmiana metod API rdzenia, a library users-podczas dodawania domen specyficzne rozszerzenia. Korzeń problemu jest prawdopodobnie możliwe kolizje. Implementacje DOM w popularnych przeglądarkach zazwyczaj mają properietary API. Co gorsze jest to, że te API nie są statyczne, lecz ciągle się zmieniać w miarę jak nowe wersje przeglądarek wyjdzie. Niektóre części się przestarzałe, a inne są dodawane lub modyfikowane. W rezultacie, zestaw właściwości oraz metody na obiektach DOM jest trochę ruchomy cel.

Biorąc pod uwagę ogromną ilość środowisk w użyciu, staje się niemożliwe, aby powiedzieć, jeśli pewien obiekt nie jest już częścią jakiegoś DOM. A jeśli jest, to może być zastąpiony? Lub będzie rzucać błąd podczas próby zrobić? Pamiętaj, że to obiekt gospodarzem! A jeśli możemy spokojnie go zastąpić, jak to będzie wpływać na inne części DOM? Czy nadal wszystko działa jak powinno? Jeśli wszystko jest w porządku w jednej z wersji takiej przeglądarki, ma gwarancję, że następna wersja nie wprowadza tej samej nazwie obiektu Lista pytań jest długa.

Niektóre przykłady własnych rozszerzeń, które wybuchły Prototype są własnością okład na pola tekstowe w IE (kolizji z elementem # metodę oblewania) i wybierz metodę elementów kontrolnych formularzy w Operze (kolizji z elementem # wybierz metodę). Mimo że obu tych przypadkach są udokumentowane, trzeba pamiętać te małe wyjątki jest denerwujące.

Własnościowe rozszerzenia nie są jedynym problemem. HTML5 wprowadza nowe metody i właściwości w tabeli. I większość popularnych przeglądarek rozpoczęli już ich realizacji. W pewnym momencie, WebForms zdefiniowane zastąpić właściwość na elementów wejściowych, który Opera postanowił dodać do swojej przeglądarki. I po raz kolejny złamał Prototype, ze względu na konflikt z elementem # zastąpić metodę.

Ale poczekaj, to nie wszystko!

Ze względu na wieloletnią tradycję DOM Level 0, jest to “wygodny” sposób dostępu formanty formularza off elementów formularza, po prostu przez ich nazwy. Oznacza to, że zamiast standardowego kolekcję elementów, można uzyskać dostęp do formantu formularza takie jak to:


// <![CDATA[

. document.forms [0] foo / / nie-standardowy dostęp
/ / W porównaniu do
. document.forms [0] elements.foo / / standardowy dostęp

Więc mówisz, że rozszerzenie z elementów formularza logowania metoda, która na przykład walidacji czeków i podnosi formularza logowania. Jeśli zdarzy się, że również kontrola formularza z “Login” nazwie (co jest dość prawdopodobne, jeśli chodzi o mnie), co dzieje się dalej nie jest ładna:



HTMLFormElement.prototype.login = function () {
zwrotu “rejestrowanie w ‘;
};

. $ (MyForm) login () / / boom!
/ / $ (MyForm). Logowanie wejście odniesienia element nie, “ metody zalogować

Co nazwane cienie Forma kontroli dziedziczone przez łańcucha prototypów. Prawdopodobieństwo kolizji i błędów na nieoczekiwanych elementów formularza jest jeszcze wyższy.

Sytuacja jest nieco podobna nazwanych elementów formularza, gdzie mogą one być dostępne bezpośrednio z dokumentu, według ich nazw:



document.foo / / [HTMLFormElement obiekt]

Przedłużając obiektów dokumentu, jest tam teraz dodatkowe ryzyko nazw formularzy konflikcie z rozszerzeniami. A co, jeśli skrypt jest uruchomiony w starszych aplikacji z ton zardzewiałej HTML, gdzie zmiany / usunięcia tych nazw nie jest to banalne zadanie?

Zatrudnianie jakąś poprzedzania strategii może złagodzić problem. Ale prawdopodobnie także przynieść dodatkowego hałasu.

Nie modyfikowania obiektów nie należy do Ciebie jest ostateczny przepis na uniknięcie kolizji. Złamanie tej zasady już dostał Prototype w kłopoty, kiedy to zastąpił document.getElementsByClassName z własnym, wdrożenia niestandardowego. Po to również oznacza, grając ładny z innych skryptów, uruchomiony w tym samym środowisku, bez względu na to, że modyfikowanie obiektów DOM, czy nie.
Overhead wydajność

Jak widzieliśmy wcześniej, przeglądarek, które nie obsługują elementu rozszerzenia-jak IE 6, 7, Safari 2.x, itp.-wymagają ręcznego rozszerzenia obiektu. Problemem jest to, że firmy rozszerzenie jest powolne, niewygodne i nie w skali. Jest powolne, ponieważ obiekt musi być przedłużony, co często jest wiele metod / właściwości. I jak na ironię, te przeglądarki są najwolniejsze z nich w okolicy. Jest to niewygodne, ponieważ obiekt powinien zostać przedłużony w celu być operowany. Więc zamiast document.createElement (‘p’). Hide (), musisz zrobić coś jak $ (document.createElement (‘p’)). Hide (). To, nawiasem mówiąc, jest jednym z najbardziej popularnych klocków stumbing dla początkujących Prototype. Wreszcie, ręczny rozszerzenie nie skalują się dobrze, ponieważ dodawanie metod API wpływa na wydajność prawie liniowo. Jeśli jest 100 na Element.prototype metody, nie musi być wykonane w 100 zadania danego elementu, jeśli nie jest 200 metody, musi być wykonane w 200 zadania elementu, i tak dalej.

Kolejny hit wydajność jest z obiektami zdarzeń. Prototype następujące podobnego podejścia ze zdarzeniami i rozszerza ich z pewnym zestawem metod. Niestety, niektóre wydarzenia w przeglądarkach-mouseMove, mouseOver, mouseOut, zmiana rozmiaru, aby wymienić kilka-może strzelać dosłownie dziesiątki razy na sekundę. Rozszerzanie każdy z nich jest niezwykle kosztowne. I po co? Wystarczy powołać się, co może być jedna z metod na obejct zdarzeń?

Wreszcie, po uruchomieniu elementów wystających, biblioteki API najprawdopodobniej trzeba wrócić rozszerzonych elementów wszędzie. W rezultacie, sprawdzania metody jak $ $ może skończyć rozszerzenie każdy element zapytania. Łatwo jest wyobrazić overead wydajności tego procesu, kiedy mówimy o setki lub tysiące elementów.
IE DOM jest bałagan

Jak pokazano w poprzednim rozdziale, ręczny DOM rozszerzenie jest bałagan. Ale ręczny rozszerzenie DOM w IE jest jeszcze gorzej, a oto dlaczego.

Wszyscy wiemy, że w IE, odwołania cykliczne między hostem i obiektów rodzimych przeciekać i najlepiej unikać. Ale dodanie metody do elementów DOM jest pierwszym krokiem w kierunku stworzenia takich odwołań cyklicznych. A ponieważ starsze wersje IE nie wystawiać “prototypy obiektów”, nie ma wiele do zrobienia, ale rozszerzyć elementy bezpośrednio. Odwołania cykliczne i wycieki są prawie nieuniknione. A w rzeczywistości, Prototype cierpiał nimi przez większość swojego życia.

Innym problemem jest sposób IE DOM właściwości mapy i atrybuty do siebie. Fakt, że atrybuty są w tych samych nazw jako właściwości, zwiększa prawdopodobieństwo kolizji i wszelkiego rodzaju nieprzewidziane niespójności. Co się stanie, jeśli element ma niestandardowy atrybut “show”, a następnie rozszerzony o Prototype. Będziesz zaskoczony, ale pokazują, “atrybut” dostanie nadpisane prototypu elementu # pokazuje metodę. extendedElement.getAttribute (‘show’) zwróci odwołanie do funkcji, a nie wartość “show” atrybutu. Podobnie extendedElement.hasAttribute (“hide”) powie “true”, nawet jeśli nigdy nie było custom “hide” atrybut elementu. Należy pamiętać, że IE