29.09.2011

Какво не е наред с разширяване на DOM

Original on http://perfectionkills.com/whats-wrong-with-extending-the-dom/

Наскоро бях изненадан, за да разберете колко малко темата на разширения DOM е покрита в интернет. Какво е смущаващ е, че недостатъците на тази привидно полезна практика не изглежда да бъдат добре известни, с изключение в някои усамотен кръгове. Липсата на информация може да обясни защо има скриптове и библиотеките, създаден днес, които все още попадат в този капан. Бих искал да обясня защо разширяване DOM като цяло е лоша идея, като показва някои от проблемите, свързани с него. Ние също така ще се търсят възможни алтернативи на това вредно упражнение.

Но на първо място, какво точно е DOM разширение? И как го прави цялата работа?

Как работи DOM разширение §

DOM разширението е просто процес на добавяне на потребителски методи / свойства на обекти DOM. Потребителски свойства са тези, които не съществуват в специално изпълнение. А какви са DOM обекти? Това са домакин обекти, Element Event елементите Document Събитие, Документ, или някой от десетки други интерфейси DOM. По време на разширение, методи / свойства могат да се добавят директно към обекти, или на техните прототипи (но само в среди, които имат подходяща подкрепа за него).

Най-често удължен обекти вероятно са DOM елементи (тези, които Element Element интерфейс), популяризирана от Javascript библиотеки като Prototype и MooTools. Събитие обекти (тези, които Event събитие интерфейс) и Document ( Документ интерфейс ) често се разширяват, както добре.


  Element.prototype.hide = function() {
    this.style.display = 'none';
  };
  ...
  var element = document.createElement('p');

  element.style.display; // ''
  element.hide();
  element.style.display; // 'none'

Причината това да “работи” е така, защото обект, посочен Element.prototype Element.prototype всъщност е един от обектите в прототип на верига от P елемент. Когато е решен hide собственост върху нея, тя е търсене по цялата верига на прототипа, докато не бъдат намерени Element.prototype този Element.prototype обект.

В действителност, ако бяхме да разгледа прототип на верига от P елемент в някои от съвременни браузъри, тя обикновено изглежда така:

 // "^" denotes connection between objects in prototype chain document.createElement('p'); ^ HTMLParagraphElement.prototype ^ HTMLElement.prototype ^ Element.prototype ^ Node.prototype ^ Object.prototype ^ null 

Забележете как най-близкия предшественик в прототип на верига P P елемент е обект, HTMLParagraphElement.prototype от HTMLParagraphElement.prototype. Това е обект, специфични за вида на елемент. За P елемент HTMLParagraphElement.prototype ; за DIV елемент, това е HTMLDivElement.prototype за елемент, A HTMLAnchorElement.prototype, и така нататък.

Но защо такива странни имена, можете да попитате?

Тези имена действително съответстват на интерфейсите, определени в DOM Level 2 Спецификация HTML. Същата спецификация определя наследство между тези интерфейси. Той казва, например, че “… HTMLParagraphElement интерфейс имат всички свойства и функции на HTMLElement интерфейс…” ( източник ) и че “… HTMLElement интерфейс, имат всички свойства и функции на Element интерфейс…” ( източник ), и така нататък.

Съвсем очевидно, ако бяхме да се създаде един имот на “прототип обект” на параграф елемент, че имотът няма да бъде на разположение, да речем, елемент на котва:


  Element.prototype.hide = function() {
    this.style.display = 'none';
  };
  ...
  var element = document.createElement('p');

  element.style.display; // ''
  element.hide();
  element.style.display; // 'none'

Това е така, защото никога прототип верига на котва елемент включва HTMLParagraphElement.prototype, посочен от HTMLParagraphElement.prototype, но HTMLAnchorElement.prototype включва посочени от HTMLAnchorElement.prototype. За да “фиксира” това, можем да присвоите собственост на обекта, разположени допълнително в прототип верига, като това, посочено HTMLElement.prototype HTMLElement.prototype Element.prototype Element.prototype Node.prototype Node.prototype.

По същия начин, създавайки един Element.prototype на Element.prototype, не би го прави достъпен на всички възли, но само на възли на тип елемент. Ако искаме да има собственост върху всички възли (напр. текст възли, коментар възли и др.), Ние ще трябва, вместо да присвоите собственост Node.prototype Node.prototype. И говорейки на текст и възли коментар, това е, как интерфейс наследство обикновено изглежда за тях:

 document.createTextNode('foo'); // < Text.prototype < CharacterData.prototype < Node.prototype document.createComment('bar'); // < Comment.prototype < CharacterData.prototype < Node.prototype 

Сега е важно да се разбере, че излагането на тези прототипи обект DOM не е гарантирано. DOM Level 2 спецификация само определя интерфейси, и наследство между тези интерфейси. Той не се посочва, че трябва да има съществува Element елемент на имущество, съотнасяне на обект, който е прототип на Element обекти за прилагане на Element интерфейс. Нито го прави държавата, че трябва да Node глобална Node собственост, съотнасяне на обект, който е прототип на Node обекти за прилагане на Node интерфейс.

Internet Explorer 7 (и по-долу) е пример на такава среда, не излагайте глобален Node, Element, HTMLElement, HTMLParagraphElement, или други свойства. Друг такъв браузър е Safari 2.x (и най-вероятно Safari 1.x).

И така, какво можем да направим в среди, които не излагат тези глобални “прототип” обекти? Решението на проблема е да се разшири DOM обекти:

 var element = document.createElement('p');... element.hide = function() { this.style.display = 'none'; };... element.style.display; // '' element.hide(); element.style.display; // 'none' 

Какво се обърка? §

Да бъдеш в състояние да разшири DOM елементи чрез прототип обекти звучи невероятно. Ние сме като се възползва от Javascript prototypal природа и скриптове DOM става много обектно-ориентиран. В действителност, разширение на DOM изглежда толкова примамливо, че преди няколко години, Prototype Javascript библиотека направи съществена част от своята архитектура. Но какво се крие зад привидно безвредни практика е огромно натоварване от неприятности. Както ще видите в момент, когато става въпрос за различни браузъри скриптове, недостатъците на този подход далеч надвишават евентуалните ползи. DOM разширението е един от най-големите грешки, Prototype.js е правил.

Така че какви са тези проблеми?

Липсата на спецификация §

Както вече споменах, експозицията на “прототип обекти” не е част от всяка спецификация. DOM Level 2 само определя интерфейси и наследството си отношения. В заповед за изпълнение в съответствие с DOM Level 2 напълно, няма нужда да се излагат тези Node Element елемент HTMLElement HTMLElement и др. обекти. Нито има изискване да ги изложи по някакъв друг начин. Като се има предвид, че винаги има възможност да разшири DOM обекти ръчно, това не изглежда като голям проблем. Но истината е, че ръководство разширение е доста бавен и неудобен процес (както ще видим скоро). А фактът, че бързо “, прототип на обект”-базирани разширение е просто известна степен на де-факто стандарт сред малкото браузъри, прави ненадеждни тази практика, когато става въпрос за бъдещото приемане или преносимост през не-convential платформи (например мобилни устройства).

Водещ обекти са без правила §

Следващият проблем с DOM разширението е, че DOM обекти са домакин обекти, и домакин обекти са най-лошото куп. По спецификация (ECMA-262 3rd. ЕД), домакин обекти е позволено да правите неща, няма други предмети могат дори да мечтаят. За да цитирам съответния раздел [ 8.6.2] :

Приемащите предмети могат да прилагат тези вътрешни методи с всяко зависимо от конкретната реализация поведение, или тя може да бъде, че множество обект изпълнява само някои вътрешни методи, а други не.

Вътрешният спецификация методи говори за [[]], [[Сложете]], [[Delete]], и т.н. Забележете как се казва, че вътрешните методи на поведение е зависимо от конкретната реализация. Какво означава това е, че това е абсолютно нормално за домакин на обект, за да хвърлят грешка на позоваването на, да речем, [[]] метод. И unfortunatey, това не е само теория. В Internet Explorer, лесно можем да наблюдаваме точно този пример за домакин обект [[Get]] хвърляне грешка:

 document.createElement('p').offsetParent; // "Unspecified error." new ActiveXObject("MSXML2.XMLHTTP").send; // "Object doesn't support this property or method" 

Разширяване на DOM обекти е нещо като разходка в минно поле. По дефиниция, работа с нещо, което е позволено да се държат в непредсказуемо и напълно хаотично начин. И не само неща може да се взривят, има също така възможността за мълчалив провали, което е дори по-лошо сценарий. Един пример за хаотично поведение е applet, object и embed на елементи, които в някои случаи хвърлят грешки за възлагане на имоти. Подобни бедствие се случва с XML възли:

 var xmlDoc = new ActiveXObject("Microsoft.XMLDOM"); xmlDoc.loadXML(' bar '); xmlDoc.firstChild.foo = 'bar'; // "Object doesn't support this property or method"  var xmlDoc = new ActiveXObject("Microsoft.XMLDOM"); xmlDoc.loadXML(' bar '); xmlDoc.firstChild.foo = 'bar'; // "Object doesn't support this property or method"  var xmlDoc = new ActiveXObject("Microsoft.XMLDOM"); xmlDoc.loadXML(' bar '); xmlDoc.firstChild.foo = 'bar'; // "Object doesn't support this property or method" 

Има други случаи на повреди в IE, като document.styleSheets[99999] хвърляне “Невалидна повикване процедура или аргумент” или document.createElement('p').filters хвърляне “държава не е намерен.” изключения. Но не само MSHTML DOM е проблем. Опитвайки се да презапише “мишена”, собственост event събитието обект в Mozilla хвърля Тип грешка, оплаквайки се, че имота има само едно кариерист (което означава, че това е само за четене и не могат да бъдат настроени ). Правейки едно и също нещо в WebKit, резултатите в тих недостатъчност, когато “мишена” продължава да се позове на оригиналния обект след възлагане.

При създаването на API за работа със събитие обекти, сега има необходимост да се обмисли всички на тези само за четене имоти, вместо да се фокусира върху кратки и описателни имена.

Добро правило на палеца е да се избегне докосване домакин обекти, колкото е възможно повече. Опитвайки се да архитектура на база на нещо, че по дефиниция могат да се държат така спорадично е едва ли е добра идея.

Вероятност от сблъсък §

Въз основа на елемент разширения DOM API е трудно да се скала. Трудно е да се скала за разработчиците на библиотеката при добавяне на нови или промяна на методите на API на ядро, и за библиотека потребителите при добавяне на домейн разширения. Коренът на проблема е вероятно шанс на сблъсъци. DOM реализации в популярните браузъри обикновено всички properietary API. По-лошото е, че тези API не са статични, но постоянно се променят, като новите версии на браузъра излизат. Някои части получават непрепоръчителен, други са добавени или променени. В резултат на това, набор от свойства и методи на обекти DOM, е до известна степен от движеща се мишена.

Като се има предвид огромното количество на околни среди, които се използват днес, става невъзможно да се каже, ако даден имот не е вече част от някои DOM. И ако е, може ли да бъде презаписан? Или ще го хвърли грешка, когато се опитвате да го направят? Не забравяйте, че това е домакин обект! И, ако можем да тихо го презапишете, как ще го засегне и други части на DOM? Дали всичко все още работят, както се очаква? Ако всичко е наред в една версия на такъв браузър, има ли гаранция, че следващата версия не се въведе един и същи име имот? Списъкът от въпроси.

Някои примери на патентовани разширения, които избухнаха Prototype обвивам собственост на textareas в IE (сблъсък с Element # обвивам метод ), и select метод на елементи на форма на контрол в Opera ( сблъсък с Element # изберете метод ). Въпреки че и двете от тези случаи са документирани, се налага да помните тези малки изключения, е досадно.

Патентованите разширения не са единственият проблем. HTML5 носи нови методи и свойства на таблицата. И повечето от популярните браузъри вече са започнали изпълнението им. В един момент, WebForms определени replace замени на собственост за въвеждане на елементи, които Opera реши да добави към своя браузър. И отново, счупи Prototype, поради конфликт с Element # замени метод.

Но чакайте, има още!

Благодарение на дългогодишната традиция на DOM Level 0, този “удобен” начин за достъп до форма контрол на формата елементи, просто чрез тяхното име. Какво означава това е, че вместо да се използва elements на стандартни елементи, можете да получите достъп до форма на контрол като това:


  <form action="">
    <input name="login">
    ...
  </form>
  ...
  <script type="text/javascript">
    HTMLFormElement.prototype.login = function(){
      return 'logging in';
    };
    ...
    $(myForm).login(); // boom!
    // $(myForm).login references input element, not `login` method
  </script>

Така че, да речем разшири форма елементи login вход метод, който например контролни проверки и внася логин формата. Ако се случи да има контрол на форма с име “вход” (което е доста вероятно, ако питате мен), какво се случва по-нататък не доста:

 <form action=""> <input name="login">... </form>... <script type="text/javascript"> HTMLFormElement.prototype.login = function(){ return 'logging in'; };... $(myForm).login(); // boom! // $(myForm).login references input element, not `login` method </script> 

Всеки име форма на контрол сенки свойства, наследени чрез прототип верига. Шансът на сблъсъци и неочаквани грешки по форма елементи, е дори по-висока.

Ситуацията е по-сходен с име form елементи, където те могат да бъдат достъпни директно document от имената им:


  <form name="foo">
    ...
  </form>
  ...
  <script type="text/javascript">
    document.foo; // [object HTMLFormElement]
  </script>

При удължаване на документ предмети, сега има допълнителен риск от имената на формите, в противоречие с разширения. А какво ще стане, ако скриптът работи в наследство приложения с тона на ръждясал HTML, където променят / за премахването на тези имена не е тривиална задача?

Използвайки някаква представка стратегия може да облекчи проблема. Но вероятно ще донесе допълнителен шум.

Не модифициране на обекти, които не притежават, е рецепта за избягване на сблъсъци. Актуални това правило вече има прототип в беда, когато презаписано document.getElementsByClassName със собствени, потребителски изпълнение. След също така означава да се играе добре с други скриптове, работи в същата среда, без значение, ако те променят DOM обекти или не.

Ефективност режийни §

Както сме виждали и преди, браузъри, които не поддържат елемент разширения подобно на IE 6, 7, Safari 2.x и т.н. изискват ръчна разширение обект. Проблемът е, че разширението на ръководство е бавно, неудобни и не мащаб. Това е бавен, защото обект трябва да бъде разширен с това, което е често голям брой методи / свойства. И по ирония на съдбата, тези браузъри са най-бавните. Това е неудобно, защото трябва първо да бъде разширен, за да бъдат експлоатирани на обект. Така че, вместо на document.createElement('p').hide(), вие ще трябва да се направи нещо като $(document.createElement('p')).hide(). Това, между другото, е един от най-честите блокове stumbing за начинаещи на Prototype. И накрая, ръководство на разширение не работи добре, защото добавянето на API методи засяга резултати доста по-линейно. Ако има 100 методи на Element.prototype, там трябва да бъде 100 назначения, направени в въпросния елемент, ако има 200 методи, трябва да бъде 200 назначения, направени до един елемент, и така нататък.

Друг хит на изпълнение е със събитие обекти. Prototype следва подобен подход с събития и да ги разширява с определен набор от методи. За съжаление, някои събития в браузъри-mousemove, Изследвай с мишката, mouseout, промяна на размера, име няколко, могат да стрелят с буквално десетки пъти в секунда. Разширяване на всеки един от тях е изключително скъп процес. И за какво? Само да се позове на това, което би могло да бъде единен метод за събитие obejct?

Накрая, след като започнете разширяване елементи, библиотека API най-вероятно нужди, за да се върне по- дълги елементи навсякъде. В резултат, заявки методи, като $$ разширяване на всеки един елемент в заявка. Това е лесно да си представим производителността на overead на този процес, когато говорим за стотици или хиляди елементи.

IE DOM е бъркотия §

Както е показано в предишния раздел, ръководство на DOM разширението е бъркотия. Но ръководство DOM разширение в IE е още по-лошо, и ето защо.

Ние всички знаем, че в IE, кръгови препратки между домакин и изтичане на родния обекти, и са най-добре да се избягват. Но добавянето на методи за DOM елементи е първа стъпка към създаването на такива циклични референции. И тъй като по-стари версии на IE не излагайте “обект прототипи”, няма кой знае какво да правя, но разшири елементи директно. Кръгови препратки и течове са почти неизбежни. И всъщност, Prototype, претърпени от тях по-голямата част от живота.

Друг проблем е начина, по който IE DOM карти свойства и атрибути помежду си. Фактът, че атрибути са в същото пространство от имена като имоти, увеличава шанса на сблъсъци и всички видове неочаквани несъответствия. Какво се случва, ако елемент има обичай “шоу” атрибут и след това се разширява от Prototype. Ще се изненадате, но шоуто “атрибут” ще получи заменена от Prototype, Element#show метод. extendedElement.getAttribute('show') ще се върне референция към функция, а не стойността на атрибут “шоу”. Същия начин extendedElement.hasAttribute('hide') ) бих казал, “истински”, дори ако никога не е имало обичай “скрие” атрибут на елемент. Имайте предвид, че IE < hasAttribute липсва hasAttribute, но ние все още може да видите атрибут / собственост typeof extendedElement.attributes['show']!= "undefined" ” недефинирани “.

И накрая, един от по-малко известни недостатъци е факта, че добавянето на свойства на DOM елементи причинява преформатиране в IE, така че просто удължаване на елемент става доста скъпа операция. Това всъщност има смисъл, като се има предвид недостатъчното картиране на атрибути и свойства в своята DOM.

Бонус: програмни грешки в браузърите §

Ако всичко, ние сме били досега, не е достатъчно (в който случай, вие вероятно сте един мазохист), ето още няколко грешки в началото на всички.

В някои версии на Safari 3.x, има бъг, където навигация към предишна страница чрез бутона за връщане на кърпички край всички разширения домакин обект. За съжаление, бъг е неоткриваем, така да се работи по въпроса, Prototype трябва да се направи нещо ужасно. Sniffs браузър за тази версия на WebKit, и изрично забранява bfcache, като се прибави “разтоварване” слушател събитие window. Хора с увреждания bfcache означава, че браузъра трябва да донесе страница, когато навигацията чрез назад / напред бутони, вместо възстановяването на страница от кеширани състояние.

Друг бъг е с HTMLObjectElement.prototype и HTMLAppletElement.prototype в IE8, и начина, по който обект и елементи на аплета не се наследяват от тези прототип обекти. Можете да присвоите собственост на HTMLObjectElement.prototype, но този имот никога не е “решен” на обект елемент. Същото се отнася и за аплети. В резултат на тези елементи винаги трябва да бъде удължен ръчно, което е друг режийни.

IE8 също разкрива само подмножество на прототипи на обекти, когато в сравнение с други популярни приложения. Например, има HTMLParagraphElement.prototype (както и други специфични за типа), Element.prototype и Element.prototype, HTMLElement не HTMLElement ( HTMLElement.prototype така HTMLElement.prototype Node или Node ( Node.prototype така Node.prototype Element.prototype. Element.prototype в IE8, също няма “наследяват от Object.prototype. Това не са бъгове, сама по себе си, но е нещо, което да имайте предвид, въпреки това: няма нищо добро в това да се опитва да Node възел, например.

Wrappers към спасителните §

Един от най-често срещаните алтернативи на цялата тази бъркотия на DOM разширението е обект опаковки. Това е подход JQuery, е взел от самото начало, както и няколко други библиотеки последва по-късно. Идеята е проста. Вместо да се разширява елементи или директно, да създавате обвивка около тях, и методи за делегат. Не сблъсъци, няма нужда да се справят с домакин обекти лудост, по-лесно да управляват течове и да работят в нефункционално MSHTML DOM, по-добра производителност, по-разумни поддръжка и безболезнено мащабиране.

И все още се избегне процедурните подход.

Прототип 2.0 §

Добрата новина е, че Prototype грешка е нещо, че отива в следващата голяма версия на библиотеката. Що се отнася до мен, всички основните разработчици разбират проблемите, споменати по-горе, и тази обвивка подход е по-разумни начин да се движи напред. Аз не съм сигурен, че плановете са в други DOM разширяване на библиотеките като MooTools. От това, което те вече използват опаковки със събития, но все пак да удължи елементи. Аз съм certinaly надявайки се те се движат далеч от тази лудост в близко бъдеще.

Контролирана среда §

Досега разгледахме DOM разширение от гледна точка на оглед на библиотека кръстосано браузър скриптове. В този контекст, това е ясно, как неприятен тази идея наистина. Но какво да кажем за контролирана среда? Когато скрипта се изпълнява само в една или две среди, като например тези, базирани на Gecko, WebKit или всякакви други модерни не-MSHTML DOM. Може би това е интранет заявление, че са достъпни чрез някои браузъри. Или десктоп, WebKit-базирани ап.

В този случай, situtation определено е по- добре. Нека погледнем точките, изброени по-горе.

Липсата на спецификация става до известна степен без значение, тъй като няма нужда да се притеснявате за съвместимост с други платформи, или бъдещи издания. Повечето от които не MSHTML среди DOM излагат DOM обект на прототипи за доста време, и е малко вероятно да го пуснете в близко бъдеще. Все още има възможност за промяна, обаче.

Точка за ненадеждност на домакин обекти също губи теглото си, тъй като домакин обекти в Gecko или WebKit-базирани ФОД са много, много по-разумни, отколкото тези в MSHTML DOM. Но те все още са домакин обекти, и така трябва да бъдат лекувани с грижи. Освен това, има само за четене имоти, обхванати преди, което лесно може да осакати гъвкавостта на API.

Точката за сблъсък все още държи тегло. Тези среди подкрепа нестандартна форма контрол на достъпа, имате собственически API, и постоянно прилагане на нови функции на HTML5. Промяната на обекти, които не притежават е все още нечестив идея и може да доведе до трудно да се намерят грешки и несъответствия.

Изпълнение натоварване и на практика не съществува, тъй като прототип на базата на тези подкрепа DOM разширението DOM. Ефективността може действително да бъде още по-добре, в сравнение с, да речем, опаковки подход, тъй като няма нужда да създава допълнителни обекти, с цел да се позове на методи (или достъп до свойства) на разстояние от предмети на DOM.

Разширяване на DOM в контролирана среда, че изглежда като напълно здрави нещо да направя. Но въпреки че основният проблем е, че с колизии, аз все още ще посъветва да наемат опаковки вместо. Това е сигурен начин да се движи напред, и ще ви спести от поддръжка режийни в бъдеще.

Послеслов §

Hopefuly, можете да ясно да видим цялата истина зад това, което изглежда елегантен подход. Следващия път, когато се изработи рамка на Javascript, просто кажете “не” на разширения DOM. Кажете не, и да си спестите от всички неприятности за поддържане тромава API и страдание ненужни режийни разходи за изпълнение. Ако от друга страна, вие сте обмисля да използва Javascript библиотека, която се простира DOM, да спре за секунда, и се запитайте, ако сте готови да поемат риск. Е ellusive удобство на DOM разширение наистина си струва всички неприятности?

Comments are closed.