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

אריק בידלמן

:scope מוגדר ב-סלקטורים ב-CSS 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 שמסונן כך שיכלול רק את קבוצת הצמתים א.) שתואמים לבורר ו-b.) שהם גם צאצאים של רכיב ההקשר. בדוגמה השנייה הדפדפן מוצא את כל רכיבי 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 בשימוש בפסאודו-מחלקה :scope ב-querySelector[All](). אפשר לבדוק אותה ב-Chrome Canary 27.

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

    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 תתוקן, השימוש בו אמור לשפר באופן תיאורטי את הביצועים הטובים ביותר בהשוואה לאי-שימוש.