JQuery3.1.1原始碼解讀(十八)【class】
眼看 jQuery 的原始碼就快到頭了,後面還有幾個重要的內容,包括 ajax 和動畫操作,加油把它們看完,百度前端學院的新一批課程也開始了。百度前端學院。
class 的的操作應該算是比較愉快的,因為內容不是很多,或者說,內容涉及到的原生操作不是很大,就一個 className 或 getAttribute,主要還是來看下它涉及到的一些相容性操作。
class 操作
先來說一個比較有趣的 class 操作,先把連結貼上。
js 有一個非常大的缺陷,就是無法控制偽元素的樣式,比如 after 和 before,這樣子會失去很多樂趣(同樣也帶來了很多樂趣)。上面的連結是 stackoverflow 的解答。
1. class 方式
通過事先定義 class 的方式來解決:
p:before {
content: "c1"
}
p.click:before {
content: "click"
}
// js
$("p").on("click", function(){
$(this).toggleClass('click');
})
2. 內聯 style 方式
這種方式不優雅,也是一種解決辦法。
var str = "click";
$('<style>p:before{content:"' + str + '""}</style>').appendTo('head' );
3. jQuery data-attr 來解決
這種方式是依靠 content 的特性:
p:before {
content: attr(data-click);
}
//js
var str = 'click';
$("p").on("click", function(){
$(this).attr("data-click", str);
})
這種方式應該是動態改變。
jQuery 的應用還是挺廣泛的。
fn.hasClass
jQuery 中的 class 操作還是很有意思,會用到很多正則表示式,我超喜歡正則表示式的。
如果讓我用原生的 js 來實現 class 操作,我會想到兩種方式,一種是
先從 hasClass 說起吧:
// 獲取 class-name
function getClass( elem ) {
return elem.getAttribute && elem.getAttribute( "class" ) || "";
}
// 將 class name 進行處理
function stripAndCollapse( value ) {
var tokens = value.match( /[^\x20\t\r\n\f]+/g ) || [];
return tokens.join( " " );
}
jQuery.fn.extend( {
hasClass: function( selector ) {
var className, elem,
i = 0;
className = " " + selector + " ";
while ( ( elem = this[ i++ ] ) ) {
if ( elem.nodeType === 1 &&
( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) {
return true;
}
}
return false;
}
} );
可以看出 getClass
函式使用的是 getAttribute
方法。
fn.addClass
接下來看一下新增 add:
jQuery.fn.extend( {
addClass: function( value ) {
var classes, elem, cur, curValue, clazz, j, finalValue,
i = 0;
// 引數為函式...
if ( jQuery.isFunction( value ) ) {
return this.each( function( j ) {
jQuery( this ).addClass( value.call( this, j, getClass( this ) ) );
} );
}
if ( typeof value === "string" && value ) {
// 可以新增多個 class
classes = value.match( /[^\x20\t\r\n\f]+/g ) || [];
while ( ( elem = this[ i++ ] ) ) {
curValue = getClass( elem );
cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " );
if ( cur ) {
j = 0;
while ( ( clazz = classes[ j++ ] ) ) {
if ( cur.indexOf( " " + clazz + " " ) < 0 ) {
cur += clazz + " ";
}
}
// 在這裡 set class,有個 diff 判斷
finalValue = stripAndCollapse( cur ); // 去除兩側空格
if ( curValue !== finalValue ) {
elem.setAttribute( "class", finalValue );
}
}
}
}
return this;
}
} );
jQuery 大致處理的思路是這樣的:先把當前 elem 中的 class 取出來 cur
,要新增的 value
如果在 cur 中 indexOf
的值顯示不存在,就在 cur 後面加上 value。
fn.removeClass
刪除可能要麻煩一點點:
jQuery.fn.extend( {
removeClass: function( value ) {
var classes, elem, cur, curValue, clazz, j, finalValue,
i = 0;
// 不知道在哪裡用到 value 為 function 情況
if ( jQuery.isFunction( value ) ) {
return this.each( function( j ) {
jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) );
} );
}
// 無引數表示 移除所有的 class ...
if ( !arguments.length ) {
return this.attr( "class", "" );
}
if ( typeof value === "string" && value ) {
classes = value.match( rnothtmlwhite ) || [];
while ( ( elem = this[ i++ ] ) ) {
curValue = getClass( elem );
// This expression is here for better compressibility (see addClass)
cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " );
if ( cur ) {
j = 0;
while ( ( clazz = classes[ j++ ] ) ) {
// 移除所有需要移除的 class
while ( cur.indexOf( " " + clazz + " " ) > -1 ) {
cur = cur.replace( " " + clazz + " ", " " );
}
}
// Only assign if different to avoid unneeded rendering.
finalValue = stripAndCollapse( cur );
if ( curValue !== finalValue ) {
elem.setAttribute( "class", finalValue );
}
}
}
}
return this;
}
} );
可以看出 remove 的操作基本上和 add 一樣,只不過處理 class 的時候略有不同:
// 這裡用 while,是有技巧的
while ( cur.indexOf( " " + clazz + " " ) > -1 ) {
cur = cur.replace( " " + clazz + " ", " " );
}
用 replace 替換匹配的 clazz 為空格。
fn.toggleClass
toggleClass 使用的頻率也比較高。
先來看看大致用法,你肯定會忽略它的第二個引數的意思。.toggleClass(),當第二個引數為 true 的情況,就是 addClass,為 false 時,removeClass,從原始碼來看,就是直接呼叫的這兩個函式。
除了兩個引數,還有無參和只有 false 情況,下面也都有明確的處理辦法。
jQuery.fn.extend( {
toggleClass: function( value, stateVal ) {
var type = typeof value;
// 第二個引數為 boolean
if ( typeof stateVal === "boolean" && type === "string" ) {
return stateVal ? this.addClass( value ) : this.removeClass( value );
}
if ( jQuery.isFunction( value ) ) {
return this.each( function( i ) {
jQuery( this ).toggleClass(
value.call( this, i, getClass( this ), stateVal ),
stateVal
);
} );
}
return this.each( function() {
var className, i, self, classNames;
if ( type === "string" ) {
// Toggle individual class names
i = 0;
self = jQuery( this );
classNames = value.match( rnothtmlwhite ) || [];
while ( ( className = classNames[ i++ ] ) ) {
// 有則刪,無則加,邏輯很簡單
if ( self.hasClass( className ) ) {
self.removeClass( className );
} else {
self.addClass( className );
}
}
// 當無參或只有一個 false 時,所有 class 都執行
} else if ( value === undefined || type === "boolean" ) {
className = getClass( this );
if ( className ) {
// Store className if set
dataPriv.set( this, "__className__", className );
}
if ( this.setAttribute ) {
this.setAttribute( "class",
className || value === false ?
"" :
dataPriv.get( this, "__className__" ) || ""
);
}
}
} );
}
} );
看得出來,這個邏輯和前面兩個很像,不過當無參或只有一個 boolean 且 false 時,先將當前的 className 儲存到 data cache 中,然後實現 toggle 操作:
if ( this.setAttribute ) {
this.setAttribute( "class",
className || value === false ? // 判斷條件
"" : // 有則設空
dataPriv.get( this, "__className__" ) || "" // 無則從 data cache 取
);
}
總結
感覺 jQuery 中的 class 操作不是很複雜,難道是我在進步嗎,哈哈。