javascript筆記:String的replace,由正則表達式到jQuery選擇器

 我在上一篇博客裡談到瞭javascript裡面的String類的 replace方法的一些問題,今天我真正的學習瞭javascript裡的正則表達式的用法(以前總是不屑學習這個技術,現在發現編程裡字符處理的技術還是相當的重要,應用領域很廣泛而且也有一定難度,比如jQuery源碼裡面就有很多正則表達式的使用),對於String類裡 s.replace(regex,function(){})的理解更清晰,以前不清晰的原因是沒有學習好正則表達式。我寫瞭下面的測試代碼:

 

//下面代碼請在裝有firebug的firefox裡面運行
function myReplace()
{
 var reg = /%[1-4]/g;
 var data = "And the %1 want to know whose %2 you %3";
 
 while(1==1)
 {
  var val = reg.exec(data);
  if (val == null)
  {
   break;
  }else{
   console.log(val);
  }
 }
 
 reg = /CJ[0-9]{2}/g;
 data = 'CJ9080,CJ8976,CJ12919,CJ8765';
 while(1==1)
 {
  var val = reg.exec(data);
  if (val == null)
  {
   break;
  }else{
   console.log(val);
  }
 }
 
}
myReplace();

結果如下:

 

  其實String類的s.replace(regex,function(){})用法就是瞭Regex的exec()方法,隻不過當正則式為[1-4]這樣格式的時候,replace方法會在遍歷字符串時候把裡面的1-4的值都取出來,放到function的argument[1]裡面。

  今天抽時間讀瞭一下jQuery的源代碼,jQuery說白瞭就是一個選擇器,例如我們常看到這樣的寫法:

  

jQuery('#userId').val();
jQuery('p').text();

  

上面代碼就是在使用jQuery選擇器,jQuery選擇器實現瞭下列四個方法:


jQuery( expression, context )
Returns: jQuery
這個函數接收一個CSS選擇器的字符串,然後用這個字符串去匹配一組元素。
This function accepts a string containing a CSS selector which is then used to match a set of elements.
jQuery( html, ownerDocument )
Returns: jQuery
根據HTML原始字符串動態創建Dom元素.
Create DOM elements on-the-fly from the provided String of raw HTML.
jQuery( elements )
Returns: jQuery
將一個或多個Dom對象封裝jQuery函數功能(即封裝為jQuery包裝集)
Wrap jQuery functionality around a single or multiple DOM Element(s).
jQuery( callback )
Returns: jQuery
$(document).ready()的簡寫方式
A shorthand for $(document).ready().
上面摘選自jQuery官方手冊。

在選擇器方法裡面使用到瞭這樣一個正則表達式:

quickExpr = /^[^<]*(<[\w\W]+>)[^>]*$|^#([\w-]+)$/
這個正則表達式都是在我們傳入jQuery裡面第一個參數是string時候會調用,具體點就是當你不是傳入$(""), $(null), $(undefined)或者$(DOMElement)時候就會使用到這個正則表達式。因此我想在這裡好好分析下這兩個正則表達式。

首先補充下正則表達式的基礎知識:

 

元字符

描述

.

匹配任何單個字符。例如正則表達式r.t匹配這些字符串:ratrutr t,但是不匹配root

$

匹配行結束符。例如正則表達式weasel$ 能夠匹配字符串"He's a weasel"的末尾 
但是不能匹配字符串"They are a bunch of weasels."

^

匹配一行的開始。例如正則表達式^When in能夠匹配字符串"When in the course of human events"的開始,但是不能匹配"What and When in the"

*

匹配0或多個正好在它之前的那個字符。例如正則表達式。*意味著能夠匹配任意數量的任何字符。

\

這是引用符,用來將這裡列出的這些元字符當作普通的字符來進行匹配。例如正則表達式\$被用來匹配美元符號,而不是行尾,類似的,正則表達式\.用來匹配點字符,而不是任何字符的通配符。

[ ] 
[c1-c2] 
[^c1-c2]

匹配括號中的任何一個字符。例如正則表達式r[aou]t匹配ratrotrut,但是不匹配ret。可以在括號中使用連字符來指定字符的區間,例如正則表達式[0-9]可以匹配任何數字字符;還可以制定多個區間,例如正則表達式[A-Za-z]可以匹配任何大小寫字母。另一個重要的用法是排除,要想匹配除瞭指定區間之外的字符——也就是所謂的補集——在左邊的括號和第一個字符之間使用^字符,例如正則表達式[^269A-Z] 將匹配除瞭269和所有大寫字母之外的任何字符。

\< \>

匹配詞(word)的開始(\<)和結束(\>)。例如正則表達式\<the\>能夠匹配字符串"for the wise"中的"the",但是不能匹配字符串"otherwise"中的"the"。註意:這個元字符不是所有的軟件都支持的。

\( \)

\( \) 之間的表達式定義為group),並且將匹配這個表達式的字符保存到一個臨時區域(一個正則表達式中最多可以保存9個),它們可以用\1 \9 的符號來引用。

|

將兩個匹配條件進行邏輯Or)運算。例如正則表達式(him|her) 匹配"it belongs to him""it belongs to her",但是不能匹配"it belongs to them."。註意:這個元字符不是所有的軟件都支持的。

+

匹配1或多個正好在它之前的那個字符。例如正則表達式9+匹配999999等。註意:這個元字符不是所有的軟件都支持的。

?

匹配01個正好在它之前的那個字符。註意:這個元字符不是所有的軟件都支持的。

{i} 
{i,j}

匹配指定數目的字符,這些字符是在它之前的表達式定義的。例如正則表達式A[0-9]{3} 能夠匹配字符"A"後面跟著正好3個數字字符的串,例如A123A348等,但是不匹配A1234。而正則表達式[0-9]{4,6} 匹配連續的任意4個、5個或者6個數字字符。註意:這個元字符不是所有的軟件都支持的。

\s

匹配任何空白字符,包括空格、制表符、換頁符等等。等價於[?\f\n\r\t\v]

\w

匹配包括下劃線的任何單詞字符。等價於’[A-Za-z0-9_]‘

\W

匹配任何非單詞字符。等價於‘[^A-Za-z0-9_]‘

正則表達式quickExpr = /^[^<]*(<[\w\W]+>)[^>]*$|^#([\w-]+)$/,可以由| 分為兩個部分,前一個部分是^[^<]*(<[\w\W]+>)[^>]*$,這個有開始符號^和結束符號$,該表達式按順序分析:

1.     [^<]*—-標示字符的頭部可以是除瞭<的任意字符或者是幹脆沒有字符

2.     (<[\w\W]+>)—–這個表示字符串裡要包含用<>包含的字符,例如<p>,<p>等等都是符合要求的

3.     [^>]*—-字符串尾部是除瞭>的任意字符或者沒有字符

由上可知表達式^[^<]*(<[\w\W]+>)[^>]*$的意思是字符串裡面一定要包含被尖括號包含的字符也就是html代碼。

正則表達式的後半部分是:^#([\w-]+)$,這個就比較簡單瞭,它的含義是匹配帶上#號的任意字符。

那麼整個表達式的含義就是匹配HTML標記和ID表達式。下面我做瞭針對這個表達式的測試,代碼如下:

 

function regrexFtn()

{

        var quickExpr = /^[^<]*(<[\w\W]+>)[^>]*$|^#([\w-]+)$/;

        var data = "#userId";

       

        console.log(quickExpr.exec(data));

       

        data = "<span>javascript jquery</span>";

        console.log(quickExpr.exec(data));

       

        data = "start<span>javascript jquery</span>end";

        console.log(quickExpr.exec(data));

       

        data = "p .red";

        console.log(quickExpr.exec(data));

       

        data = "javascript jquery";

        console.log(quickExpr.exec(data));

       

        data = "p";

        console.log(quickExpr.exec(data));

       

        data = ".odd";

        console.log(quickExpr.exec(data));

       

}

 

regrexFtn();

結果如下:

 

 

我在讀jQuery選擇器源碼時候,對於這個表達式的正確理解是我的難點,所以我單獨研究下這個表達式,現在清楚瞭它的功能,那麼我在讀取選擇器的源碼就簡單多瞭。下面是jquery-1.4.1.js的選擇器的源碼:

 init: function( selector, context ) {
  var match, elem, ret, doc;

  // Handle $(""), $(null), or $(undefined)
  if ( !selector ) {
   return this;
  }

  // Handle $(DOMElement)
  if ( selector.nodeType ) {
   this.context = this[0] = selector;
   this.length = 1;
   return this;
  }

  // Handle HTML strings
  if ( typeof selector === "string" ) {
   // Are we dealing with HTML string or an ID?
   match = quickExpr.exec( selector );

   // Verify a match, and that no context was specified for #id
   if ( match && (match[1] || !context) ) {

    // HANDLE: $(html) -> $(array)
    if ( match[1] ) {
     doc = (context ? context.ownerDocument || context : document);

     // If a single string is passed in and it's a single tag
     // just do a createElement and skip the rest
     ret = rsingleTag.exec( selector );

     if ( ret ) {
      if ( jQuery.isPlainObject( context ) ) {
       selector = [ document.createElement( ret[1] ) ];
       jQuery.fn.attr.call( selector, context, true );

      } else {
       selector = [ doc.createElement( ret[1] ) ];
      }

     } else {
      ret = buildFragment( [ match[1] ], [ doc ] );
      selector = (ret.cacheable ? ret.fragment.cloneNode(true) : ret.fragment).childNodes;
     }

    // HANDLE: $("#id")
    } else {
     elem = document.getElementById( match[2] );

     if ( elem ) {
      // Handle the case where IE and Opera return items
      // by name instead of ID
      if ( elem.id !== match[2] ) {
       return rootjQuery.find( selector );
      }

      // Otherwise, we inject the element directly into the jQuery object
      this.length = 1;
      this[0] = elem;
     }

     this.context = document;
     this.selector = selector;
     return this;
    }

   // HANDLE: $("TAG")
   } else if ( !context && /^\w+$/.test( selector ) ) {
    this.selector = selector;
    this.context = document;
    selector = document.getElementsByTagName( selector );

   // HANDLE: $(expr, $(…))
   } else if ( !context || context.jquery ) {
    return (context || rootjQuery).find( selector );

   // HANDLE: $(expr, context)
   // (which is just equivalent to: $(context).find(expr)
   } else {
    return jQuery( context ).find( selector );
   }

  // HANDLE: $(function)
  // Shortcut for document ready
  } else if ( jQuery.isFunction( selector ) ) {
   return rootjQuery.ready( selector );
  }

  if (selector.selector !== undefined) {
   this.selector = selector.selector;
   this.context = selector.context;
  }

  return jQuery.isArray( selector ) ?
   this.setArray( selector ) :
   jQuery.makeArray( selector, this );
 },
源碼解析:

參數說明:selector:選擇器的符號,可以是任意數據類型。

         Context:上下文,指定在文檔DOM中那個節點下進行查詢,默認值是document

1.     如果我們寫的選擇器是這樣jQuery (""),jQuery (null), jQuery (undefined),那麼執行的代碼是:


        if ( !selector ) {

            return this;

        }


直接返回一個jQuery對象。


2.     如果我們寫的選擇器是jQuery(document.getElementById('#d01'));也就是jQuery裡的參數是一個DOM元素,那麼執行的代碼是:

  if ( selector.nodeType ) {
   this.context = this[0] = selector;//把當前的DOM元素存入到jQuery類的數組中
   this.length = 1;//設置jQuery類的數組長度,方便以後遍歷訪問
   return this;//返回jQuery對象
  }
3.     如果我們在jQuery選擇器裡面傳入的是字符串,就是這樣的形式jQuery('..'),那麼jQuery都會執行這段代碼:

  if ( typeof selector === "string" ) {
   // Are we dealing with HTML string or an ID?
   match = quickExpr.exec( selector );
下面代碼如何運行都是根據match這個變量所決定的,根據我上面測試的結果來看看這段代碼。

4.jQuery('#userIds'),jQuery裡面的參數是一個ID值,match = quickExpr.exec( selector );的執行結果是:

 


那麼if ( match && (match[1] || !context) )裡面的match && (match[1] || !context)等價於(true && (false || true))結果就是false瞭。接下來執行的代碼是:


    } else {
     elem = document.getElementById( match[2] );//獲取DOM元素

     if ( elem ) {
      //獲取該元素確保元素存在,因為在ie或是Opera
      //瀏覽器裡面這個方法可能是根據name而不是ID
      if ( elem.id !== match[2] ) {
       return rootjQuery.find( selector );
      }

      //否則就將elem放入jQuery類的數組裡,直接返回jQuery對象s
      this.length = 1;// 設置jQuery類的數組長度,方便以後遍歷訪問

      this[0] = elem;// 把當前的DOM元素存入到jQuery類的數組中
     }

     this.context = document;//設置jQuery對象的上下文屬性
     this.selector = selector;
     return this;//返回jQuery對象s
    }

5.     jQuery(‘p’),當jQuery參數是html標簽,那麼match = quickExpr.exec( selector );執行的結果就是null瞭,這時候程序會執行下面代碼:

   } else if ( !context && /^\w+$/.test( selector ) ) {//正則表達式是檢測傳入參數是不是符合標簽規則的字符串
    this.selector = selector;
    this.context = document;
    selector = document.getElementsByTagName( selector );
   }
然後代碼會走到這裡:
else {
    return jQuery( context ).find( selector );
   }
但是這時候程序並沒有跳出代碼,而是走到瞭

  return jQuery.isArray( selector ) ?
   this.setArray( selector ) :
   jQuery.makeArray( selector, this );
這個蠻奇怪的。

6.     如果jQuery選擇器這麼寫jQuery('start<span>javascript jquery</span>end')或者jQuery('<span>javascript jquery</span>s'),match = quickExpr.exec( selector );執行的結果如下:

 


程序執行的代碼是:

   if ( match[1] ) {
     doc = (context ? context.ownerDocument || context : document);
     ret = rsingleTag.exec( selector );

     if ( ret ) {
      if ( jQuery.isPlainObject( context ) ) {
       selector = [ document.createElement( ret[1] ) ];
       jQuery.fn.attr.call( selector, context, true );

      } else {
       selector = [ doc.createElement( ret[1] ) ];
      }

     } else {
      ret = buildFragment( [ match[1] ], [ doc ] );
      selector = (ret.cacheable ? ret.fragment.cloneNode(true) : ret.fragment).childNodes;
     }
這段代碼裡操作的都是match[1],可以看出,這裡使用到的結果是<span>javascript jquery</span>,也就是代碼隻是對html代碼進行操作。

7.     當我們寫這樣的選擇器時候jQuery('p .red'),這時候match = quickExpr.exec( selector );執行的結果是null,代碼最終會執行到:

quickExpr.exec( selector );執行的結果是null,代碼最終會執行到:
   } else if ( !context || context.jquery ) {
    return (context || rootjQuery).find( selector );//默認調用document.find()方法。

作者“sharpxiajun”
 

發佈留言