¿Para qué sirve la pseudoclase CSS :scope?

:scope se define en Selectores CSS 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 "aplicar el alcance" a 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

Probablemente conoces 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 que está filtrado para incluir solo el conjunto de nodos a.) que coincide con el selector y b.) que también son subordinados 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. Más información.

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 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 comparar con jQuery aquí, que toma 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.

Corrige 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. Como un buen ingeniero, creé 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 si no se usara :scope. ¡Demostraciones! Cuando se solucione crbug.com/222028, en teoría, deberías obtener un ligero aumento de rendimiento si lo usas en comparación con no usarlo.