למה משמש פסאודו-מחלקה של CSS :היקף?

:scope מוגדר ב-CSS Selectors 4 בתור:

פסאודו-סיווג שמייצג כל רכיב שנמצא בקבוצת רכיבי ההפניה לפי הקשר. זוהי קבוצה של אלמנטים (שעשויה להיות ריקה) שצוינה במפורש, כמו זו שצוינה על ידי querySelector(), או הרכיב ההורה של רכיב <style scoped>, שמשמשת ל'הגדרת ההיקף' של סלקטור כך שהוא יתאים רק בתוך עץ משנה.

דוגמה לשימוש ב-<style scoped> (מידע נוסף):

<style>
    li {
    color: blue;
    }
</style>

<ul>
    <style scoped>
    li {
        color: red;
    }
    :scope {
        border: 1px solid red;
    }
    </style>
    <li>abc</li>
    <li>def</li>
    <li>efg</li>
</ul>

<ul>
    <li>hij</li>
    <li>klm</li>
    <li>nop</li>
</ul>

צבע הרכיבים li בצבע ul הראשון בצבע אדום, ובגלל הכלל :scope, מוסיף גבול מסביב ל-ul. הסיבה לכך היא שבהקשר של <style scoped> הזה, ה-ul תואם ל-:scope. זהו ההקשר המקומי. אם נוסיף כלל :scope ב-<style> החיצוני, הוא יתאים לכל המסמך. בעיקרון, זה שווה ל-:root.

אלמנטים לפי הקשר

סביר להניח שאתם מודעים לגרסה Element של querySelector() ו-querySelectorAll(). במקום לבדוק את כל המסמך, ניתן להגביל את קבוצת התוצאה לאלמנט הקשרי:

<ul>
    <li id="scope"><a>abc</a></li>
    <li>def</li>
    <li><a>efg</a></li>
</ul>
<script>
    document.querySelectorAll('ul a').length; // 2

    var scope = document.querySelector('#scope');
    scope.querySelectorAll('a').length; // 1
</script>

כשמפעילים אותם, הדפדפן מחזיר NodeList שעבר סינון כך שיכלול רק את קבוצת הצמתים ש-א) תואמים לבורר ו-ב) הם גם הצאצאים של רכיב ההקשר. בדוגמה השנייה, הדפדפן מוצא את כל הרכיבים של a ואז מסנן את אלה שלא נמצאים ברכיב scope. הפתרון הזה עובד, אבל אם לא תהיו זהירים, הוא עלול להוביל להתנהגות מוזרה. המשך לקרוא.

כש-querySelector משתבש

יש נקודה חשובה במפרט של הבוררים שאנשים לרוב לא שמים לב אליה. גם כשמפעילים את querySelector[All]() על רכיב, בוררי ה-CSS עדיין מבצעים הערכה בהקשר של כל המסמך. כלומר, יכולים לקרות דברים בלתי צפויים:

    scope.querySelectorAll('ul a').length); // 1
    scope.querySelectorAll('body ul a').length); // 1

WTF! בדוגמה הראשונה, ul הוא הרכיב שלי, אבל עדיין אפשר להשתמש בו והוא תואם לצמתים. באפשרות השנייה, body הוא אפילו לא צאצא של הרכיב שלי, אבל "body ul a" עדיין תואם. שתי התוצאות האלה מבלבלות ולא תואמות לציפיות.

כדאי להשוות את הקוד הזה ל-jQuery, שבו הגישות נכונות והקוד עושה את מה שציפיתם:

    $(scope).find('ul a').length // 0
    $(scope).find('body ul a').length // 0

…יש להזין :scope כדי לפתור את הבעיות הסמנטיות האלה.

תיקון querySelector באמצעות :scope

WebKit קיבלת לאחרונה תמיכה בשימוש בפסאודו-מחלקה :scope ב-querySelector[All](). אפשר לבדוק את התכונה ב-Chrome Canary 27.

אפשר להשתמש בו כדי להגביל את הבוררים לרכיב הקשר. נראה דוגמה. בהמשך, :scope משמש כדי 'להגדיר את ההיקף' של הסלקטור ל-subtree של רכיב ההיקף. כן, אמרתי 'היקף' שלוש פעמים!

    scope.querySelectorAll(':scope ul a').length); // 0
    scope.querySelectorAll(':scope body ul a').length); // 0
    scope.querySelectorAll(':scope a').length); // 1

השימוש ב-:scope הופך את הסמנטיקה של השיטות querySelector() לקצת יותר צפויה ותואמת למה שספריות אחרות כמו jQuery כבר עושות.

שיפור בביצועים?

עדיין לא :(

רציתי לדעת אם השימוש ב-:scope ב-qS/qSA משפיע על הביצועים. אז… כמו מהנדס טוב, הרכבת בדיקה. הנימוק שלי: פחות שטח פנים שבו הדפדפן מבצע התאמת סלקטור מאפשר חיפושים מהירים יותר.

בניסוי שלי, ל-WebKit נדרש כרגע פי 1.5-2 יותר זמן מאשר ללא שימוש ב-:scope. ציורים! אחרי שהבעיה ב-crbug.com/222028 תטופל, שימוש בה אמורה להניב תיאורטית שיפור קל בביצועים בהשוואה לשימוש ללא האפשרות הזו.