¿Para qué sirve la pseudoclase CSS :scope?

:scope se define en los selectores CSS 4 de la siguiente manera:

Es una seudoclase que representa cualquier elemento que se encuentra en el conjunto de elementos de referencia contextual. Este es un conjunto de elementos especificado de forma explícita (potencialmente vacío), como el especificado por querySelector(), o el elemento superior de un elemento <style scoped>, que se usa para "limitar" un selector de modo que solo coincida con un subárbol.

Un ejemplo de uso de esto es dentro de una <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>

De esta manera, los elementos li del primer ul se pintan de rojo y, debido a la regla :scope, se coloca un borde alrededor de ul. Esto se debe a que, en el contexto de esta <style scoped>, la ul coincide con :scope. Es el contexto local. Si agregáramos una regla :scope en el <style> externo, coincidiría con todo el documento. En esencia, 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 que se filtra para incluir solo el conjunto de nodos que a.) coinciden con el selector y b.) que también son descendientes del elemento de contexto. 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 muy punto importante en la especificación de los 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 cosas 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 la segunda, body ni siquiera es un descendiente de mi elemento, pero "body ul a" igual coincide. Ambas opciones son confusas y no son lo que esperas.

Vale la pena hacer una comparación con jQuery aquí, 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 estas travesuras semánticas.

Cómo corregir querySelector con :scope

WebKit recibió compatibilidad con el uso de la seudoclase :scope en querySelector[All](). Puedes probarlo en Chrome Canary 27.

Puedes usarlo para restringir selectores a un elemento de contexto. Veamos un ejemplo. A continuación, :scope se usa para "limitar" el selector con respecto al subárbol del elemento de alcance. Así es. Dije de alcance tres veces.

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

Usar :scope hace que la semántica de los métodos querySelector() sea un poco más predecible y se alinea con lo que otros como jQuery ya hacen.

¿Ganaste en el rendimiento?

Aún no :(

Tenía curiosidad por saber si usar :scope en qS/qSA aumentaba el rendimiento. Entonces... como un buen ingeniero, hice una prueba. La razón:

En mi experimento, WebKit tarda entre 1.5 y 2 veces más tiempo que si no usara :scope. ¡Datos! Cuando se corrige crbug.com/222028, en teoría, su uso debería aumentar ligeramente el rendimiento en comparación con no usarlo.