Let me first talk about Element’s technical ideas for DOM expansion. I also read the code for a day, and then I got some insights.
When using prototype, the most commonly used code is $('div1'). To obtain the expanded element object, we can then use its various extended methods, such as:
$('div1').addClassName('loading').show();
Therefore, we should use this as the entry point to study the extension of Element.
function $(element) {
if (arguments.length > 1) {
for (var i = 0, elements = [], length = arguments.length; i elements.push($(arguments[i]));
return elements;
}
if (Object.isString(element))
element = document.getElementById(element);
return Element.extend(element);
}
This function can handle multiple parameters with one clever recursion, which really impresses me. The key in the code is: Element.extend(element). Before extend, element is just an ordinary DOM object. After extend, it is extended. It can be seen that the secret is in extend.
Since it is Element.extend(element), then of course we cannot study extend alone. We must first understand what Element is.
(function() {
var element = this.Element;
this.Element = function(tagName, attributes) {
attributes = attributes || { };
tagName = tagName. toLowerCase();
var cache = Element.cache;
if (Prototype.Browser.IE && attributes.name) {
tagName = ' ';
delete attributes.name;
return Element.writeAttribute(document.createElement(tagName), attributes);
}
if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName));
Return Element.writeAttribute(cache[tagName].cloneNode(false), attributes);
};
Object.extend(this.Element, element || { });
}).call (window);
This kind of code is more personalized. The basic structure is: (function(){...}).call(window); Therefore, when you see this structure, you must know that this appears in the ellipsis part. It points to the window.
var element=this.Element; This line is not difficult to understand, but the key point is that this.Element has not been defined yet. The next step is to define a class: window.Element class, which has two parameters: tagName, attributes. Its function is to create an element and put a DOM object corresponding to a pure specified tag into the cache. After the element is created, the specified attribute value is written. Here is a reminder:
The two functions writeAttribute and readAttribute obviously have the function of reading and writing attributes. However, their code is not simple. Its complexity mainly comes from the reading and writing of attributes in different browsers. The method is different.
This is the definition of the constructor of the Element class, followed by Element.cache = { }; cache, what kind of cache, it is not easy to describe, the cache of pure versions of DOM element objects of various tags? This is so disgusting to say. Immediately following is one: Element.Methods={......}, here, almost all extension methods are defined. The extension methods here all have one feature. None of them use this in the code. They all pass in an element reference honestly. This is a foreshadowing. There is a reason why it is defined like this. I'll tell you later.
Now that we have briefly talked about Element, we have to talk about Element.extend.
Element.extend = (function() {
if (Prototype.BrowserFeatures.SpecificElementExtensions)
return Prototype.K;
var Methods = { }, ByTag = Element.Methods.ByTag;
var extend = Object.extend(function(element) {
if (!element || element._extendedByPrototype ||
element.nodeType != 1 || element == window) return element;
var methods = Object.clone(Methods),
tagName = element.tagName, property, value;
// extend methods for specific tags
if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]);
for (property in methods) {
value = methods[property];
if (Object.isFunction(value) && !(property in element))
element[property] = value.methodize();
}
element._extendedByPrototype = Prototype.emptyFunction;
return element;
}, {
refresh: function() {
// extend methods for all tags (Safari doesn't need this)
if (!Prototype.BrowserFeatures.ElementExtensions) {
Object.extend(Methods, Element.Methods);
Object.extend(Methods, Element.Methods.Simulated);
}
}
});
extend.refresh();
return extend;
})();
第一行的原理我不大肯定,不说,下面的代码看似复杂,待我抽出它的大概结构来:
var extend=Object.extend(function(element){……},{refresh:function(){……}});
extend内,第一个函数作用是从XXXX.Methods中获取方法,并复制到本element中。这儿主要基于这样的考虑:
一、元素如果是一个Form,就得从Form.Methods取方法
二、元素如果是表单内的可输入元素,就得从Form.Element中取方法
三、所有元素都应当从Element.Methods中取得通用方法(后面的refresh所考虑的)。
也就是说,这儿要考虑多种情况,本来应当是个if语句的事,但是,这儿巧妙地设计了一个Element.Methods.ByTag。从而解决了这个问题。
if (Object.isFunction(value) && !(property in element))
element[property] = value.methodize();
如果Methods中的成员不是函数或者函数在element中已存在,则不会覆盖。这儿到了关键了,那个value.methodize(),这时,前面的伏笔生效了,methodize的作用就是把当前调用者传递进方法。且作为第一个参数传入。它的使用方法一般是:
obj.methodname=functionname.methodize();
这样,调用时,obj对象就会作为第一个参数传入functionsname这个函数。
到此为止,这个extend函数中的大概思路应当清晰了,现在还有一个问题没有清楚:根据元素的tagName来获得应当从哪个Methods获得扩展,那么,我们有必要了解一下ByTag的详情,查找一下,找到了:
Object.extend(Element.Methods.ByTag, {
"FORM": Object.clone(Form.Methods),
"INPUT": Object.clone(Form.Element.Methods),
"SELECT": Object.clone(Form.Element.Methods),
"TEXTAREA": Object.clone(Form.Element.Methods)
});
ok,差不多就这样了。