למה משמש פסאודו-מחלקה של 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]() על רכיב, בוררי הבחירה עדיין מבצעים הערכה בהקשר של כל המסמך. כלומר, יכולים לקרות דברים בלתי צפויים:

    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 תמיכה בשימוש בסוגי המשנה (pseudo-class) :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 יותר זמן מאשר ללא שימוש ב-:scope. נורא! אחרי שהבעיה ב-crbug.com/222028 תטופל, שימוש בה אמורה להניב תיאורטית שיפור קל בביצועים בהשוואה לשימוש ללא ההגדרה הזו.