prototype1.6.03 库 对源码的一些理解(Dom扩展)

看了一天prototype 源码,其实根本没想看的,才刚碰了1天啊,可是有些地方就是想知道他怎么实现的,
又或者有些结果不是自己想象的那样,忍不住还是拿firebug追踪了一下,虽然看的一头雾水,好歹有点收获,这种行为就是浪费时间啊。。。。时间过的好快!一会儿一天就过去了。记录一下,说的很乱,因为还不是非常理解。

且看一点理解一点,理解一点记录一点,最后再去整理吧,不知要等到何时呀~

1 页面加载时,.<script type="text/javascript" src="prototype-1.6.0.3.js"></script> 首先被加载,
js引擎先解析其中定义的变量,也就是 var xxx = .... 的语句。
然后返回头来一句一句执行。定义了各个对象以及工具函数, 几个重点看过的对象/方法有:
( 写法不标准,方便写而已,源码中可要规范多了)
Object.extend(A,B) 实现A继承B的属性方法.,就是一个拷贝属性的函数

Element : 自定义的window.Element , 继承了浏览器自带的Element对象,并用Element.Methods里面的方法进行了扩展。在扩展的构造函数中,对各个扩展方法也进行了methodize化(下面有说)。
其实在prototype中我们使用页面元素对象的情况大多就两种:
1. var e = new Element(tagName);
2   var e = $('ID')
这两种方式有什么异同呢? 对使用来说,第一种用于创建一个元素对象,就像document.createElement一样,但是该对象支持prototype的扩展方法。 第二种用于获取页面中的一个元素,参数ID自然就是表示元素的id了(也可以用别的,文档中有).

重要的是,二者的实现过程。今天看了下源码,大体理了下思路:
1 $() 方法:
function $(element) {
if (arguments.length > 1) { //这一段使用一个递归,来完成多个参数的处理
    for (var i = 0, elements = [], length = arguments.length; i < length; i++)
      elements.push($(arguments[i]));
    return elements;
}
if (Object.isString(element)) //如果传入的是字符串(ID), 先以DOM API获取该元素
    element = document.getElementById(element);
return Element.extend(element); //然后对该元素进行扩展, 用到了Element.extend()方法
}

而Element就稍微复杂一些,因为不同浏览器实现不同,有的内置Element对象(Firefox),有的没有(IE),作者写了一个函数来解决:
// 将window.Element 扩展,使用链式构造函数
(function() {
var element = this.Element; //保存原来的window.Element
this.Element = function(tagName, attributes) { //构造函数
    attributes = attributes || { };
    tagName = tagName.toLowerCase();
    var cache = Element.cache; //该对象用来缓存已经创建的指定tagName的对象,用于快速创建新的指定tagName的对象 //下面会有用法: 使用clone快速从cache里面复制对象,而不用再次document.createElement

//有些元素,xhtml标准是不允许有name属性的,但是IE允许,而且可以用下面的方式使用createElement函数(因为不同浏览器
//对API的实现不同), 因此如果属性中给出了name属性且是ie,就满足用户的需求.既然有name,那么自然也不用缓存了
//因为不可重用啊,name理论上应该是唯一的!
    if (Prototype.Browser.IE && attributes.name) {
      tagName = '<' + tagName + ' name="' + attributes.name + '">';
      delete attributes.name;
   //写入属性并返回Element对象
      return Element.writeAttribute(document.createElement(tagName), attributes);
    }
//如果没有缓存过该对象,则创建并缓存它,比如cache["div"]=document.createElement("div")
    if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName));
//使用cloneNode 从缓存中赋值对象,然后写入属性参数,返回该对象,完成创建
    return Element.writeAttribute(cache[tagName].cloneNode(false), attributes);
};

Object.extend(this.Element, element || { }); //将原来的Element属性加载到新Element
if (element) this.Element.prototype = element.prototype;//加载旧的prototype
}).call(window);
// 以window对象的身份调用该函数, 将Element类附加到window的属性中,完成扩展.

在后面的代码中,对Element类附加了一些方法,自然包括Element.Methods啦。
// 将Element.Methods 赋给Element类.
Object.extend(Element, Element.Methods);
....
//某些浏览器,比如IE,是没有该方法的,因此要进行扩展
Element.hasAttribute = function(element, attribute) {
if (element.hasAttribute) return element.hasAttribute(attribute);
return Element.Methods.Simulated.hasAttribute(element, attribute);
};


此外还有一些方法,暂时还没看,都是以 Element.functionName = function(){..} 形式定义的。

另一个重要方法是Element.extend, 它是完成元素对象扩展的重头戏:
Element.extend(x)
//很有意思的写法, Element.extend 指向的函数决定于一个条件:
// if (Prototype.BrowserFeatures.SpecificElementExtensions),
//如果为真,则extend = k(x), 否则就是一个完成扩展功能的函数.
Element.extend = (function() {
if (Prototype.BrowserFeatures.SpecificElementExtensions)
    return Prototype.K;
   // 返回一个function,其功能是对参数对象进行扩展,主要是将 Element.Methods 中的方法附加到对象上
}
Element.Methods 对象,包含了各个扩展方法, 其各个方法都有个elem参数,也就是调用该方法的元素对象。而实际代码中,调用的时候是不传入该参数的,比如 函数定义:
update(elem, argument); 用一个div元素对象去调用它 mydiv.update(argument), 不传入mydiv这个对象参数。 实际作者是这么做的:
首先对Fouction 类的原型做扩展,加上methodize方法,在对 Element对象扩展的时候,当要把Element.Methods 中的方法加到 elem 对象上,是这样做的: 不然函数名是 update,
element["update"] = update.methodize(); 这个methodize() 的作用就是:
返回一个函数: function(){
    return update.apply (null, [this].concat(arguments));
}
也就是如果调用 div.update(arg), 实际调用的是 update.apply(this, [div,arg..]) ,也就符合了 Methods中的定义 update(elem,arguments);
因此这些扩展函数有三种调用方法:
1. $(元素ID).update(参数);
2. Element.update(元素ID, 参数); // prototype初始化的时候将扩展函数都继承到Element类了,而且是没有经过methodize()函数包装的"原装方法"。
3. var e = new Element('div');   e.update(参数); 类似方式1.

至于作者这样做的原因, 开始我以为直接调用会造成this 对象丢失的情况,也就是 div.update(args) , 在执行过程中this并不指向div对象,但测试下来没有这个问题。 因此我认为,作者是为了给用户一个方便的调用方式。 毕竟 div.update(args) 比 div.update(div, args) 要清晰、方便一些。

看代码过程中的一些注解
//很有意思的写法, Element.extend 指向的函数决定于一个条件:
// if (Prototype.BrowserFeatures.SpecificElementExtensions),
//如果为真,则extend = k(x), 否则就是一个完成扩展功能的函数.
//该方法将传入elem对象扩展后返回新对象.

// 看了半天才看懂, NND..
//不返回K的情况(IE测试,FF不会执行这一段):
//function(){
//定义Methods = {}
// 局部变量extend = Object.extend(function(element),refresh());
// 调用extend.refresh(); 就用 Element.Methods,Element.Methods.Simulated
//填充了Methods,然后返回extend.
//}, 然后function() 调用. 返回extend代表的函数

//注意,这里使用了js里面的闭包特性, 我们关注那个var Methods = { }对象,
//extend方法中,var methods = Object.clone(Methods) 这一句拷贝了Methods中的内容.而实际调用extend的
//是外部元素Element.也就是在外部function(即Element.extend = (function() 中那个function)之外调用,而Methods是一个私有变量,它的值是外部函数返回时的值
//,但也会受到内部函数的影响。
//即,当调用了外部函数时,默认执行了一个refresh, 然后返回extend.此时的Methods就是refresh过后的,那么extend内也会用到这些内容.
//如果以后再次调用refresh(), Methods依然存在,虽然它是在外部函数中,并且会得到更新,再次调用extend,它引用的Methods内容也会
//更新.
//不过我觉得,这里不用闭包这种抽象的概念也可以完成,呵呵,大概是为了便于管理:)

//关于扩展函数: 一共有两个来源, 一个是 Element.Methods,一个是Element.Methods.ByTag[]
//其中Element.Methods中的方法是对各个元素通用的, 而Element.Methods.ByTag[] 是针对特定标签元素的
//在后续的Element.AddMethods 方法中, 会根据AddMethods的参数来决定是通用方法还是面向特定元素的方法
//然后向Element.Methods 或者 Element.Methods.ByTag填充方法.
//最后,调用Element.extend.refresh() 将新的方法填充到Methods对象.因为extend方法是从Methods对象中取方法,
//而refresh方法是向Methods对象中填充方法.

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;

    //看到没有,这里methods 数据来自Methods,而Methods由refresh()决定
    var methods = Object.clone(Methods),
      tagName = element.tagName.toUpperCase(), property, value;
  
     //对于指定的标签,将指定标签扩展方法加载到methods中
    // extend methods for specific tags
    if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]);

    //进行扩展,这里使用了Function.prototype的methodize(),methidze是将调用者附加到方法参数中.
    for (property in methods) {
      value = methods[property];
      if (Object.isFunction(value) && !(property in element))
      {
   //alert("DEBUG: Mtheods.value: "+value);
   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();//填充Methods对象
return extend; //返回内部的扩展函数
})();

// 该对象包含的方法是模拟浏览器原生的一些方法,
//比如hasAttribute, IE就没有, 而Firefox原生内置该方法.
//当一个元素被ELement.extend扩展后,就具有了该能力

Element.Methods.Simulated = {
hasAttribute: function(element, attribute) {
    attribute = Element._attributeTranslations.has[attribute] || attribute;
    var node = $(element).getAttributeNode(attribute);
    return !!(node && node.specified);
}
};

还有一个方法,Elment.AddMethods
给Element添加方法。其工作过程涉及Element.Methods 和Element.Methods.ByTag
Element.Methods 是对所有元素要扩展的方法集合,ByTag是对特定元素的一些附加方法

Element.addMethods = function(methods) {
//注意T 是对对象的引用哦,开始脑袋晕了,居然当成值传递了,导致下面的代码感觉有问题-_-!
var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag;

//如果无参数,则扩展特殊元素的方法
if (!methods) {
    Object.extend(Form, Form.Methods);
    Object.extend(Form.Element, Form.Element.Methods);
    Object.extend(Element.Methods.ByTag, { //维护扩展函的数组(以tagName索引)
      "FORM":     Object.clone(Form.Methods),
      "INPUT":    Object.clone(Form.Element.Methods),
      "SELECT":   Object.clone(Form.Element.Methods),
      "TEXTAREA": Object.clone(Form.Element.Methods)
    });
}

if (arguments.length == 2) {
    var tagName = methods; //arguments[0],Array or Object
    methods = arguments[1];//the 2nd argument which is the method to be added
}

//如果参数不是2,即不是针对某个tag的,则扩展到Methods.
if (!tagName) Object.extend(Element.Methods, methods || { });
else {//否则对参数中每个tag进行继承扩展
    if (Object.isArray(tagName)) tagName.each(extend);
    else extend(tagName);
}

//将函数附加到ByTag[]数组
function extend(tagName) {
    tagName = tagName.toUpperCase();
    if (!Element.Methods.ByTag[tagName])
      Element.Methods.ByTag[tagName] = { };
    Object.extend(Element.Methods.ByTag[tagName], methods); //将方法添加到特定的tag元素上
}

//拷贝方法,用到了methodize
function copy(methods, destination, onlyIfAbsent) {
    onlyIfAbsent = onlyIfAbsent || false;
    for (var property in methods) {
      var value = methods[property];
      if (!Object.isFunction(value)) continue;
      if (!onlyIfAbsent || !(property in destination))
        destination[property] = value.methodize();
    }
}

//返回DOM对象的类,以tagName做元素索引
function findDOMClass(tagName) {
    var klass;
    var trans = {
      "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph",
      "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList",
      "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading",
      "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote",
      "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION":
      "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD":
      "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR":
      "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET":
      "FrameSet", "IFRAME": "IFrame"
    };
    if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element';
    if (window[klass]) return window[klass];
    klass = 'HTML' + tagName + 'Element';
    if (window[klass]) return window[klass];
    klass = 'HTML' + tagName.capitalize() + 'Element';
    if (window[klass]) return window[klass];

    window[klass] = { };
    window[klass].prototype = document.createElement(tagName)['__proto__'];
    return window[klass];
}

//我要说说这两个条件:
//F.ElementExtensions :我认为是判断浏览器是否有内置元素对象
//比如IE 输出div对象的时候,只有Object, 而Firefox会输出HTMLdivElement,FF对其进行了区分,这就是不同
//F.SpecificElementExtensions 类似,是支持一些特殊的元素扩展
//这两个对象在Prototype对象中有描述.
//事实上, Prototype库采用了这样创建/扩展元素的方法:
//对于不符合这些条件的浏览器,创建/获取/扩展对象的时候,都用Element.extend将对象进行扩展.
//否则,就用下面这两段代码进行扩展,直接扩展到元素的原型上.
//在使用$() 来获取元素对象的时候,会调用Element.extend()方法, 而Element.extend内部,会根据是否符合这两个
//条件,决定是直接返回对象(因为已经在下面对原型进行了扩展,不需要再扩展了),还是扩展后返回。
//当然,支持这些条件的浏览器会有较高的效率,因为每次获取不同的元素,不需要再次扩展了,这也是原型扩展的好处

//如果浏览器内置了那些那些对象,比如Firefox中的window.HTMLdivElement
//就直接将扩展函数拷贝给HTMLElement
if (F.ElementExtensions) {
    copy(Element.Methods, HTMLElement.prototype);
    copy(Element.Methods.Simulated, HTMLElement.prototype, true);
}

//同上, 满足条件则把对特定标签的扩展方法直接加到对象的原型上
if (F.SpecificElementExtensions) {
    for (var tag in Element.Methods.ByTag) {
      var klass = findDOMClass(tag);
      if (Object.isUndefined(klass)) continue;
      copy(T[tag], klass.prototype);
    }
}

//alert("Extended " + counter++);
//将更新后的Element.Methods附加到Element类
Object.extend(Element, Element.Methods);
delete Element.ByTag
;//上一句也将ByTag附加给Element类了,去掉它,因为Element不需要ByTag

if (Element.extend.refresh) Element.extend.refresh();
Element.cache = { };//既然上面将Element类更新了(添加了新方法),那么自然要清空缓存,因为缓存的对象都是未更新之前
      //的Element类的对象,不支持新方法.
};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值