What is the problem with Dom extension?

Source: Internet
Author: User
Tags mootools

Original article address: What's wrong with extending the DOM

Recently, I was surprised to find that there are few extensions to Dom on the Internet.Article. It is disturbing that the shortcomings of this seemingly good practice are not so well known. Except for some secluded social circles. The lack of information can be a good explanation of why modern scripts and class libraries still fall into this trap (Dom is not as good as imagined ). I want to explain why Dom extension is not a good practice by presenting related issues. I will also provide some possible alternatives to this bad practice.

First, we need to know what Dom extensions are? And how does it work?

How does it work?

Dom extension is a simple process of adding custom methods and attributes to DOM objects. Custom Attributes are special implementations that do not exist. What is a DOM object? It is the implementation of the Host object, such as element, event, document and other dom interfaces. Through extension, methods and attributes can be directly added to objects or directly extended to their prototype, if the runtime environment is supported (Firefox supports direct extension on the prototype of the DOM object, whereas IE does not ). The most common object extension method may be the extension of DOM elements, such as the prototype and mootools frameworks. Event objects and document objects are also frequently extended.

Some development tools can display the Element Object prototype (such as firebug). Below is an example of Dom extension:

Element. prototype. hide = function () {This. style. display = 'none ';};... vaR element = document. createelement ('P'); element. style. display; // ''element. hide (); element. style. display; // 'none'

As you can see, the "hide" function is first allocated to the element. the hide attribute of prototype is called directly from an element, and the display attribute in this element style is set to "NONE ".
The principle is that when a P element is created, it inherits the prototype of the element object on the prototype chain, and there will also be the hide method.

When the hide attribute is called, it searches the entire prototype chain until it finds the hide method on the prototype of the element object.

In fact, if we check the prototype chain of the P element in some modern browsers, it may look like this:

// "^" Denotes connection between objects in prototype chaindocument. createelement ('P'); ^ htmlparagraphelement. prototype ^ htmlelement. prototype ^ element. prototype ^ node. prototype ^ object. prototype ^ null

Note that the closest ancestor of the prototype chain of the P element is htmlparagraphelement. prototype, which is an object of the specific element type. For P elements, htmlparagraphelement. prototype is used. For DIV elements, htmldivelement. prototype is used. For element a, htmlanchorelement. prototype is used.
You may ask, why is there such a strange name?
These names comply with interfaces defined in Dom Level 2 HTML specification. These names also define inheritance relationships between these interfaces. For example:The htmlparagraphelement interface has all the attributes and methods of the htmlelement interface.(Description), for example:The htmlelement interface has all the attributes and methods of the element interface.(Description.
Obviously, if we create an attribute on the prototype of paragraph element (P tag), it will not work on anchor element (a tag:

Htmlparagraphelement. prototype. hide = function () {This. style. display = 'none ';};... typeof document. createelement ('A '). hide; // "undefined" typeof document. createelement ('P '). hide; // "function"

This is because the prototype of anchor element (a tag) does not contain HTML paragraphelement. prototype (the prototype of the P tag) is referenced, but contains. prototype (prototype of the tag) reference. To "correct" the attribute, we can write it on the deeper ancestor, as shown inHtmlelement. Prototype,Element. PrototypeOrNode. prototype.Similarly, attributes created on element. Prototype do not take effect on all nodes, but only on element nodes. If we want all nodes (such as text nodes and comment nodes) to have an attributeNode. Prototype. When it comes to text nodes and comment nodes, This is the method that the inheritance interface usually looks:

Document. createtextnode ('foo'); // <text. prototype <characterdata. prototype <node. prototypedocument. createcomment ('bar'); // <comment. prototype <characterdata. prototype <node. prototype

Now, it is very important that the DOM prototype just revealed is not guaranteed. Dom level2 only defines these interfaces and their inheritance relationships. It does not stipulate that there must be a global element attribute, referencing object that's a prototype of all objects implementingElementInterface. There is no rule that a global node attribute should exist. Referencing object that's a prototype of all objects implementingNodeInterface.
IE7 is an example of this environment. It does not display globalNode,Element,Htmlelement,Htmlparagraphelement, or their attributes. The other browser is safari 2.x( like safari 1.x)
So what should we do to deal with environments where the Global Object prototype cannot be "expose? A work und is to directly extend the DOM object:

VaR element = document. createelement ('P ');... element. hide = function () {This. style. display = 'none ';};... element. style. display; // ''element. hide (); element. style. display; // 'none'

What's wrong?
Expanding DOM elements through prototype objects sounds amazing. We have made full use of the essential advantages of the JS prototype and become very object-oriented for Dom programming. In fact, Dom extensions have been so attractive and useful for some years, and prototype JavaScript library regards it as a necessary part of the architecture. However, this seemingly harmless practice hides huge load problems. In a moment, we will see that cross-browser programming has far more disadvantages than advantages. Dom extension is the biggest mistake prototype. js has ever made.
So where is the problem?

Lack of standards

As I mentioned, not all standards have components that expose the object prototype. Dom Level 2 only defines the inheritance relationship between interfaces and them. To fully comply with the DOM Level 2 standard implementation, there is no need to expose global nodes, elements, and other objects. There is no such requirement in any other aspect. Given them, it is possible to manually expand DOM objects, which does not seem to be a big problem. But the truth is that manual scaling is a very slow and inconvenient process (we will see it later ). In fact, its "fast" is limited to a small number of browsers. In the future, portability and cross-platform access capabilities will also be unreliable (such as mobile devices ).

The Host object has no rules to follow.

The problem with the next Dom extension is that the DOM object is the host object, and the host object is the worst group of things. According to ECMA-262 version 3rd, the host object is allowed to do something, not as imagined by other objects. Reference the relevant chapter [8.6.2]:

Host objects may implement these internal methods with any implementation-dependent behaviour, or it may be that a host object implements only some internal methods and not others.

The internal method standards refer to [[get], [[put], [[delete], etc, and so on. Note what it means. Internal method behavior depends on implementation. This means that when calling [[get], it is absolutely normal for the host object to throw an exception. Unfortunately, this is not just a theory. In IE, we can easily and accurately observe these-Examples of the Host object [[get] throwing an exception:

Document. createelement ('P '). offsetparent; // "unspecified error. "New activexobject (" msxml2.xmlhttp "). send; // "object doesn't support this property or method"

The extended DOM object is like walking in the minefield. By definition, you are working on something that allows the performance to be unpredictable and totally unstable. More than that. It may also silent and fail, which is worse. An example of unstable behavior is applet, object, and embed. They throw an exception in some cases when assigning attributes. Similar disasters occur on XML nodes:

VaR xmldoc = new activexobject ("Microsoft. xmldom "); xmldoc. loadxml ('bar'); xmldoc. firstchild. foo = 'bar'; // "Object doesn' t support this property or method"

There are some other failures in IE, such as document. stylesheets [99999] will throw the "invalid procedure call or argument" error, document. createelement ('P '). filters will report "member not found. ". Not only mshtml Dom is a problem. In Firefox, the target attribute of the rewrite event object will throw a "typeerror", because those are read-only and cannot be overwritten. In WebKit, it is not the same. After "target" is assigned and then called in the original object, it will silent and fail to be called.
When creating an API for the event object, you need to consider those read-only attributes, rather than focusing on those concise descriptions.
Opportunity of conflict
It is difficult to scale APIs Based on Dom element extensions. When adding and Modifying core API methods, it is difficult for developers of class libraries to scale them. This is also true for class library users when adding extensions to specific domains. The fundamental problem is that there may be conflicts. Dom has API ownership in the implementation of popular browsers. These APIs are not static. They are often updated based on the release of the browser version. Some will be discarded, and some will be updated or added. Therefore, setting attributes and methods for DOM objects may be like moving targets.
Considering that there are already a large number of Web environments in use today, it is impossible to tell a certain attribute if it is no longer in the Dom. If yes, can it be overwritten? Or will an error be reported when you rewrite it? Remember that he is a host object. If we can rewrite it normally, what does it affect other parts of the DOM object? Will everything happen as expected? If all this works properly in browsers of this version, who can ensure that they will not use the same name in the next version? There are a bunch of problems to continue.
The example about extended ownership that interrupts prototype development involves "Wrap attributes of textarea on IE" (conflicts with the element's wrap method ), there is also the "select method for Form Control Elements on opera" (which conflicts with the select method of the element ). Even though these two cases have already been documented in the document, this is still annoying.
Extended ownership is not the only problem. HTML5 provides a basket of attributes and methods. Most popular browsers are already compatible with them. Sometimes, webforms defines the replace attribute for the input element, and opera decides to add it to their browser, which is in conflict with the relpace method of the element in prototype, it interrupts prototype again.
Wait, there are more problems.
Due to the long tradition of Dom level 0, there is a simple way to disable the Access Form Control of form elements through their name value. This means that in addition to using standard element chains, you can also access them like this:

<Form action = ""> <input name = "foo"> </form>... <SCRIPT type = "text/JavaScript"> document. forms [0]. foo; // non-standard access // compare to document. forms [0]. elements. foo; // standard access </SCRIPT>

Therefore, if you say that you have extended the form element through the login method, you can check the verification information and submit it. And there is a form control whose name value is equal to login in the form. What happens next is not so cute:

<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>

Each form control with a name value masks the attributes inherited from the prototype chain. There are more opportunities for exceptions and conflicts on form elements.
In this case, form elements with name values are similar. They can be accessed directly on the document through their name values:

<Form name = "foo">... </form>... <SCRIPT type = "text/JavaScript"> document. foo; // [object htmlformelement] </SCRIPT>

When the document object is extended, there is an additional risk that it will conflict with the name value of the form element. If the script runs in an old application with a large number of HTMLProgramDeleting these name values is cumbersome, isn't it?
Using some types of prefixes can alleviate these problems, but may also cause some side effects.
Do not modify objects that do not belong to you as a final solution to avoid conflicts. Prototype that destroys this rule is already in trouble.Document. getelementsbyclassname. At the same time, the scripts (other frameworks), whether or not they modify the DOM object, run better in the same environment.

Performance overhead

As we have seen earlier, browsers that do not support element expansion, such as IE6, IE7, Safari 2.x, can only be manually expanded. The problem is that manual scaling is slow, inconvenient, and unmeasurable. The reason for slowness is that objects need to expand a large number of frequently used attributes and methods. The irony is that these browsers are already the slowest among the many browsers. The inconvenient reason is that the object needs to be extended before the operation. Therefore, before document. createelement ('P'). Hide (), you need $ (document. createelement ('P'). Hide (). This method is undoubtedly a stumbling block for beginners of prototype. Finally, manual scaling cannot be measured because the method for adding APIs affects performance linearly. In this case, if there are 100 methods on element. prototype, 100 distributions must be created on one element. If there are 200 methods on element. prototype, 200 distributions must be created on one element.

Another attack on performance is about event objects. Prototype extends a set of methods in the same way. Unfortunately, browser events such as mousemove, Mouseover, mouseout, and resize can be triggered multiple times in one second. Scaling any of them is a very expensive process. So what should we do? Can event objects only call a single method?

Finally, once you start to expand the element, the class library API usually needs to return the extended element. The result is that a query method like $ is terminated when a simple element is extended in the query. It is easy to imagine the performance overhead when we talk about thousands or even hundreds of elements.

Dom of IE is a mess

As shown in the previous chapter, manual Dom extension is confusing. In IE, it will be worse. The following will explain why.

We all know that there is a vulnerability in IE that calls the host and local objects cyclically. We 'd better avoid it. However, the first step to add a method to the DOM element is to create a reference to this loop. The old version of IE does not expose the "object prototype". We can only extend it directly on elements without any other methods. Loop references and vulnerabilities cannot be avoided. In fact, prototype has suffered a lot of losses in its lifecycle.

Another problem is how IE Dom maps attributes (attributes) and properties of each element. In fact, attributes (attributes) and properties in the same namespace increase the chance of conflict, and there are various exceptions and inconsistencies. If the show attribute is customized, what will happen? Will prototype be extended. You will be surprised to find that the show attribute will be overwritten by the element # Show method of prototype. Extendedelement. getattribute ('show') returns a reference to a function instead of the value of the show attribute. Similarly, extendedelement. hasattribute ('hide ') returns true even if the hide attribute is not customized on the element. IE8 and below do not have hasattribute, but we can still see the conflict between attribute and property: typeof extendedelement. attributes ['show ']! = "Undefined ".

Finally, a drawback few people know is that adding properties in IE will cause reflux. Therefore, simply scaling elements is a very expensive operation. It makes sense to discard the ing between attributes (attributes) with defects in the Dom and properties.

"Extra rewards": browser bugs

If this is not enough (maybe you are a masochistic user), there are several buckets that are far behind the above.

In some versions of sarfri 3.x, the extension of all the host objects is erased by clicking the return button in the browser navigation. Unfortunately, this bug cannot be noticed. To solve this problem, prototype had to do something terrible. First, sniff out the WebKit of that version, and then explicitly disable the bfcache (for back-forward cache) on the window unload event ). Disabling bfcache means that the browser will retrieve the page again instead of reading the stored page from the cache.

In IE8,Htmlobjectelement. PrototypeAndHtmlappletelement. Prototype also has bugs. The object and Applet elements are not inherited here. You can assign an attribute to htmlobjectelement. prototype, but it does not work on the object element. The same applies to applets. All these objects need to be manually extended, which is an overhead.

Compared with other popular implementations, IE8 only exposes some prototype objects. For example, it has htmlparagraphelement. Prototype (the same as other special types), element. prototype, but noHtmlelement,Htmlelement. prototype, Node,Node. prototype. Element. prototype is not inherited from object. Prototype in IE8. This is not a bugs in itself, but it should be noted that expansion of nonexistent nodes is of no benefit.

Use encapsulation to save

Instead of this messy Dom extension solution, the most common method is object encapsulation. This is the method jquery started to use, and other class libraries are also followed. This idea is simple. Instead of directly extending the elements or events, You encapsulate them into other objects and delegate methods to them. There is no conflict. You do not need to handle crazy behavior like host objects. vulnerabilities are easier to manage and operate on the abnormal mshtml dom for better performance, sound maintenance and painless scaling.

You can also avoid program methods.

Prototype 2.0

The good news is that prototype will not make such a mistake in the next main version. As for my concerns, all core developers have understood the problems mentioned above, and encapsulation is in a sound development direction. I'm not sure about other plans for Dom-based extension class libraries like mootools. As far as I know, they have encapsulated the events object, but are still extending the element. I hope they will stay away from this stupid behavior in the future.

Controllable Environment


Post notes

Next time, you should also consider risks when using a class library or framework that uses Dom extensions.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.