jQuery源码解析之width()

栏目: jQuery · 发布时间: 6年前

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

jQuery源码解析之width()

一、在讲之前,先弄清 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 标签的默认值

jQuery源码解析之width()

二、 $() .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()

(完)


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Natural Language Processing with Python

Natural Language Processing with Python

Steven Bird、Ewan Klein、Edward Loper / O'Reilly Media / 2009-7-10 / USD 44.99

This book offers a highly accessible introduction to Natural Language Processing, the field that underpins a variety of language technologies, ranging from predictive text and email filtering to autom......一起来看看 《Natural Language Processing with Python》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

MD5 加密
MD5 加密

MD5 加密工具