¿Para qué sirve la pseudoclase CSS :scope?

:scope se define en CSS Selectors 4 de la siguiente manera:

Es una pseudoclase que representa cualquier elemento que se encuentra en el conjunto de elementos de referencia contextual. Es un conjunto de elementos especificados de forma explícita (potencialmente vacío), como el que especifica querySelector(), o el elemento superior de un elemento <style scoped>, que se usa para "alcanzar" un selector de modo que solo coincida dentro de un subárbol.

Un ejemplo de uso de esto es dentro de un <style scoped> (más información):

<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>

Esto colorea los elementos li en el primer ul de color rojo y, debido a la regla :scope, coloca un borde alrededor del ul. Esto se debe a que, en el contexto de este <style scoped>, ul coincide con :scope. Es el contexto local. Si agregáramos una regla :scope en el <style> externo, coincidiría con todo el documento. Esencialmente, equivale a :root.

Elementos contextuales

Es probable que conozcas la versión Element de querySelector() y querySelectorAll(). En lugar de consultar todo el documento, puedes restringir el conjunto de resultados a un elemento contextual:

<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>

Cuando se los llama, el navegador muestra un NodeList filtrado para incluir solo el conjunto de nodos que a.) coinciden con el selector y b.) que también son descendientes del elemento de contexto. Por lo tanto, en el segundo ejemplo, el navegador encuentra todos los elementos a y, luego, filtra los que no están en el elemento scope. Esto funciona, pero puede generar un comportamiento extraño si no tienes cuidado. Sigue leyendo.

Cuando querySelector falla

Hay un punto muy importante en la especificación de selectores que las personas suelen pasar por alto. Incluso cuando se invoca querySelector[All]() en un elemento, los selectores aún se evalúan en el contexto de todo el documento. Esto significa que pueden ocurrir situaciones imprevistas:

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

¿Qué rayos está pasando? En el primer ejemplo, ul es mi elemento, pero aún puedo usarlo y coincide con los nodos. En el segundo, body ni siquiera es un descendiente de mi elemento, pero "body ul a" aún coincide. Ambas opciones son confusas y no son lo que esperas.

Vale la pena hacer la comparación con jQuery, que adopta el enfoque correcto y hace lo que esperas:

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

…ingresa :scope para resolver estos problemas semánticos.

Cómo corregir querySelector con :scope

WebKit recientemente lanzó compatibilidad para usar la pseudoclase :scope en querySelector[All](). Puedes probarlo en Chrome Canary 27.

Puedes usarlo para restringir los selectores a un elemento de contexto. Veamos un ejemplo. En el siguiente ejemplo, se usa :scope para “aplicar el alcance” del selector al subárbol del elemento de alcance. Así es, dije alcance tres veces.

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

El uso de :scope hace que la semántica de los métodos querySelector() sea un poco más predecible y esté alineada con lo que otros, como jQuery, ya hacen.

¿Mejoró el rendimiento?

Aún no :(

Me gustaría saber si usar :scope en qS/qSA mejora el rendimiento. Así que, como buen ingeniero, armé una prueba. Mi razonamiento: menos área de superficie para que el navegador realice la coincidencia del selector significa búsquedas más rápidas.

En mi experimento, WebKit actualmente tarda entre 1.5 y 2 veces más que no usar :scope. ¡Maldición! Cuando se corrija crbug.com/222028, en teoría, deberías obtener un ligero aumento de rendimiento si lo usas en comparación con no usarlo.