WEB开发网
开发学院网页设计JavaScript Jquery源码分析---DOM元素(上) 阅读

Jquery源码分析---DOM元素(上)

 2010-09-14 13:36:38 来源:WEB开发网   
核心提示:5.1 dom元素的属性对dom元素的操作,对元素的属性进行操作是很重要的一项,Jquery源码分析---DOM元素(上),我们可以通过dom元素的原始方法对元素元素进行操作,但是由于浏览器的兼容等各方面的问题,在⑤处是通过elem.currentStyle[name]来获取属性值,对于为数值的值,jquery和其它的

5.1 dom元素的属性

对dom元素的操作,对元素的属性进行操作是很重要的一项。我们可以通过dom元素的原始方法对元素元素进行操作,但是由于浏览器的兼容等各方面的问题,jquery和其它的lib一样,都提供了一个完好兼容的操作。

5.1.1 Attr

名称及描述返回兼容性
getAttribute( name )

当前节点给定Name的属性值

ObjectAll
getAttributeNS( namespace, name )

当前节点给定namespace,Name的属性值

ObjectAll
getAttributeNode( name )

取当前节点给定name的属性节点。

AttrAll
getAttributeNodeNS( namespace, name )

取当前节点给定namespace,name的属性节点。

AttrAll
hasAttribute( name )

当前节点是否有指定name的属性

BooleanAll
hasAttributeNS( namespace, name )

当前节点是否有指定namespace,name的属性

BooleanAll
hasAttributes()

当前节点是否有属性

BooleanAll
removeAttribute( name )

删除当前节点给定name的属性。

-All
removeAttributeNS( namespace, name )

删除当前节点给定namespace,name的属性。

removeAttributeNode( name )

删除当前节点给定name的属性节点

-All
setAttribute( name, value )

设定当前节点给定name的属性值.

-All
setAttributeNS( namespace, name, value )

设定当前节点给定namespace,name的属性值.

-All
setAttributeNode( name, attrNode )

设定当前节点给定name的属性值节点。

-All
setAttributeNodeNS( namespace, name, attrNode )

设定当前节点给定namespace,name的属性值节点。

-All
上表是 mozilla文档中element的对于attribute的相关操作函数。我们可以看出对于属性的操作只有取值,设值,删除,判断四个方面。每个方面都有对属性值,属性节点和带有命名空间的操作。

但是对于大多数使用,我们只会用到getAttribute( name )、setAttribute( name, value )、removeAttribute( name )这三种。而jquery正好提供了这三种的实现。它主要的功能就是解决IE、FF等等的兼容问题。比如opacity属性。还有一些html中标签的属性名是元素的属性的简写,如for—htmlFor。我们现在调用Jquery的方法只要直接用简写的for就可以,jquery会完成它们对应的转换。

Jquery不光在这里给我们提供支援了方便,就是名字上也提供了方便。它把getAttribute( name )、setAttribute( name, value )长且难记的方法名统一在attrr的函数中。

  // attr(properties)
  // 将“名/值”形式的对象设置为所有匹配元素的属性。
  // attr(name)
  // 取得第一个匹配元素的属性值。通过这个方法可以方便地从第一个匹配元素中获取一个//属性的值。如果元素没有相应属性,则返回 undefined 。
  // attr(key,value)
  // 为所有匹配的元素设置一个属性值。$("img").attr("src","test.jpg");
  // attr(key,fn)
  // 为所有匹配的元素设置一个由这个函数计算的值做属性值。
  attr : function(name, value, type) {
    var options = name;// 支持"名/值"形式的对象和string
   if (name.constructor == String)
    if (value === undefined)
      // 调用curCss,attr静态方法返回本对象第一个元素的name的属性值
      return this[0] && jQuery[type || "attr"](this[0], name); ①
    else {// 设定属性值,把name String 转换成"名/值"对象,便于统一的处理
       options = {};
       options[name] = value;
    }
    return this.each(function(i) {// 为每个元素的指定的name属性设值
       for (name in options)
      // value=options[name],可以是Fn(index),this-->each elem
      jQuery.attr(type ? this.style : this, name, jQuery  ②
         .prop(this, options[name], type, i, name));
      });
 },

上面的代码的①是一个取值操作,因为type是内部使用的参数,type至目前只能是curCSS,也就是如果传入type的值,那就是调用jQuery.curCSS在元素的CSS中去找到对应的属性,如hegth,width等。或者就是调用jQuery.attr在元素中找到对应名字的属性。注意CSS中的属性和元素的属性是不一样,CSS是通过元素的属性class或style来设定的。

在代码的②处也是调用jQuery.attr进行设值,不过它会先调用jQuery.prop对传入的value进行计算,如value是函数,就取函数返回值。如果value为数字且是为元素的CSS设属性值(如height)的话,说明其没有指定单位,prop会加上px作为默认的单位。

我们先看一下jQuery.prop的代码:

// 根据指定元素(elem)的指定的name来修正value值,如加px,exec Fn.
  prop : function(elem, value, type, i, name) {
    if (jQuery.isFunction(value))// value=Fn
      value = value.call(elem, i);// 得到Fn的返回value
    // 对于element的style中CSS属性,对需要加上单位的加上px单位
    return value && value.constructor == Number && type == "curCSS"
         && !exclude.test(name) ? value + "px" : value;
   },

jQuery.Prop返回修正后的value,再回到attr 中代码的②处看一下jQuery.attr,它完成取值和设值的功能。

  // 为给定的elem的name属性设定value值或取elem的name属性值
  attr : function(elem, name, value) {
    // 文本,注释节点不处理                       ①
   if (!elem || elem.nodeType == 3 || elem.nodeType == 8)
      return undefined;
   var notxml = !jQuery.isXMLDoc(elem),//不处理xml文档的   
      set = value !== undefined,// 取值还是设值?
      msie = jQuery.browser.msie;//ie
  // 兼容的处理,标签中的属性名是元素属性名的简化:for--htmlFor,
  //标签中的属性名全部是小写,而元素属性名采用lamb形式:rowspan : "rowSpan"   name = notxml && jQuery.props[name] || name;//可以看出&&、||的用法。  
if (elem.tagName) {//是元素                     ②
   var special = /href|src|style/.test(name);// 要特殊处理
  // 对于safari的特殊处理
   if (name == "selected" && jQuery.browser.safari)
         elem.parentNode.selectedIndex;
   if (name in elem && notxml && !special) {// 通过DOM 0方式进入属性③
     if (set) {// 改变属性但IE中的type类型的元素不能改变
       if (name == "type" && jQuery.nodeName(elem, "input")
              && elem.parentNode)
           throw "type property can't be changed";
      elem[name] = value;                     
      }
      // 对于attr(form,name),取的是form[name].value
    if (jQuery.nodeName(elem, "form")&& elem.getAttributeNode(name))
          return elem.getAttributeNode(name).nodeValue;   ④
  return elem[name];                                    
}
  // 对style进行属性的操作     
  if (msie && notxml && name == "style")               ⑤
    //再次调用本函数,执行第二部分的代码,设定style中cssText.
     return jQuery.attr(elem.style, "cssText", value);
  // IE会报错 see #1070 "" + value转换为字符串
if (set) elem.setAttribute(name, "" + value); //dom1       ⑥
  var attr = msie && notxml && special//href|src|style 在IE要特殊处理
       ? elem.getAttribute(name, 2): elem.getAttribute(name);
       // 不存在的属性返回null时,改成返回undefined
  return attr === null ? undefined : attr;
}
// 元素的CSS的属性,也就是当elem参数是元素的style时。。。         ⑦
// IE 使用 filters for opacity
if (msie && name == "opacity") {                   ⑧
  if (set) {// IE opacity 要层的支持
    elem.zoom = 1;
    // 设 alpha filter 来设定 opacity
elem.filter = (elem.filter || "").replace(/alpha([^)]*)/,"") + ((parseInt(value) + '' == "NaN"? "": "alpha(opacity=" + value * 100 + ")"));
   }
  //找到opacity的值并返回。
return elem.filter && elem.filter.indexOf("opacity=") >= 0?
(parseFloat(elem.filter.match(/opacity=([^)]*)/)[1])/100)+'': "";
}
// lamb字的支持
name = name.replace(/-([a-z])/ig, function(all, letter) {     ⑨
      return letter.toUpperCase();});
if (set) elem[name] = value;
return elem[name];
},

jQuery.attr函数分成三个部分,第一部分②处之前的代码,它是处理一些准备工作,如标签属性名与元素的属性名之间的对应。第二部分是②~⑦,它主要完成对元素的属性进行设值或取值。③~⑤首先通过Dom 0的方式(elem[name])来,如果不能完成的话,就采用⑤~⑦的Dom1的方式通过getAttribute( name )、setAttribute( name, value )来进行操作。这种操作相对Dom0的方式,可以对XML进行操作。而且还能对Dom1的href|src|style属性进行操作。

在⑤处,我们可以看出IE 中Style属性要进行特殊的处理。这个处理就是第三部分的的任务,位处于⑦之后的代码,对于元素的CSS属性进行取值或设值。在⑨处,我们可以看到CSS属性名只支持lamb字的形式。

jQuery.attr不但可以完成对元素的属性设/取值,还可以能CSS进行设/取值。

对于属性的操作,jquery还提供了removeAttr。它通过调用Element ‘s removeAttribute()来完成对this中每个元素都删除属性。

// 一组对元素attr,class等进行操作的函数
jQuery.each( {
  removeAttr : function(name) {// 除去元素的一个属性
      jQuery.attr(this, name, "");
      if (this.nodeType == 1)
       this.removeAttribute(name);//this==dom element
    },
    .. .. ..
  }, function(name, fn) {
    jQuery.fn[name] = function() {
      return this.each(fn, arguments);
    };
});

5.1.2 Class

在开发过程中,对元素的class进行操作是经常的事情,如为元素增加一个class或删除一个class或对一个class进行toggle操作。Jquery提供了三个方法addClass、removeClass、toggleClass用来完成对class的操作。

// 一组对元素attr,class等进行操作的函数
jQuery.each( {
    addClass : function(classNames) {// 为元素增加一些classNames
      jQuery.className.add(this, classNames);
    },
    removeClass : function(classNames) {// 除去元素的一些classNames
      jQuery.className.remove(this, classNames);
    },
    toggleClass : function(classNames) {// 开关该class,
      jQuery.className[jQuery.className.has(this, classNames)
         ? "remove" : "add"](this, classNames);
    },
  }, function(name, fn) {
    jQuery.fn[name] = function() {
      return this.each(fn, arguments);
    };
  });

上面的代码简单,它们调用jQuery. className中的add或remove方法:

  // 一组内部使用的Class操作函数
className : {
    // 为元素增加classNameS
   add : function(elem, classNames) {// 多个className,空格分开
    jQuery.each((classNames || "").split(/s+/),
     function(i, className) {
if (elem.nodeType == 1 
         && !jQuery.className.has(elem.className,className))
      elem.className += (elem.className ? " " : "") + className;
      });
    },
// 为元素除去classNames
remove : function(elem, classNames) {
  if (elem.nodeType == 1)// 元素
   elem.className = classNames != undefined ? jQuery.grep(
    elem.className.split(/s+/), function(className) {// 过滤
       return !jQuery.className.has(classNames,className);
      }).join(" ") : "";
    },
// 元素有没有className?
has : function(elem, className) {
  return jQuery.inArray(className, (elem.className || elem)
      .toString().split(/s+/)) > -1;
    }
},

jQuery.className.has方法先把elem.className分成多个class(如果有多个的话),再判断参数className在数组中的位置来判断元素是否包含指定的class。jQuery.className.add先判断元素是不是含有指定的class,没有话就追加。jQuery.className.remove 正好相反。

Jquery还提供了一个hasClass用来判断其集合的元素是否含有指定的class,如果有一个含有的话,就返回true。

/ 检查当前的元素是否含有某个特定的类,如果有,则返回true
  hasClass : function(selector) {
    return this.is("." + selector);
  },

5.1.3 expando (data)

有些时间我们需要保存于一些数据,这些数据与dom元素有关。比如我自定义的事件。当然有很多方法来实现。其中有一个环节,我们是不可以少的,就是元素和数据的位置联系起来。采用dom元素的name或ID做为储存的名字行不行,可以,但是不是每个元素都有名字或Id的。而且这个name或ID是经常用到的属性。耦合性有点高。如果能提供一个用户不知道的又不会用的属性来做为两者之间的关系是多好。用户根本不要去关心这个关连的环节。只要知道怎么用就可以。

Jquery给dom元素扩展了expando属性。Expando还是有可能会属性冲突的,jquery采用了动态生成的常量,它对于每个客户端的浏览器都不一样,但是对运行jquery的的整个生命周期就是常量,不变。我们要的就是这样。它定义了一个var expando = "jQuery" + now()。只要通过Jquery[expando]就可以找到属于这个生命周期的expando常量。

现在Jquery[expando]就是元素的扩展的属性名。只要在这个属性中分配一个全局唯一的的Id,同时比存储的地方也指定这个Id,那么dom元素就和数据的储存联系起来。

Jquery采用cache : {}做为自定义的数据的储存位置。每一个不同的dom元素,要保存数据的话,都在这个里建立相对于的Id的key/value对。如cache={Dom1_Id:{},Dom2_id:{}}。如我们在合用$(dom1).data(xx,yyy)。那么其cache={Dom1_Id:{xx:yyy} }。这样我们可以为元素保储众多的又不冲突的数据。这个在事件应该是很多的。

接下我们看一下jquery提供的Data的方法

// data(name,value) 在元素上存放数据,同时也返回value。
  // 如果jQuery集合指向多个元素,那将在所有元素上设置对应数据。
  // data(name)返回元素上储存的相应名字的数据,用data(name, value)来设定。
  // 如果jQuery集合指向多个元素,那将只返回第一个元素的对应数据
  data : function(key, value) {
   var parts = key.split(".");
   parts[1] = parts[1] ? "." + parts[1] : "";
  if (value === undefined) {// 取值
      // 加上"!"表明是自定义的事件。在系统扩展的事件用到。
      // 执行getDataxx的定义事件的所有处理函数。返回处理的结果集。
//如Ext组件的事件机制。这里主要是UI设计之前。
    var data = this.triggerHandler("getData" + parts[1] + "!",
         [parts[0]]);
    if (data === undefined && this.length)
       data = jQuery.data(this[0], key);
    return data === undefined && parts[1] ? this.data(parts[0]) : data;
    }
  else { // 设值
    return this.trigger("setData" + parts[1] + "!", [parts[0], value])
         .each(function() {jQuery.data(this, key, value);
         });
    }
  },

上面的代码中的事件先可以不去用研究。这是自定义事件的一种方法。除了这个就是调用了jQuery.data来完成设值和取值。

//没有data时,就初始化,或取data,有就保存
  data : function(elem, name, data) {
      elem = elem == window ? windowData : elem;
      var id = elem[expando];    
      if (!id)//为元素扩展一个全局的id.
       id = elem[expando] = ++uuid;
      if (name && !jQuery.cache[id])
       jQuery.cache[id] = {};//生成cache      
     //防止覆盖已经命名的cache采用没有定义的values   
      if (data !== undefined)
       jQuery.cache[id][name] = data;
      //cache={id1:{name1:data}}     
      //返回命名的cache data
      return name ? jQuery.cache[id][name] : id;
   },

jQuery.data如果只有一个参数elem。其作用是就取得该元素的expando常量的uuid属性值。如果之前没有分配,就分配一个唯一的UUID,之后返回该值。通常一个参数做为初始化元素的扩展expando属性而用的。

对于二个参数,elem,name,那就是从cache:{elem_uuid:{name:vaue}}取name对应的value。如果cache没有对应的uuid,就先初始化,在cache分配其对应的空间,之后返回空对象。如果三个参数都有,就是为cache中name设值。这个data可以是任何对象。

内存泄漏一直都是程序的话题,有了增加数据,为了不内存泄漏,在不使用的时候,一定要removeData:

  // 在元素上移除存放的数据 与$(...).data(name, value)函数作用相反
  removeData : function(key) {
    return this.each(function() {
      jQuery.removeData(this, key);
    });
  },

看一下: jQuery.removeData:

 removeData : function(elem, name) {
      elem = elem == window ? windowData : elem;
      var id = elem[expando];    
      if (name) {//remove指定名字的
       if (jQuery.cache[id]) {
         //remove name对应的数据
         delete jQuery.cache[id][name];
  
        //在cache[id])中找不到,说明该元素没有事件
         //那么除去该元素,也就是全局的guid:{}
         name = "";
         for (name in jQuery.cache[id])
           break;
         if (!name)
           jQuery.removeData(elem);
       }     
      } else {      
       try {//remove元素的扩展的expando属性
         delete elem[expando];
       } catch (e) {
         //IE必须要调用removeAttribute在remove
         if (elem.removeAttribute)
           elem.removeAttribute(expando);
       }
       //在cache中除去uuid:{}
        delete jQuery.cache[id];
      }
    },

RemoveData有二个参数,如果只有一个elem的参数,那么就把这个扩展的属性除去,同时还把cache中与扩展expando对应的所有的数据都删除。

如果传入两个参数,先删除指定的name对应的数据。如果elem范扩展的属性expando对应的uuid是空对象的话,也把这个给删除。如:cache:{elem_uuid:{ }}—> cache:{ }。

5.2 dom 元素的CSS

对于web的应用,控制页面的显示效果是必不可少的一项工作,元素的显示就是通过CSS来实现的。CSS可以控制元素的宽度,高度,位置,可见性等等。

5.2.1 CSS 属性

对于页面样式控制,我们可以事先通过class文件或元素的style的属性来定好。但是有的时候我们也需要动态去改变样式,比如我想每点击一次,元素的高度就变高一点。这个我们就得动态通过javascript来设定。一般的情况我们会采用elem.style.heigth=elem.style.heigth+1。这只是一个简单的情况,如果我设的CSS属性很多,这样就很烦琐。

Jquery的css的函数就提供了能key/value的形式来提供多样式属性。

  // css(name)
  // 访问第一个匹配元素的样式属性。
  // css(name,value)
  // 在所有匹配的元素中,设置一个样式属性的值。数字将自动转化为像素值
  // css(properties)
  // 把一个“名/值对”对象设置为所有匹配元素的样式属性。这是一种在所有匹配的元素上设置大量样式属性的最佳方式。
  css : function(key, value) {
    if ((key == 'width' || key == 'height') && parseFloat(value) < 0)
      value = undefined;// 忽略负数
    return this.attr(key, value, "curCSS");// 调用了curCSS方法
 },

上面的代码中可以看出其还是调用attr来完成任务。在attr的type参数提供了curCSS值。在5.1.1节上,我们可以看出如果是取值就是采用jquery. curCSS,如果是设值还是采用jQuery.attr来完成。Jquery.attr已经分析。我们看看jquery. curCSS怎么从样式中取出值?设值和取值在CSS中是不一样,也就是为什么设值能统一在jQuery.attr,而取值不能。在CSS中取值,要分成二种情况,一是从elem.style中取值。第二种是已经计算过的CSS属性值,这个就包含从class得到样式属性值。因为一般来说style中的属性优先级高于class。所以可以先从style中然后再从已经计算的CSS属性中找。不过在FF中是通过defaultView.getComputedStyle 来取得属性值,而IE是通过elem.currentStyle来获得的。如果在class中采用了!import。那么force就应该为true。直接从计算过的属性中找才是当前的正在使用的属性。

//找elem的对应name的CSS的属性。
curCSS : function(elem, name, force) {
  var ret, style = elem.style;
  // elem的属性值被破坏时
  function color(elem) {//仅仅是为了safari
    if (!jQuery.browser.safari) return false;
    // 从defaultView 取得元素的已经计算艽的的样式。
    var ret = defaultView.getComputedStyle(elem, null);
    return !ret || ret.getPropertyValue("color") == "";
    }
  // IE 中opacity 不兼容
  if (name == "opacity" && jQuery.browser.msie) {
    ret = jQuery.attr(style, "opacity");//调用attr,设定IE opacity
    return ret == "" ? "1" : ret;// 1是100%的显示
    }
// Opera的display bug修正, 见 #2037
  if (jQuery.browser.opera && name == "display") {
    var save = style.outline;
    style.outline = "0 solid black";
    style.outline = save;
  }
   //http://www.w3.org/TR/DOM-Level-2-Style
///css.html#CSS-DOMImplementationCSS
   //http://developer.mozilla.org/en/docs/DOM:CSS      
   //http://developer.mozilla.org/en/docs/CSS:float
  if (name.match(/float/i))// 标签中float是通过styleFloat取值的
    name = styleFloat;
  if (!force && style && style[name])                 ①
    ret = style[name];// 取值
    //Returns computed style of the element. Computed style represents
//the final computed values of all CSS properties for the element.
else if (defaultView.getComputedStyle)            ②
{// 看看defaultView的已经计算过的CSS defaultView一般==window       
    if (name.match(/float/i))  name = "float";
    // 转换成lamb,如addMethod变成add-method
    name = name.replace(/([A-Z])/g, "-$1").toLowerCase();        
    //window.getComputedStyle(element, pseudoElt);
    //pseudoElt is a string specifying the pseudo-element to match.
//Should be an empty string for regular elements        //http://developer.mozilla.org/en/docs/DOM:window.getComputedStyle
    var computedStyle = defaultView.getComputedStyle(elem, null);
    if (computedStyle && !color(elem))
    //http://www.w3.org/TR/DOM-Level-2-Style/
//css.html#CSS-CSSStyleDeclaration
      ret = computedStyle.getPropertyValue(name);         ③
    else {// Safari没有正确地报道,会提示none elements are involved ④
      var swap = [], stack = [], a = elem, i = 0;
      // 找到所有的父节点display为none的节点。
      for (;a && color(a); a = a.parentNode) stack.unshift(a);
      for (;i < stack.length; i++)//显示它们
       if (color(stack[i])) {
         swap[i] = stack[i].style.display;
         stack[i].style.display = "block";
       }
    //有的浏览器只有在display的情况下才计算CSS的值
    ret = name == "display" && swap[stack.length - 1] != null          ? "none": (computedStyle && computedStyle               .getPropertyValue(name))|| "";
    //恢复改变display的节点为none
    for (i = 0;i < swap.length; i++)
      if (swap[i] != null) stack[i].style.display = swap[i];
    }
  // We should always get a number back from opacity
  if (name == "opacity" && ret == "")ret = "1";
} else if (elem.currentStyle) {// 元素的currentStyle,在IE中。   ⑤
  //lamb字
  var camelCase = name.replace(/-(w)/g, function(all, letter) {
      return letter.toUpperCase();  });
  ret = elem.currentStyle[name] || elem.currentStyle[camelCase];
  // From the awesome hack by Dean Edwards
// http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
// If we're not dealing with a regular pixel number
// but a number that has a weird ending, we need to convert it to pixels
if (!/^d+(px)?$/i.test(ret) && /^d/.test(ret)) {       ⑥
    // Remember the original values
    var left = style.left, rsLeft = elem.runtimeStyle.left;
  // Put in the new values to get a computed value out
  elem.runtimeStyle.left = elem.currentStyle.left;
  style.left = ret || 0;
  ret = style.pixelLeft + "px";
// Revert the changed values
  style.left = left;
  elem.runtimeStyle.left = rsLeft;
  }
}
return ret;
},

上面代码中①处是通过style[name]来获得style中CSS值。如果style没有设定这个样式属性或显示指示不从style中获取的话,那么从就是计算过的CSS中取值。在FF系列中会执行②处中间的代码,在IE中会执行⑤的代码。

在③处是通过defaultView.getComputedStyle(elem, null). getPropertyValue(name)来获取属性值,如果没有取到值,很有可能是元素的在文档中没有显示。有的浏览器不会计算它的属性。在④为了取得没有显示出来的元素的属性值而设定的。其先让元素显示出来(如果是父辈元素没有显示,也全部显示),之后计算出属性值取值。再之后恢复其为不显示。

在⑤处是通过elem.currentStyle[name]来获取属性值。对于为数值的值,通过⑥计算其基于px的数值值。

文章来源:http://jljlpch.javaeye.com/category/37744

Tags:Jquery 源码 分析

编辑录入:爽爽 [复制链接] [打 印]
赞助商链接