jQueryのセレクタ解析のSizzleについて

jQueryソースコードjquery-2.1.4.js を見ていきます。

Sizzleとは

Stand Alone のCSSセレクタの解析エンジンで、指定したセレクタに対するDOM要素を抽出してくれるものです。

Sizzle JavaScript Selector Library

jQueryとSizzleの関係性について

下記のブログ記事に詳しくまとめられています。

lealog.hateblo.jp

jquery-2.1.4.js内でいうと、553 〜 2611行にSizzleが記述されています。

553行目付近のSizzle宣言箇所

var Sizzle =
/*!
 * Sizzle CSS Selector Engine v2.2.0-pre
 * http://sizzlejs.com/
 *
 * Copyright 2008, 2014 jQuery Foundation, Inc. and other contributors
 * Released under the MIT license
 * http://jquery.org/license
 *
 * Date: 2014-12-16
 */
(function( window ) {

2611行目付近, Sizzle記述終了部分とjQueryとSizzleの結びつけの設定を行っています。

return Sizzle;

})( window );



jQuery.find = Sizzle;
jQuery.expr = Sizzle.selectors;
jQuery.expr[":"] = jQuery.expr.pseudos;
jQuery.unique = Sizzle.uniqueSort;
jQuery.text = Sizzle.getText;
jQuery.isXMLDoc = Sizzle.isXML;
jQuery.contains = Sizzle.contains;

jQuery.find の実体がSizzleに設定されていて、その他CSSセレクタとはあまり関係ないオブジェクトもSizzle内のオブジェクトを利用している感じですね。

Sizzleの設計

Sizzle 自体はシンプルな関数として定義されています。

750行目辺り。

function Sizzle( selector, context, results, seed ) {

その他の外部向けのAPIを定義する際は、 API名 = Sizzle.API名 = function() という形でSizzleオブジェクト経由で参照できるようになっています。

/**
 * A low-level selection function that works with Sizzle's compiled
 *  selector functions
 * @param {String|Function} selector A selector or a pre-compiled
 *  selector function built with Sizzle.compile
 * @param {Element} context
 * @param {Array} [results]
 * @param {Array} [seed] A set of elements to match against
 */
select = Sizzle.select = function( selector, context, results, seed ) {

例えば Sizzle.select は上記のように定義されています。

function Sizzle

Sizzle関数を見ていきます。 公式ドキュメントでは下記のように記されています。

Sizzle( String selector[, DOMNode context[, Array results]] )

The main function for finding elements. Uses querySelectorAll if available.

returns (Array): All elements matching the selector

Parameters

selector: A CSS selector

context: An element, document, or document fragment to use as the context for finding elements. Defaults to document. Note: Prior to version 2.1, document fragments were not valid here.

results: An array or an array-like object, to which Sizzle will append results. For example, jQuery passes a jQuery collection. An "array-> like object" is an object with a nonnegative numeric length property and a push method.

ざっくりいうと、セレクターに該当するDOMElementsを array-like object で返す。 querySelectorAllが使えれば使用する、と書いてあります。

document.querySelectorAll - Web API インターフェイス | MDN


処理の流れとしては、

  1. シンプルなセレクター(document.getElementByIdのようあ標準関数で抽出できるようなもの)であれば、すぐに処理して返す。

  2. querySelectorAllが使えれば、処理をquerySelectorAllに委ねる。

  3. select関数に処理を委ねる。

となっています。

シンプルなセレクターかどうかの判定
// Easily-parseable/retrievable ID or TAG or CLASS selectors
rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,

// Try to shortcut find operations when possible (e.g., not under DocumentFragment)
  if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) {

対象処理部分は上記ようになっていて、

match[1]があればIDとして処理、match[2]があればタグ名として処理、match[3]があればクラス名として処理しています。

querySelectorAllが使えるかの判定
// QSA path
if ( support.qsa && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) {

support.qsaは、documentにquerySelectorAllが定義されているかどうかで、

rbuggyQSAは互換性等の問題で、意図通りの処理がされないセレクタ文字列が配列に格納されています。

rbuggyQSAの処理は 1156 〜 1262行目辺りに記述されていて、動作確認用のdomに対してquerySelectorAllを呼び出して、期待通りの挙動をしているかを確認しています。

function select

select関数は2467行目辺りに定義されています。

/**
 * A low-level selection function that works with Sizzle's compiled
 *  selector functions
 * @param {String|Function} selector A selector or a pre-compiled
 *  selector function built with Sizzle.compile
 * @param {Element} context
 * @param {Array} [results]
 * @param {Array} [seed] A set of elements to match against
 */
select = Sizzle.select = function( selector, context, results, seed ) {