JQuery3.1.1原始碼解讀(十六)【dom-html】
上一章談到了 dom 的幾個插入操作,雖然插入的方式多種多樣,但只要在懂了原生方法等基礎上,程式碼看起來都不是很複雜。比較有意思的一個函式就是 buildFragment 方法,用來將 html 字串轉換成 dom 碎片。本章來看一下 dom 的其它方法。
html、text 方法
說到 elem 的操作,就必然要提一下 elem 的型別。NodeType,一個節點的 nodeType
為 1 表示元素節點,為 3 表示 text 文位元組點,為 9 表示 document,11 表示 documentFragment,就是上一章所說的文件碎片。大概知道這幾個就可以了。
原生的 elem 方法包括 innerHTML,outerHTML,innerText,outerText,然而,在開始本章之前,一定要對這幾個方法很常熟練才行。
innerHTML 和 outerHTML 一個顯著的差異性就是 outer 會把當前 elem 也一起算進去並獲得 html 字串,inner 不會。
innerText 和 outerText 獲取時候沒有顯著差異,但是 set 情況下(設定)的時候,outer 會把當前 elem 也給刪掉,使用還是要謹慎。
有時候因為瀏覽器的相容問題,可以用 textContent 替代 innerText。
access 函式原始碼
下面是jQuery.fn.html
和 text 的原始碼,看了之後肯定有話要說:
jQuery.fn.extends( {
html: function ( value ) {
return access( this, function( value ) {
... // callback 函式
}, null, value, arguments.length )
}),
text: function( value ) {
return access( this, function( value ) {
...// 回撥函式
}, null, value, arguments.length)
})
} );
好吧,我承認,又是同樣的套路,先交給 access 函式來處理,然後 callback 函式,我猜這個時候 callback 函式肯定是採用 call 方式使 this 綁定當前 elem。這個套路似曾相識,對,就是 domManip 函式。
其實 access 前面已經介紹了過了,不過還是值得來重現介紹一下。
像 html、text、css 這些函式的功能,都有一個特點,就是可以帶引數,也可以不帶引數,先用 access 函式對引數校正,執行回撥。
var access = function( elems, fn, key, value, chainable, emptyGet, raw ) {
var i = 0,
len = elems.length,
bulk = key == null;
// key values 多種情況
if ( jQuery.type( key ) === "object" ) {
chainable = true;
for ( i in key ) {
access( elems, fn, i, key[ i ], true, emptyGet, raw );
}
// Sets 情況
} else if ( value !== undefined ) {
chainable = true;
// value 為函式,不知道這是一種什麼情況
if ( !jQuery.isFunction( value ) ) {
raw = true;
}
if ( bulk ) {
// 執行回撥
if ( raw ) {
fn.call( elems, value );
fn = null;
// ...except when executing function values
} else {
bulk = fn;
fn = function( elem, key, value ) {
return bulk.call( jQuery( elem ), value );
};
}
}
// css 走這一步
if ( fn ) {
for ( ; i < len; i++ ) {
fn(
elems[ i ], key, raw ?
value :
value.call( elems[ i ], i, fn( elems[ i ], key ) )
);
}
}
}
// chainable 表示引數長度 0 或 1
if ( chainable ) {
return elems;
}
// Gets
if ( bulk ) {
return fn.call( elems );
}
return len ? fn( elems[ 0 ], key ) : emptyGet;
};
access 中出現了一種 value 為函式的情況,沒有碰到過,暫不知道什麼意思。access 函式基本沒有做太大的變化處理看,看起來也不是很難。(哈哈,找到了,後面 css 操作的時候,key 可以為 object)
fn.html 原始碼
現在就是主要來看這個回撥函數了,當前的 this 使指向 jQuery 物件的,並沒有指向單獨的 elem 元素,html 肯定要進行判斷:
jQuery.fn.extends( {
html: function( value ) {
return access( this, function( value ) {
var elem = this[ 0 ] || {},
i = 0,
l = this.length;
// 引數為空的情況,get
if ( value === undefined && elem.nodeType === 1 ) {
return elem.innerHTML;
}
// set 操作
if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
!wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) {
value = jQuery.htmlPrefilter( value );
try {
for ( ; i < l; i++ ) {
elem = this[ i ] || {};
// Remove element nodes and prevent memory leaks
if ( elem.nodeType === 1 ) {
// cleanData 是清除 dom 繫結 cache 的資料
jQuery.cleanData( getAll( elem, false ) );
elem.innerHTML = value;
}
}
elem = 0;
// If using innerHTML throws an exception, use the fallback method
} catch ( e ) {}
}
if ( elem ) {
this.empty().append( value );
}
}, null, value, arguments.length );
}
} );
fn.text 原始碼
下面是 text 原始碼,關於 html 和 text 在開頭已經介紹了,算是比較基礎的 dom 操作吧,直接來看原始碼吧:
jQuery.fn.extends( {
text: function( value ) {
return access( this, function( value ) {
// 前面已經說了,回撥函式裡的 this 指向 jQuery 物件
return value === undefined ?
// get
jQuery.text( this ) :
// set
this.empty().each( function() {
if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
this.textContent = value;
}
} );
}, null, value, arguments.length );
}
} );
先來看看 set 的情況,這裡有個 empty 函式,原始碼在下面,兩個功能,先清空 dom 內容,在刪除 data cache 中儲存的資料。這裡沒有用 innerText 方法,而是使用 textContent 方法,貌似就 set 來說,textContent 相容性更好。
jQuery.fn.extends( {
empty: function() {
var elem,
i = 0;
for ( ; ( elem = this[ i ] ) != null; i++ ) {
if ( elem.nodeType === 1 ) {
// Prevent memory leaks
jQuery.cleanData( getAll( elem, false ) );
// 清空
elem.textContent = "";
}
}
return this;
},
} );
set 的方法知道了,那麼 get 呢?get 首先呼叫了 jQuery.text
方法,找了半天才找到它在哪裡,原來呼叫的是 Sizzle 中的方法:
jQuery.text = Sizzle.getText;
var getText = Sizzle.getText = function( elem ) {
var node,
ret = "",
i = 0,
nodeType = elem.nodeType;
if ( !nodeType ) {
// elem 是一個 dom 陣列
while ( (node = elem[i++]) ) {
// 分步來搞
ret += getText( node );
}
} else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
// innerText usage removed for consistency of new lines (jQuery #11153)
// 依然使用 textContent 方法
if ( typeof elem.textContent === "string" ) {
return elem.textContent;
} else {
// Traverse its children
for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
ret += getText( elem );
}
}
// 3 (text)或 4 (4 貌似被移除)直接返回 nodeValue
} else if ( nodeType === 3 || nodeType === 4 ) {
return elem.nodeValue;
}
return ret;
};
總結
我自己在瀏覽器上面測試,發現 textContent 方法並不會把空白符給刪了,而且 jQuery 的 text 方法也沒有做過濾,每個瀏覽器的解析也不一樣,就可能導致瀏覽器帶來的差異,實際使用的時候,還是要小心點好,多長個心眼。