美文网首页
jQuery源码解析之width()

jQuery源码解析之width()

作者: 小进进不将就 | 来源:发表于2019-05-13 22:21 被阅读0次

    一、在讲之前,先弄清 boxSizing 属性
    (1)box-sizing 是默认值 "content-box"

    <body>
    <script src="jQuery.js"></script>
    <div id="pTwo"
         style="width: 55px;
         border:1px red solid;">这是divTwo</div>
    <script>
      $("#pTwo").width() //55
    </script>
    </body>
    

    $().width()的值是 55

    (2)box-sizing 是 "border-box"

    <div id="pTwo"
         style="width: 55px;
         box-sizing: border-box;
         border:1px red solid;">这是divTwo</div>
    

    $().width()的值是 53

    因为 border-box 是包括 border、padding、content 的,而 content-box 只包括 content。
    可想而知,jQuery的$().width() 中也包含了对 borderBox 的判断。

    • 注意下div标签的默认值

    二、$().width()
    作用:
    获取目标元素的宽度

    源码:

      //源码7033行
      //$.each(obj,callback(index,item){})
      jQuery.each( [ "height", "width" ], function( i, dimension ) {
        //i:0 dimension:height
        //i:1 dimension:width
    
        //cssHooks是用来定义style方法的
        jQuery.cssHooks[ dimension ] = {
          //读
          //$().width()
          //参数:elem:目标DOM元素/computed:true/extra:"content"
          get: function( elem, computed, extra ) {
            console.log(elem, computed, extra,'extra7040')
            if ( computed ) {
    
              // 某些元素是有尺寸的信息的,如果我们隐式地显示它们,前提是它必须有一个display值
              // Certain elements can have dimension info if we invisibly show them
              // but it must have a current display style that would benefit
    
              // 上面这句话的意思是,某个元素用display:none,将它从页面上去掉了,此时是获取不到它的宽度的
              // 如果要获取它的宽度的话,需要隐式地显示它们,比如display:absolute,visible:hidden
              // 然后再去获取它的宽度
    
              // block:false
              // none:true
              // rdisplayswap的作用是检测 none和table开头的
              return rdisplayswap.test( jQuery.css( elem, "display" ) ) &&
    
              // 兼容性的考虑,直接看 getWidthOrHeight
    
              // Support: Safari 8+
              // Table columns in Safari have non-zero offsetWidth & zero
              // getBoundingClientRect().width unless display is changed.
              // Support: IE <=11 only
              // Running getBoundingClientRect on a disconnected node
              // in IE throws an error.
    
              // display为none的话,elem.getBoundingClientRect().width=0
              // elem.getClientRects() 返回CSS边框的集合
              // https://developer.mozilla.org/zh-CN/docs/Web/API/Element/getClientRects
              ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ?
                swap( elem, cssShow, function() {
                  return getWidthOrHeight( elem, dimension, extra );
                } ) :
                //$().width()情况
                //dimension:width/extra:"content"
                getWidthOrHeight( elem, dimension, extra );
            }
          },
       };
    } );
    

    解析:
    (1)box-sizing 是默认值,并且 display 不为 none
    rdisplayswap
    作用:
    检测目标元素的display属性的值 是否为none或以table开头

        // 检测 display 的值是否为 none 或以 table 开头
        // Swappable if display is none or starts with table
        // 除了 "table", "table-cell", "table-caption"
        // except "table", "table-cell", or "table-caption"
        // display 的值,请访问 https://developer.mozilla.org/en-US/docs/CSS/display
        // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display
        // 源码6698行
    var rdisplayswap = /^(none|table(?!-c[ea]).+)/,
    

    如果displaynone的话,就会调用swap()方法,反之,就直接调用getWidthOrHeight()方法

    getWidthOrHeight()
    作用:
    获取widthheight的值

     //获取 width 或 height
      //dimension:width/extra:"content"
      //源码6823行
      function getWidthOrHeight( elem, dimension, extra ) {
    
        // Start with computed style
        var styles = getStyles( elem ),
          val = curCSS( elem, dimension, styles ),
          //判断 box-sizing 的值是否 是 border-box
          //如果启用了 box-sizing,js 的 width 是会算上 margin、border、padding的
          //如果不启用的话,js 的 width 只会算 content
          //jQuery 的 width 自始至终都是算的 content
          isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box",
    
          valueIsBorderBox = isBorderBox;
    
        //火狐兼容性处理,可不看
        // Support: Firefox <=54
        // Return a confounding non-pixel value or feign ignorance, as appropriate.
        if ( rnumnonpx.test( val ) ) {
          if ( !extra ) {
            return val;
          }
          val = "auto";
        }
    
        // 通过getComputedStyle检查style属性,并返回可靠的style属性,这样可以防止浏览器返回不可靠的值
        // Check for style in case a browser which returns unreliable values
        // for getComputedStyle silently falls back to the reliable elem.style
        valueIsBorderBox = valueIsBorderBox &&
          ( support.boxSizingReliable() || val === elem.style[ dimension ] );
        console.log(valueIsBorderBox,'valueIsBorderBox6853')
        // Fall back to offsetWidth/offsetHeight when value is "auto"
        // This happens for inline elements with no explicit setting (gh-3571)
        // Support: Android <=4.1 - 4.3 only
        // Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602)
        if ( val === "auto" ||
          !parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) {
    
          val = elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ];
          console.log(val,'val6862')
          // offsetWidth/offsetHeight provide border-box values
          valueIsBorderBox = true;
        }
        // Normalize "" and auto
        // 55px
        val = parseFloat( val ) || 0;
        console.log(val,extra,'val6869')
        // Adjust for the element's box model
        return ( val +
          boxModelAdjustment(
            //DOM节点
            elem,
            //width
            dimension,
            //content
            extra || ( isBorderBox ? "border" : "content" ),
            //true/false
            valueIsBorderBox,
            //styles
            styles,
            //55
            // Provide the current computed size to request scroll gutter calculation (gh-3589)
            val
          )
        ) + "px";
      }
    

    getWidthOrHeight() 里面有好多方法,我们一一来解析:

    getStyles( elem )
    作用:
    获取该 DOM 元素的所有 css 属性的值

      //获取该DOM元素的所有css属性的值
      //源码6501行
      var getStyles = function( elem ) {
        // 兼容性处理,旨在拿到正确的view
        // Support: IE <=11 only, Firefox <=30 (#15098, #14150)
        // IE throws on elements created in popups
        // FF meanwhile throws on frame elements through "defaultView.getComputedStyle"
        var view = elem.ownerDocument.defaultView;
    
        if ( !view || !view.opener ) {
          view = window;
        }
        //获取所有CSS属性的值
        return view.getComputedStyle( elem );
      };
    

    可以看到,本质是调用了getComputedStyle()方法。

    curCSS( elem, dimension, styles )
    作用:
    获取元素的当前属性的值

    // 获取元素的当前属性的值
      // elem, "position"
      // elem,width,styles
      // 源码6609行
      function curCSS( elem, name, computed ) {
    
        var width, minWidth, maxWidth, ret,
    
          // Support: Firefox 51+
          // Retrieving style before computed somehow
          // fixes an issue with getting wrong values
          // on detached elements
          style = elem.style;
        //获取elem所有的样式属性
        computed = computed || getStyles( elem );
        // console.log(computed,'computed6621')
        // getPropertyValue is needed for:
        //   .css('filter') (IE 9 only, #12537)
        //   .css('--customProperty) (#3144)
        if ( computed ) {
          //返回元素的属性的当前值
          //position:static
          //top:0px
          //left:0px
          ret = computed.getPropertyValue( name ) || computed[ name ];
          console.log(ret,'ret6627')
          //如果目标属性值为空并且目标元素不在目标元素所在的文档内(感觉这种情况好奇怪)
          if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) {
            //使用jQuery.style方法来获取目标元素的属性值
            ret = jQuery.style( elem, name );
          }
    
          // A tribute to the "awesome hack by Dean Edwards"
          // Android Browser returns percentage for some values,
          // but width seems to be reliably pixels.
          // This is against the CSSOM draft spec:
          // https://drafts.csswg.org/cssom/#resolved-values
          //当属性设置成数值时,安卓浏览器会返回一些百分比,但是宽度是像素显示的
          //这违反了CSSOM草案规范
          //所以以下方法是修复不规范的width属性的
          if ( !support.pixelBoxStyles() && rnumnonpx.test( ret ) && rboxStyle.test( name ) ) {
    
            // Remember the original values
            width = style.width;
            minWidth = style.minWidth;
            maxWidth = style.maxWidth;
    
            // Put in the new values to get a computed value out
            style.minWidth = style.maxWidth = style.width = ret;
            ret = computed.width;
    
            // Revert the changed values
            style.width = width;
            style.minWidth = minWidth;
            style.maxWidth = maxWidth;
          }
        }
    
        return ret !== undefined ?
          // 兼容性,IE下返回的zIndex的值是数字,
          // 而使用jQuery获取的属性都是返回字符串
          // Support: IE <=9 - 11 only
          // IE returns zIndex value as an integer.
          ret + "" :
          ret;
      }
    

    可以看到,curCSS本质是调用了computed.getPropertyValue( name )方法,也就是说我们可以这样去获取目标元素的属性值:

    let a=document.getElementById("pTwo")
    a.ownerDocument.defaultView.getComputedStyle(a).getPropertyValue('width')
    //55px
    

    目标元素的所属 view,调用getComputedStyle()方法,获取目标元素的所有 CSS 属性,再调用getPropertyValue('width'),获取目标width的属性值,为 55px

    注意:无论box-sizing的值是border-box还是content-box,上面的方法获取的width值都是55px,这是不符合 CSS3 盒子模型的,所以 jQuery 拿到该值后,还会继续处理。

    boxModelAdjustment
    因为这里讨论的是情况一,所以boxModelAdjustment()会直接返回 0

    综上:当box-sizing 是默认值,并且 display 不为 none时,返回的width是:

    parseFloat(a.ownerDocument.defaultView.getComputedStyle(a).getPropertyValue('width')) //55
    

    (2)box-sizing 值为 border-box

    <div id="pTwo"
         style="width: 55px;
         margin-left:2px;
         padding-left: 2px;
         box-sizing: border-box;
         border:1px red solid;">这是divTwo</div>
    
    $("#pTwo").width() //51
    document.getElementById("pTwo").style.width //55px
    

    可以看到,原生 js 获取 width 是不遵循 CSS3 盒子规范的。

    borderBox 的判断在getWidthOrHeight()方法中,直接看过去:

      //获取 width 或 height
      //dimension:width/extra:"content"
      //源码6823行
      function getWidthOrHeight( elem, dimension, extra ) {
        xxx
        ...
        var styles = getStyles( elem ),
          //true
          isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box",
          //true
          valueIsBorderBox = isBorderBox;
        xxx
        ...
        valueIsBorderBox = valueIsBorderBox &&
          //val值是通过a.ownerDocument.defaultView.getComputedStyle(a).getPropertyValue('width')得出的
          //但又通过js原生的style.width来取值并与val相比较
          ( support.boxSizingReliable() || val === elem.style[ dimension ] );
        console.log(val === elem.style[ dimension ],'valueIsBorderBox6853')
     
        // 55
        val = parseFloat( val ) || 0;
        // Adjust for the element's box model
        return ( val +
          //borderBox走这里
          boxModelAdjustment(
            //DOM节点
            elem,
            //width
            dimension,
            //content
            extra || ( isBorderBox ? "border" : "content" ),
            //true/false
            valueIsBorderBox,
            //styles
            styles,
            //55
            // Provide the current computed size to request scroll gutter calculation (gh-3589)
            val
          )
        ) + "px";
      }
    

    boxModelAdjustment():
    作用:
    集中处理borderBox的情况

    //参数说明:
      //elem:DOM节点/dimension:width/box:content/isBorderBox:true/false/styles:styles/computedVal:55
      //源码6758行
      function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) {
        var i = dimension === "width" ? 1 : 0,
          extra = 0,
          delta = 0;
    
        // 如果 boxSizing 的属性值,而不是 borderBox 的话,就直接返回 0
        // Adjustment may not be necessary
        if ( box === ( isBorderBox ? "border" : "content" ) ) {
          console.log('content1111','content6768')
          return 0;
        }
        //小技巧
        //i 的初始值是 0/1
        //然后 cssExpand = [ "Top", "Right", "Bottom", "Left" ]
        for ( ; i < 4; i += 2 ) {
    
          // Both box models exclude margin
          if ( box === "margin" ) {
            //var cssExpand = [ "Top", "Right", "Bottom", "Left" ];
            //width 的话,就是 marginRight/marginLeft
            //height 的话,就是 marginTop/marginBottom
            //jQuery.css( elem, box + cssExpand[ i ], true, styles ) 的意思就是
            //返回 marginRight/marginLeft/marginTop/marginBottom 的数字,并给 delta 加上
            delta += jQuery.css( elem, box + cssExpand[ i ], true, styles );
          }
    
          // If we get here with a content-box, we're seeking "padding" or "border" or "margin"
          // 如果不是 borderBox 的话
          if ( !isBorderBox ) {
    
            // Add padding
            // 添加 padding-xxx
            delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
    
            // For "border" or "margin", add border
            if ( box !== "padding" ) {
              delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
    
              // But still keep track of it otherwise
            } else {
              extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
            }
    
            // If we get here with a border-box (content + padding + border), we're seeking "content" or
            // "padding" or "margin"
          } else {
            // 去掉 padding
            // For "content", subtract padding
            if ( box === "content" ) {
              //width,去掉paddingLeft,paddingRight的值
              delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
            }
    
            // For "content" or "padding", subtract border
            // 去掉 borderXXXWidth
            if ( box !== "margin" ) {
              //width,去掉borderLeftWidth,borderRightWidth的值
              delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
            }
          }
        }
    
        // Account for positive content-box scroll gutter when requested by providing computedVal
        if ( !isBorderBox && computedVal >= 0 ) {
    
          // offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border
          // Assuming integer scroll gutter, subtract the rest and round down
          delta += Math.max( 0, Math.ceil(
            //就是将dimension的首字母做个大写
            elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] -
            computedVal -
            delta -
            extra -
            0.5
          ) );
        }
    
        return delta;
      }
    

    可以看到,isBorderBox 为 true 的话,会执行下面两段代码:

    if ( box === "content" ) {
       //width,去掉paddingLeft,paddingRight的值
       delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
    }
    
     if ( box !== "margin" ) {
        //width,去掉borderLeftWidth,borderRightWidth的值
        delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
    }
    

    去除了paddingLeftpaddingRightborderLeftWidthborderRightWidth,并最终返回值

    二、$().width(xxx)
    作用:
    设置目标元素的宽度

    源码:

    
      //源码7033行
      //$.each(obj,callback(index,item){})
      jQuery.each( [ "height", "width" ], function( i, dimension ) {
        //i:0 dimension:height
        //i:1 dimension:width
    
        //cssHooks是用来定义style方法的
        jQuery.cssHooks[ dimension ] = {
          //写
          //$().width(55)
          //elem:DOM节点,value:55,extra:content
          set: function( elem, value, extra ) {
            var matches,
              styles = getStyles( elem ),
              isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box",
              //-4
              subtract = extra && boxModelAdjustment(
                elem,
                dimension,
                extra,
                isBorderBox,
                styles
              );
    
            // 如果是 borderBox 的话,通过 offset 计算的尺寸是不准的,
            // 所以要假设成 content-box 来获取 border 和 padding
            // Account for unreliable border-box dimensions by comparing offset* to computed and
            // faking a content-box to get border and padding (gh-3699)
            //true true 'static'
            //调整 subtract
            if ( isBorderBox && support.scrollboxSize() === styles.position ) {
              subtract -= Math.ceil(
                elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] -
                parseFloat( styles[ dimension ] ) -
                boxModelAdjustment( elem, dimension, "border", false, styles ) -
                0.5
              );
              console.log(subtract,'subtract7169')
            }
            // 如果需要进行值调整,则转换为像素
            // Convert to pixels if value adjustment is needed
            //如果是 borderBox 并且 value 的单位不是 px,则会转换成像素
            if ( subtract && ( matches = rcssNum.exec( value ) ) &&
              ( matches[ 3 ] || "px" ) !== "px" ) {
              elem.style[ dimension ] = value;
              value = jQuery.css( elem, dimension );
            }
            //59px
            return setPositiveNumber( elem, value, subtract );
          }
      };
    } );
    

    解析:
    (1)整体上看,实际上两个 if ,最后再 return 一个setPositiveNumber()方法

    (2)注意subtract,如果有 borderBox 属性,并且 borderWidth、padding 有值的话,subtract 一般为负数,比如下面的例子,subtract = -4

    <div id="pTwo"
         style="width: 55px;
         margin-left:2px;
         padding-left: 2px;
         box-sizing: border-box;
         /*box-sizing: content-box;*/
         /*display: none;*/
         border:1px red solid;">
      这是divTwo
    </div>
    
    $("#pTwo").width(55)
    

    反之则会是 0

    (3)两个 if 我试了下,都会去执行,所以直接看的setPositiveNumber ()

    setPositiveNumber:
    作用:
    设置真正的 width 值

      function setPositiveNumber( elem, value, subtract ) {
        // 标准化相对值
        // Any relative (+/-) values have already been
        // normalized at this point
    
        //[
        // "55px",
        // undefined,
        // "55",
        // "px",
        // index: 0,
        // input: "55px",
        // groups: undefined,
        // index: 0
        // input: "55px"
        // ]
        var matches = rcssNum.exec( value );
        console.log(matches,( subtract || 0 ),'matches6760')
        return matches ?
          //(0,55-(-4))+'px'
          //Math.max(a,b) 返回两个指定的数中带有较大的值的那个数
          // Guard against undefined "subtract", e.g., when used as in cssHooks
          Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) :
          value;
      }
    

    如果是 borderBox,width 会设置成 59px(虽然表面上开发者设置的是$("#pTwo").width(55)),反之,则是 55px

    总结:
    1、$().width()
    (1)不是borderBox
    $().width()=parseFloat(elem.ownerDocument.defaultView.getComputedStyle(elem).getPropertyValue('width'))

    (2)是borderBox()
    在(1)的基础上执行boxModelAdjustment()方法,去除 borderWidth、padding

    2、$().width(xxx)
    (1)不是borderBox
    width=xxx
    (2)是borderBox
    width=xxx+ setPositiveNumber()


    (完)

    相关文章

      网友评论

          本文标题:jQuery源码解析之width()

          本文链接:https://www.haomeiwen.com/subject/boncoqtx.html