jQuery 源碼系列(十七)css 相關操做

歡迎來個人專欄查看系列文章。javascript

樣式操做也是 jQuery 比較經常使用的一個操做,就我本人而言,這個 css 函數用多了,感受本身有點傻乎乎的,主要仍是本身不瞭解 js 中 css 的真正含義。css

不過如今不怕了。html

開始以前,先拋開 jQuery,咱們來看看一個有趣的面試題(聽說是一道微信面試題)。html5

一道頗有深度的面試題

用原生的 js 得到一個 HTML 元素的 background-color,固然,這只是一個引入,爲何不是 color,爲何不是 font-size?java

css 渲染的優先級

寫過 css 樣式的同窗都知道,css 是有優先級區分的,!important優先級最高,內聯優先級其次,id,類和僞類,元素和僞元素。優先級會按照順序依次比較,同一級別相同則看下一級別,優先級相同,後面會覆蓋前面。node

好比,就像理科生排成績,規定 總分 > 數學 > 語文 > 英語,若是 A,B 兩人總分同樣,就看數學成績誰高,數學成績同樣,就看語文成績誰高,以此類推。git

記得在一家公司實習的時候(初學者),那個時候修改網站的主頁樣式,因爲找不到樣式對應,就大量使用 !important,並把全部樣式都寫在樣式標的最後,估計,後面接手的人要氣炸吧。github

問題來了,對於任意的一個 elem,DIV 也好,P 也好,都只會保留一個 style 樣式表,通常能夠經過 getComputedStyle 函數或 IE 的 currentStyle 參數活動(萬惡的 IE,如今 jQuery 已經不支持低版本 IE,連淘寶都不支持 IE8 了)。不管這個樣式表是經過前面哪一個優先級得到的,但它必定是惟一且只有一個。並且它仍是隻讀的屬性,因此經過 JS 來改變樣式,若是不能修改 css 文件的狀況下,只能採用內聯。面試

內聯有兩種,一種是在 elem 上添加一個 style 屬性,還有一種是在 HTMl 新建一個 <style> 標籤,很顯然,第一種貌似更符合 js 的特性,由於找到那個 elem 並不困難,並且還有一個 elem.style 可使用。chrome

js 獲取元素樣式

elem.style 並非萬能的,也有很大的侷限性。通常的思路就是,先看看有沒有內聯,若是沒有內聯,就走通常流程。

內聯值和 getComputedStyle 的值會不同嗎,我本身作過測試,在 chrome 下面,內聯值和樣式表 getComputedStyle 的值是同樣的,並且,當內聯值改變,樣式表也會跟着改變,除非在 css 文件中有比內聯優先級還高的 important,這個時候內聯是不起做用的,只能經過樣式表來獲取。

var dom = document.getElementById("test");
var styleList = getComputedStyle(dom);
styleList.color; // 'black'

dom.style.color = 'red';

// 會自動刷新的
styleList.color; // 'red'

styleList.color 不變的時候,就知道可能有 important 樣式的存在,也能夠做爲判斷 important 的一個標準。

樣式表有 font-size,有人寫成駝峯 fontSize,這能夠理解,統一一下就好啦。因爲 elem.stylegetComputedStyle 使用的是駝峯寫法(實際上即便用破折法去獲取也是能夠獲得的)要藉助下面這兩個函數來:

// 變成駝峯
function camel(str){
  return str.replace(/-(\w)/g, function(m0, m1){
    return m1.toUpperCase();
  })
}

// 變成破折
function dashes(str){
  return str.replace(/[A-Z]/g, function(m0){
    return '-' + m0.toLowerCase();
  })
}

所以:

function getStyle(elem, name){
  var value, styles, sty;
  if(!name){ // 只有一個參數,直接返回吧
    return false;
  }
  if(elem.nodeType !==1 && elem.nodeType !== 9 && elem.nodeType !== 11){
    // 確定不是 HTMLElement
    return false;
  }

  name = camel(name); //將 name 轉變成駝峯
  value = elem.style[name];
  if(value){
    return value;
  }
  styles = (sty = elem.currentStyle) ? sty :
    (sty = document.defaultView.getComputedStyle) ? sty(elem) : {};
  return (sty = styles[name]) ? sty : false;
}

// 測試,無視駝峯和破折
getStyle(dom, 'font-size'); // '16px'
getStyle(dom, 'fontSize'); // '16px'

這道題目仍是頗有意思的,固然,答案還不止,還能夠繼續優化,這樣能夠給面試官好感,連接

由於咱們測的是 background-color,這個屬性很特別,當它是 inherit表示繼承父類,transparent 表示透明,也該爲 flase,看:

function fixColor(elem){
  var color = getStyle(elem, 'background-color');
  if(color){
    if(color == 'transparent' || color == 'rgba(0, 0, 0, 0)')
      return false;
    else if(getStyle(elem, 'opacity') == '0'){
      return false; // 透明
    }
    else if(getStyle(elem, 'display') == 'none'){
      return false; // none
    }
    else if(getStyle(elem, 'visibility') == 'hidden'){
      return false; // 隱藏
    }
  }
  if(color == 'inherit'){ // 繼承父
    return elem.parentNode ? fixColor(elem.parentNode) : false;
  }
  return color;
}

愈來愈有意思了。若是是 html5 中的 canvas,貌似又要去找。

fn.css() 源碼

好吧,步入正題了。我想,若是仔細看了前面面試題的同窗,也該對原生 js 操做 css 作法徹底懂了,jQuery 的思路也徹底是如此,只是多了更多的兼容考慮:

jQuery.fn.extend( {
  css: function( name, value ) {
    return access( this, function( elem, name, value ) {
      var styles, len,
        map = {},
        i = 0;

      if ( jQuery.isArray( name ) ) {
        styles = getStyles( elem );
        len = name.length;

        for ( ; i < len; i++ ) {
          map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles );
        }

        return map;
      }

      return value !== undefined ?
        jQuery.style( elem, name, value ) :
        jQuery.css( elem, name );
    }, name, value, arguments.length > 1 );
  }
} );

css 也是有 set 和 get 的,可是它們並不在 fn.css 函數裏處理,set 對應 jQuery.style,get 對應 jQuery.css

在此以前,先來看一個很熟悉的函數:

var getStyles = function( elem ) {

    // 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;
    }

    return view.getComputedStyle( elem );
  };

jQuery 已經不支持 currentStyle,也就是拋棄了低版本 IE 瀏覽器。

jQuery.extend( {
  camelCase: function( string ) {
    return string.replace( /^-ms-/, "ms-" ).replace( /-([a-z])/g, function( all, letter ) {
      return letter.toUpperCase();
    } );
  }
} );

camelCase 也是一個很熟悉的函數(ms 是有其餘用途的)。

jQuery.extend( {
  style: function( elem, name, value, extra ) {

    // 處理特殊狀況 !elem.style 能夠借鑑
    if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
      return;
    }

    // Make sure that we're working with the right name
    var ret, type, hooks,
      origName = jQuery.camelCase( name ),
      style = elem.style;

    name = jQuery.cssProps[ origName ] ||
      ( jQuery.cssProps[ origName ] = vendorPropName( origName ) || origName );

    // hooks
    hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];

    // Check if we're setting a value
    if ( value !== undefined ) {
      type = typeof value;

      // Convert "+=" or "-=" to relative numbers (#7345)
      if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) {
        value = adjustCSS( elem, name, ret );

        // Fixes bug #9237
        type = "number";
      }

      // Make sure that null and NaN values aren't set (#7116)
      if ( value == null || value !== value ) {
        return;
      }

      // If a number was passed in, add the unit (except for certain CSS properties)
      if ( type === "number" ) {
        value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" );
      }

      // background-* props affect original clone's values
      if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) {
        style[ name ] = "inherit";
      }

      // If a hook was provided, use that value, otherwise just set the specified value
      if ( !hooks || !( "set" in hooks ) ||
        ( value = hooks.set( elem, value, extra ) ) !== undefined ) {

        style[ name ] = value;
      }

    } else {

      // If a hook was provided get the non-computed value from there
      if ( hooks && "get" in hooks &&
        ( ret = hooks.get( elem, false, extra ) ) !== undefined ) {

        return ret;
      }

      // 千辛萬苦,終於等到你
      return style[ name ];
    }
  }
} );
jQuery.extend( {
css: function( elem, name, extra, styles ) {
    var val, num, hooks,
      origName = jQuery.camelCase( name );

    // Make sure that we're working with the right name
    name = jQuery.cssProps[ origName ] ||
      ( jQuery.cssProps[ origName ] = vendorPropName( origName ) || origName );

    // Try prefixed name followed by the unprefixed name
    hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];

    // If a hook was provided get the computed value from there
    if ( hooks && "get" in hooks ) {
      val = hooks.get( elem, true, extra );
    }

    // Otherwise, if a way to get the computed value exists, use that
    if ( val === undefined ) {
      val = curCSS( elem, name, styles );
    }

    // Convert "normal" to computed value
    if ( val === "normal" && name in cssNormalTransform ) {
      val = cssNormalTransform[ name ];
    }

    // Make numeric if forced or a qualifier was provided and val looks numeric
    if ( extra === "" || extra ) {
      num = parseFloat( val );
      return extra === true || isFinite( num ) ? num || 0 : val;
    }
    return val;
  }
} );

總結

若是你對 css 看起來很吃力,請把那個微信面試題再仔細閱讀一下吧。

參考

解密jQuery內核 樣式操做
CSS並不簡單--一道微信面試題的實踐
微信面試題-獲取元素的最終background-color

本文在 github 上的源碼地址,歡迎來 star。

歡迎來個人博客交流。

相關文章
相關標籤/搜索