一、在讲之前,先弄清 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() //51document.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()


(完)