1. 程式人生 > >JQuery3.1.1原始碼解讀(十六)【dom-html】

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,然而,在開始本章之前,一定要對這幾個方法很常熟練才行。

解密jQuery核心 DOM操作方法(二)html,text,val

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 方法也沒有做過濾,每個瀏覽器的解析也不一樣,就可能導致瀏覽器帶來的差異,實際使用的時候,還是要小心點好,多長個心眼。

參考