• jQuery源码学习笔记四


    这一节重点讲jQuery对样式的处理,虽然IE同时拥有style,currentStyle与runtimeStyle,但没有一个能获取used value,这是原罪。直接导致的结果是处理样式,就是处理IE的非精确值问题,有时能否获得值也是个大问题。jQuery与其他类库一样,在这方面下了很大工夫,最终在这方面打败其他类库。

    //@author  司徒正美|なさみ|cheng http://www.cnblogs.com/rubylouvre/  All rights reserved
          //这里的代码写得很垃圾啊,不过这样写肯定有它的道理,既然版本号已经发展1.32,那当然是那么兼容以前的代码设计的
          className: {
            //顺便一提className与arguments一样是个类数组
            add: function( elem, classNames ) {
              jQuery.each((classNames || "").split(/\s+/), function(i, className){
                if ( elem.nodeType == 1 && !jQuery.className.has( elem.className, className ) )
                  elem.className += (elem.className ? " " : "") + className;
              });
            },
    
            // internal only, use removeClass("class")
            remove: function( elem, classNames ) {
              //觉得什么都用自定义函数解决效率太低了,更何况jQuery.grep的逻辑如此复杂
              if (elem.nodeType == 1)
                elem.className = classNames !== undefined ?
                jQuery.grep(elem.className.split(/\s+/), function(className){
                return !jQuery.className.has( classNames, className );
              }).join(" ") :
                "";
            },
    
            // internal only, use hasClass("class")
            has: function( elem, className ) {
              return elem && jQuery.inArray( className, (elem.className || elem).toString().split(/\s+/) ) > -1;
            }
          },
          //这是一个非常重要的内部函数,用于精确获取样式值
          // A method for quickly swapping in/out CSS properties to get correct calculations
          swap: function( elem, options, callback ) {
            var old = {};//备份用
            // Remember the old values, and insert the new ones
            for ( var name in options ) {
              old[ name ] = elem.style[ name ];
              elem.style[ name ] = options[ name ];
            }
            //交换之后调用测试函数
            callback.call( elem );
            //测试完后还原
            // Revert the old values
            for ( var name in options )
              elem.style[ name ] = old[ name ];
          },
          //jQuery对象也有一个与它同名的方法,但这不是简单的代理
          //不过实际路线图为原型的css→原型的attr→静态的attr→静态的css
          //最后是curCSS,这才是真身
          css: function( elem, name, force, extra ) {
            //处理宽与高,因为IE不能正确返回以px为单位的精确值
            if ( name == "width" || name == "height" ) {
              //props用于swap,一个聪明的手段,值得学习
              var val, props = { position: "absolute", visibility: "hidden", display:"block" }, which = name == "width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ];
    
              function getWH() {
                //Ext与Prototypet等类库也是这样实现
                //在标准模式中,offsetWidth是包含padding,borderWidth与width
                //在怪癖模式下,offsetWidth等于width,而width是包含padding与borderWidth
                //offsetHeight同理
                val = name == "width" ? elem.offsetWidth : elem.offsetHeight;
    
                if ( extra === "border" )
                  return;
                jQuery.each( which, function() {
                  if ( !extra )
                  //求出paddingLeft与paddingRight之和,或paddingTop与paddingBottom之和,
                  //然后作为减数,去减offsetWidth或offsetHeight
                    val -= parseFloat(jQuery.curCSS( elem, "padding" + this, true)) || 0;
                  if ( extra === "margin" )
    
                  val += parseFloat(jQuery.curCSS( elem, "margin" + this, true)) || 0;
                else
                  val -= parseFloat(jQuery.curCSS( elem, "border" + this + "Width", true)) || 0;
              });
            }
            if ( elem.offsetWidth !== 0 )
              getWH();
            else
            //如果display:none就求不出offsetWidht与offsetHeight,swap一下
              jQuery.swap( elem, props, getWH );
            return Math.max(0, Math.round(val));
          }
          //再调用jQuery.curCSS进行深加工
          return jQuery.curCSS( elem, name, force );
        },
    
        curCSS: function( elem, name, force ) {
          var ret, style = elem.style;
    
          // We need to handle opacity special in IE
          if ( name == "opacity" && !jQuery.support.opacity ) {
            ret = jQuery.attr( style, "opacity" );
    
            return ret == "" ?
              "1" :
              ret;
          }
    
          // Make sure we're using the right name for getting the float value
          if ( name.match( /float/i ) )
            name = styleFloat;
    
          if ( !force && style && style[ name ] )
            ret = style[ name ];//缓存结果
    
          else if ( defaultView.getComputedStyle ) {
            //标准浏览器
            // Only "float" is needed here
            if ( name.match( /float/i ) )
              name = "float";//把cssFloat转换为float
            //把驼峰风格转换为连字符风格
            name = name.replace( /([A-Z])/g, "-$1" ).toLowerCase();
    
            var computedStyle = defaultView.getComputedStyle( elem, null );
            if ( computedStyle )
              ret = computedStyle.getPropertyValue( name );
    
            // We should always get a number back from opacity
            if ( name == "opacity" && ret == "" )
              ret = "1";//把opacity设置成1
    
          } else if ( elem.currentStyle ) {
            //IE浏览器部分
            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
            //将不是以px为单位的计算值全部转换为以px为单位,用到 Dean Edwards(Base2类库的作者)的hack
            //网上有文章讲解这hach,这里不重复
            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;
        },
        attr: function( elem, name, value ) {
          // 文本,注释节点不处理
          if (!elem || elem.nodeType == 3 || elem.nodeType == 8)
            return undefined;
          //不处理xml文档的
          var notxml = !jQuery.isXMLDoc( elem ),
          //是读方法还是写方法
          set = value !== undefined;
    
          // Try to normalize/fix the name
          //兼容处理,
          //jQuery.props = {
          //"for": "htmlFor",
          //"class": "className",
          //"float": styleFloat,
          //cssFloat: styleFloat,
          //styleFloat: styleFloat,
          //readonly: "readOnly",
          //maxlength: "maxLength",
          //cellspacing: "cellSpacing",
          //rowspan: "rowSpan",
          //tabindex: "tabIndex"
          //};
          name = notxml && jQuery.props[ name ] || name;
    
          // Only do all the following if this is a node (faster for style)
          // IE elem.getAttribute passes even for style
          if ( elem.tagName ) {
    
            // These attributes require special treatment
            var special = /href|src|style/.test( name );
    
            // Safari mis-reports the default selected property of a hidden option
            // Accessing the parent's selectedIndex property fixes it
            //修正无法取得selected正确值的bug
            if ( name == "selected" && elem.parentNode )
              elem.parentNode.selectedIndex;
    
            // If applicable, access the attribute via the DOM 0 way
            if ( name in elem && notxml && !special ) {
              if ( set ){
                //不允许改写type的值
                // We can't allow the type property to be changed (since it causes problems in IE)
                if ( name == "type" && jQuery.nodeName( elem, "input" ) && elem.parentNode )
                  throw "type property can't be changed";
    
                elem[ name ] = value;
              }
    
              // browsers index elements by id/name on forms, give priority to attributes.
              if( jQuery.nodeName( elem, "form" ) && elem.getAttributeNode(name) )
              //getAttributeNode() 方法的作用是:通过指定的名称获取当前元素中的属性节点。
                return elem.getAttributeNode( name ).nodeValue;
    
              // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set
              // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
              //IE只能tabIndex
              //标准浏览器用tabindex
              if ( name == "tabIndex" ) {
                var attributeNode = elem.getAttributeNode( "tabIndex" );
                return attributeNode && attributeNode.specified
                  ? attributeNode.value
                : elem.nodeName.match(/(button|input|object|select|textarea)/i)
                  ? 0
                : elem.nodeName.match(/^(a|area)$/i) && elem.href
                  ? 0
                : undefined;
              }
    
              return elem[ name ];
            }
    
            if ( !jQuery.support.style && notxml &&  name == "style" )
              return jQuery.attr( elem.style, "cssText", value );
    
            if ( set )
            // convert the value to a string (all browsers do this but IE) see #1070
              elem.setAttribute( name, "" + value );
            //IE的getAttribute支持第二个参数,可以为 0,1,2
            //0 是默认;1 区分属性的大小写;2取出源代码中的原字符串值。
            //IE 在取 href 的时候默认拿出来的是绝对路径,加参数2得到我们所需要的相对路径。
            var attr = !jQuery.support.hrefNormalized && notxml && special
            // Some attributes require a special call on IE
              ? elem.getAttribute( name, 2 )
            : elem.getAttribute( name );
    
            // Non-existent attributes return null, we normalize to undefined
            return attr === null ? undefined : attr;
          }
    
          // elem is actually elem.style ... set the style
    
          // IE uses filters for opacity
          if ( !jQuery.support.opacity && name == "opacity" ) {
            if ( set ) {
              // IE has trouble with opacity if it does not have layout
              // Force it by setting the zoom level
              //IE7中滤镜(filter)必须获得hasLayout才能生效,我们用zoom这个IE私有属性让其获得hasLayout
              elem.zoom = 1;
    
              // Set the alpha filter to set the opacity
              elem.filter = (elem.filter || "").replace( /alpha\([^)]*\)/, "" ) +
                (parseInt( value ) + '' == "NaN" ? "" : "alpha(opacity=" + value * 100 + ")");
            }
    
            return elem.filter && elem.filter.indexOf("opacity=") >= 0 ?
              (parseFloat( elem.filter.match(/opacity=([^)]*)/)[1] ) / 100) + '':
              "";
          }
          //获得其他属性,直接用DOM 0方法读写
          name = name.replace(/-([a-z])/ig, function(all, letter){
            return letter.toUpperCase();
          });
    
          if ( set )
            elem[ name ] = value;
    
          return elem[ name ];
        },
    

    其实在curCss与attr方法中还夹着一个clean方法,总觉得clean职责太多,里面分支繁缛,看得我头晕眼花……太凌乱,这方法应该分割成几个方法条理更清晰,效率更高。

          //把字符串转换为DOM元素的纯数组
          //这里的elems为字符串数组,将用文档碎片做转换
          clean: function( elems, context, fragment ) {
            context = context || document;
    
            // !context.createElement fails in IE with an error but returns typeof 'object'
            if ( typeof context.createElement === "undefined" )
              context = context.ownerDocument || context[0] && context[0].ownerDocument || document;
    
            // If a single string is passed in and it's a single tag
            // just do a createElement and skip the rest
    
            if ( !fragment && elems.length === 1 && typeof elems[0] === "string" ) {
              var match = /^<(\w+)\s*\/?>$/.exec(elems[0]);
              if ( match )
                return [ context.createElement( match[1] ) ];
            }
            //div是用于把字符串转换为DOM的
            var ret = [], scripts = [], div = context.createElement("div");
            jQuery.each(elems, function(i, elem){
              if ( typeof elem === "number" )
                elem += '';//转换为字符串
    
              if ( !elem )
                return;
    
              // Convert html string into DOM nodes
              if ( typeof elem === "string" ) {
                // Fix "XHTML"-style tags in all browsers
                //生成闭合的标签对,亦即把在XHTML中不合法的写法强制转换过来
                elem = elem.replace(/(<(\w+)[^>]*?)\/>/g, function(all, front, tag){
                  //但对于abbr|br|col|img|input|link|meta|param|hr|area|embed等元素不修改
                  return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i) ?
                    all :
                    front + "></" + tag + ">";
                });
    
                // Trim whitespace, otherwise indexOf won't work as expected
                //将“ <div> ”去掉两边的空白“<div>”,用于下面的indexOf
                var tags = elem.replace(/^\s+/, "").substring(0, 10).toLowerCase();
    
                var wrap =
                  // option or optgroup
                //option与optgroup的直接父元素一定是select
                  !tags.indexOf("<opt") &&
                  [ 1, "<select multiple='multiple'>", "</select>" ] ||
                  //legend的直接父元素一定是fieldset
                  !tags.indexOf("<leg") &&
                  [ 1, "<fieldset>", "</fieldset>" ] ||
                  //thead,tbody,tfoot,colgroup,caption的直接父元素一定是table
                tags.match(/^<(thead|tbody|tfoot|colg|cap)/) &&
                  [ 1, "<table>", "</table>" ] ||
                  //tr的直接父元素一定是tbody,
                  !tags.indexOf("<tr") &&
                  [ 2, "<table><tbody>", "</tbody></table>" ] ||
                  //<thead> matched above
                //td与th的直接父元素一定是tr
                (!tags.indexOf("<td") || !tags.indexOf("<th")) &&
                  [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ] ||
                  //col一定是colgroup
                  !tags.indexOf("<col") &&
                  [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ] ||
    
                  // IE can't serialize <link> and <script> tags normally
                  !jQuery.support.htmlSerialize &&
                  [ 1, "div<div>", "</div>" ] ||
    
                  [ 0, "", "" ];
    
                // Go to html and back, then peel off extra wrappers
                div.innerHTML = wrap[1] + elem + wrap[2];
    
                // Move to the right depth
                while ( wrap[0]-- )
                  div = div.lastChild;
                //IE会自动添加tbody,要特殊处理
                // Remove IE's autoinserted <tbody> from table fragments
                if ( !jQuery.support.tbody ) {
    
                  // String was a <table>, *may* have spurious <tbody>
                  var hasBody = /<tbody/i.test(elem),
                  tbody = !tags.indexOf("<table") && !hasBody ?
                    div.firstChild && div.firstChild.childNodes :
    
                    // String was a bare <thead> or <tfoot>
                  wrap[1] == "<table>" && !hasBody ?
                    div.childNodes :
                    [];
    
                  for ( var j = tbody.length - 1; j >= 0 ; --j )
                    if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length )
                      tbody[ j ].parentNode.removeChild( tbody[ j ] );
    
                }
    
                // IE completely kills leading whitespace when innerHTML is used
                if ( !jQuery.support.leadingWhitespace && /^\s/.test( elem ) )
                  div.insertBefore( context.createTextNode( elem.match(/^\s*/)[0] ), div.firstChild );
                //div中的所有节点都转换为数组
                elem = jQuery.makeArray( div.childNodes );
              }
    
              if ( elem.nodeType )
              //过滤非元素节点的节点
                ret.push( elem );
              else
              //把符合要求的节点加入ret中
                ret = jQuery.merge( ret, elem );
    
            });
    
            if ( fragment ) {
              for ( var i = 0; ret[i]; i++ ) {
                //处理script元素
                if ( jQuery.nodeName( ret[i], "script" ) && (!ret[i].type || ret[i].type.toLowerCase() === "text/javascript") ) {
                  scripts.push( ret[i].parentNode ? ret[i].parentNode.removeChild( ret[i] ) : ret[i] );
                } else {
                  if ( ret[i].nodeType === 1 )
                    ret.splice.apply( ret, [i + 1, 0].concat(jQuery.makeArray(ret[i].getElementsByTagName("script"))) );
                  fragment.appendChild( ret[i] );
                }
              }
    
              return scripts;
            }
    
            return ret;
          },
    
  • 相关阅读:
    看《长安十二时辰》可以了解哪些算法知识
    面试官,我会写二分查找法!对,没有 bug 的那种!
    毕业十年后,我忍不住出了一份程序员的高考试卷
    扫雷与算法:如何随机化的布雷(一)
    降维打击!为什么我认为数据结构与算法对前端开发很重要
    盖尔-沙普利算法告诉你,你的对象在哪里?
    这道算法题太太太太太简单啦
    有点难度,几道和「滑动窗口」有关的算法面试题
    几道和「黑洞照片」那种海量数据有关的算法问题
    LeetCode 上最难的链表算法题,没有之一!
  • 原文地址:https://www.cnblogs.com/rubylouvre/p/1607255.html
Copyright © 2020-2023  润新知