看完了上篇,對Sizzle有了一個大緻的了解,我們接下來就可以正式開始啃Sizzle的源碼了。上來就講matcher難度太大,先來點開胃菜,講講Sizzle中的各個正規表達式的作用吧(本來還想講初始化的,篇幅太長了,留待下篇吧)。
友情提醒:閱讀本文請先學習正規表達式,至少對捕獲組以及js的正則API(exec,match,test,字元串轉正則)有一定的了解
這是前面一堆變量聲明和函數聲明。
var Sizzle =
/*!
* Sizzle CSS Selector Engine v1.10.16
* http://sizzlejs.com/
*
* Copyright 2013 jQuery Foundation, Inc. and other contributors
* Released under the MIT license
* http://jquery.org/license
*
* Date: 2014-01-13
*/
(function( window ) {
var i,
support, //後面會初始化為對象,儲存支援性
Expr,
getText,
isXML, //是否是XML
compile,
outermostContext, //最大查找範圍
sortInput,
hasDuplicate, //剛檢查完的兩個元素是否重複
// Local document vars
setDocument,
document,
docElem,
documentIsHTML,
rbuggyQSA,
rbuggyMatches,
matches,
contains,
// Instance-specific data
//用來對特殊的函數進行标記
expando = "sizzle" + -(new Date()),
//傾向于使用的文檔節點
preferredDoc = window.document,
dirruns = 0,
done = 0,
//這裡幾個cache實際上是函數
//通過classCache(key,value)的形式進行存儲
//通過classCache[key+' ']來進行擷取
classCache = createCache(),
tokenCache = createCache(),
compilerCache = createCache(),
sortOrder = function( a, b ) {
if ( a === b ) {
hasDuplicate = true;
}
return 0;
},
到這裡先停一下,注意到變量聲明的時候聲明了三個緩存(cache),涉及到createCache這個工具函數,這個工具函數的作用簡單來說就是建立一個緩存,用OO思想我們建立的緩存都是聲明一個對象,然後再給其加上set、get、delete等方法,但Sizzle不是這樣做的,我們先來看看源碼。
/**
* Create key-value caches of limited size
* @returns {Function(string, Object)} Returns the Object data after storing it on itself with
* property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength)
* deleting the oldest entry
*/
function createCache() {
//用來儲存已經存儲過的key,這是一種閉包
var keys = [];
//這裡使用cache這個函數本身來當作存放資料的對象。
function cache( key, value ) {
// Use (key + " ") to avoid collision with native prototype properties (see Issue #157)
//key後面加空格是為了避免覆寫原生屬性
//當長度超過限制時,則需要删除以前的緩存
if ( keys.push( key + " " ) > Expr.cacheLength ) {
// Only keep the most recent entries
delete cache[ keys.shift() ];
}
//傳回存儲好的資訊
return (cache[ key + " " ] = value);
}
return cache;
}
這裡調用createCache傳回了一個函數,函數像對象一樣可以來以key-value的形式存儲資訊(jQuery的大部分工具函數都是存儲在jQuery這個工廠函數上,這樣省了一個命名空間),當直接調用這個傳回的函數時,就可以存儲資訊(set),用cache[key+' ']的形式就可以取出存放的資訊(get),之是以要在key後面加空格,是要避免覆寫其他原生屬性。
繼續傳回上面,開始看各種變量聲明。
這裡的各個變量儲存了一些如push之類的原生方法引用,因為這些方法要多次使用,這樣的做法可以優化性能。
// General-purpose constants
//還是不明白這裡為什麼要把undefined轉換為字元串用以判斷
strundefined = typeof undefined,
MAX_NEGATIVE = 1 << 31,
// Instance methods
hasOwn = ({}).hasOwnProperty,
arr = [],
pop = arr.pop,
push_native = arr.push,
push = arr.push,
slice = arr.slice,
// Use a stripped-down indexOf if we can't use a native one
//先檢查一下有沒有原生API
indexOf = arr.indexOf || function( elem ) {
var i = 0,
len = this.length;
for ( ; i < len; i++ ) {
if ( this[i] === elem ) {
return i;
}
}
return -1;
},
//用來在做屬性選擇的時候進行判斷
booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",
// Regular expressions
// Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace
//空白符的幾種類型
//字元串裡的兩根斜杠其中有一根用來轉義,比如\\x20到了正規表達式裡就是\x20
whitespace = "[\\x20\\t\\r\\n\\f]",
然後我們接觸到了第二個正規表達式(雖然它現在是字元串,但最終是會傳入new RegExp方法的)
這裡總結了符合标準的種種空白字元,包括空格、tab、回車、換行等,這裡之是以有兩根反斜杠(backslash),是因為反斜杠在字元串中是轉義符,為了表示反斜杠這一含義,需要用\\這樣的寫法。
我們繼續看後面的正規表達式。
// http://www.w3.org/TR/css3-syntax/#characters
//\\\\.轉換到正規表達式中就是\\.+用來相容帶斜杠的css
//三種比對字元的方式:\\.+,[\w-]+,大于\xa0的字元+,為什麼比對這三個請看上面的連結
characterEncoding = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",
// Loosely modeled on CSS identifier characters
// An unquoted value should be a CSS identifier http://www.w3.org/TR/css3-selectors/#attribute-selectors
// Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
//這裡多了一個#,暫時沒細看标準不明白為何
identifier = characterEncoding.replace( "w", "w#" ),
// Acceptable operators http://www.w3.org/TR/selectors/#attribute-selectors
//\[ [\x20\t\r\n\f]* ((?:\\.|[\w-]|[^\x00-\xa0])+) [\x20\t\r\n\f]* (?:([*^$|!~]?=) [\x20\t\r\n\f]* (?:['"]) ((?:\\.|[^\\])*?) \3 | () |)|)
//\3 is (['\"])
//這種正則的連結方式對有大量空白的處理非常好,很容易讀。
//捕獲組序列:
//$1:attrName,$2:([*^$|!~]?=),$3:(['\"]),$4:((?:\\\\.|[^\\\\])*?)\\3|(" + identifier + ")|)|),$5:(" + identifier + ")
//$1捕獲的是attrName,$2捕獲的是等号或^=這樣的等号方式,$3捕獲單雙引号
//$4提供三種比對字元串的方式:\\.*?\3,非斜杠*?\3(因為斜杠沒意義),識别符,此處相當于捕獲attrValue,隻不過要相容帶引号和不帶兩種形式
//$5捕獲識别符
attributes = "\\[" + whitespace + "*(" + characterEncoding + ")" + whitespace +
"*(?:([*^$|!~]?=)" + whitespace + "*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|(" + identifier + ")|)|)" + whitespace + "*\\]",
上面兩個正規表達式不再贅述,第三個正規表達式先看開頭和結尾比對的是代表屬性選擇符的'['和']',捕獲出來的結果分别代表的含義是[attrName、等号、引号、attrValue、attrValue]。
再看下一個正規表達式
// Prefer arguments quoted,
// then not containing pseudos/brackets,
// then attribute selectors/non-parenthetical expressions,
// then anything else
// These preferences are here to reduce the number of selectors
// needing tokenize in the PSEUDO preFilter
//$1:pseudoName,$2:((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|" + attributes.replace( 3, 8 ) + ")*)|.*)
// ,$3:(['\"]),$4:((?:\\\\.|[^\\\\])*?),$5:((?:\\\\.|[^\\\\()[\\]]|" + attributes.replace( 3, 8 ) + ")*)
//$1捕獲僞元素或僞類的名字,$2捕獲兩種類型的字元,一種是帶引号的字元串,一種是attributes那樣的鍵值對。
//$3捕獲引号,$4和$5分别捕獲$2中的一部分
pseudos = ":(" + characterEncoding + ")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|" + attributes.replace( 3, 8 ) + ")*)|.*)\\)|)",
// Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter
//$1:((?:^|[^\\\\])(?:\\\\.)*)
//這個用來去除selector多餘的空格,免得幹擾到後面空格的比對關系
rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ),
//這個後面用來清除css規則中組與組之間的逗号。
rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ),
//??????第二個whitespace有什麼用???
//這個現在可以解答了。。因為空格也是用來連接配接祖先後代關系中的一種。。
//$1:([>+~]|whitespace)分别捕獲4種連接配接符:'>','+','~','whitespace'
rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ),
//$1:([^\\]'\"]*?)
//??????這個我忘了有什麼用。。
rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ),
rpseudo = new RegExp( pseudos ),
ridentifier = new RegExp( "^" + identifier + "$" ),
//這裡是最後用來檢測的正規表達式,使用形式通常是matchExpr[tokens[i].type].test(...)
matchExpr = {
"ID": new RegExp( "^#(" + characterEncoding + ")" ),
"CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ),
"TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" ),
"ATTR": new RegExp( "^" + attributes ),
"PSEUDO": new RegExp( "^" + pseudos ),
//$1:(only|first|last|nth|nth-last),$2:(child|of-type),
// $3:(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + "*(\\d+)|))
// $4:(([+-]|)(\\d*)n|),$5:([+-]|),$6:(\\d*),$7:([+-]|),$8:(\\d+)
//這是後面用來檢查你是否用到子選擇器的。
//$3第一部分比對四種字元,even,odd,[+-](\d*)n,任意字元。空格,再比對第二部分:([+-]或任意字元)+空格+(一或多個數字)
//為什麼這麼做,則要看Sizzle支援的子選擇符文法。
//這些捕獲組的含義在後面會提到,請結合具體用法了解
"CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace +
"*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace +
"*(\\d+)|))" + whitespace + "*\\)|)", "i" ),
"bool": new RegExp( "^(?:" + booleans + ")$", "i" ),
// For use in libraries implementing .is()
// We use this for POS matching in `select`
//$1是(even|odd|eq|gt|lt|nth|first|last),$2是((?:-\\d)?\\d*)
//當選擇符比對這個成功的時候,則說明這個選擇符使用的時候需要上下文,而不是像id,tag,class一樣直接查找
"needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" +whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" )
},
rinputs = /^(?:input|select|textarea|button)$/i,
//h1,h2,h3......
rheader = /^h\d$/i,
//用來檢測某個API是否是原生API
//例如document.createElement.toString() 的運作結果是 "function createElement() { [native code] }"
rnative = /^[^{]+\{\s*\[native \w/,
// Easily-parseable/retrievable ID or TAG or CLASS selectors
//容易判斷或擷取的元素單獨拿出來,ID,TAG和CLASS,這三個基本有原生API
rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,
rsibling = /[+~]/,
rescape = /'|\\/g,
// CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters
//$1:([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.),$2:(" + whitespace + ")
runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ),
//??????函數用途不明
//卧槽,jQuery還考慮了編碼http://zh.wikipedia.org/wiki/UTF-16
//轉換為UTF-16編碼,若某個字元是多種字元,超過BMP的計數範圍0xFFFF,則必須将其編碼成小于0x10000的形式。
funescape = function( _, escaped, escapedWhitespace ) {
var high = "0x" + escaped - 0x10000;
// NaN means non-codepoint
// Support: Firefox
// Workaround erroneous numeric interpretation of +"0x"
//這裡的high !== 用于判斷 high是否是NaN,NaN !== NaN
//當high為NaN,escapedWhitespace 為undefined時,再判斷high是否為負數
return high !== high || escapedWhitespace ?
escaped :
high < 0 ?
// BMP codepoint
String.fromCharCode( high + 0x10000 ) :
// Supplemental Plane codepoint (surrogate pair)
String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 );
};
因為寫了很多注釋,我覺得應該能看懂了。。看不懂的再留言交流好了