在上一章中,我们认识了 CSSOM 是什么,以及它在浏览器渲染流程中的位置。现在,是时候真正动手操作了。
如果说 CSSOM 是一张“样式地图”,那么第一步就是要学会 如何拿到这张地图——也就是如何用 JavaScript 获取到文档中的样式表对象。
你可能会有疑问:我平时用 element.style.color = 'red' 不就能改样式了吗?为什么要费劲去获取样式表?
这是个好问题。element.style 操作的是 单个元素的内联样式,影响范围有限。而通过样式表对象,我们可以 直接修改样式表本身——比如一次性改变页面中所有 .btn 按钮的颜色,或者动态添加一条全新的样式规则。这种“从源头操控”的能力,是 element.style 无法替代的。
本章将带你掌握四种获取样式表的核心方法,并深入理解样式表对象的结构。准备好了吗?我们开始。
一、document.styleSheets —— 通往所有样式表的入口
获取文档中所有样式表最直接的方式,就是使用 document.styleSheets 属性。
const sheets = document.styleSheets;
console.log(sheets); // 输出一个 StyleSheetList 对象document.styleSheets 返回一个 StyleSheetList 对象。你可以把它理解为一个“样式表列表”,里面按文档顺序存放着当前页面所有的样式表——无论是通过 <link> 引入的外部样式表,还是通过 <style> 标签定义的内部样式表,都会被收录其中。
如何访问具体的样式表?
StyleSheetList 是一个 类数组对象,这意味着你可以通过索引来访问其中的每一项:
// 获取文档中的第一个样式表
const firstSheet = document.styleSheets[0];
// 获取样式表的数量
const count = document.styleSheets.length;
console.log(`页面共有 ${count} 个样式表`);重要提示: StyleSheetList 虽然长得像数组,但它 不是 真正的数组,不能直接使用 map()、forEach() 等数组方法。不过,你可以用 for...of 循环来遍历它:
// 正确:使用 for...of 遍历
for (const sheet of document.styleSheets) {
console.log(sheet.href || '内联样式表');
}
// 错误:StyleSheetList 没有 forEach 方法
// document.styleSheets.forEach(sheet => console.log(sheet)); // 报错!
// 变通方案:转换为数组后再使用数组方法
const sheetArray = [...document.styleSheets];
sheetArray.forEach(sheet => console.log(sheet.href));关于“构造样式表”的补充
值得一提的是,除了传统的 <link> 和 <style> 方式,现在还可以通过 new CSSStyleSheet() 构造函数以编程方式创建样式表。这类“构造样式表”需要通过 document.adoptedStyleSheets 来挂载,不会出现在 document.styleSheets 中。这是较新的特性,有兴趣可以后续深入了解,但本章我们聚焦于传统的样式表获取方式。
二、通过 <style> 元素的 sheet 属性获取关联样式表
除了从 document.styleSheets 统一获取,还有一种更精确的方式: 直接通过 <style> 元素的 sheet 属性获取它关联的样式表对象。
<style id="my-style">
.btn { background: blue; color: white; }
.btn:hover { background: darkblue; }
</style>
<script>
const styleEl = document.getElementById('my-style');
const sheet = styleEl.sheet; // 获取与该 <style> 元素关联的样式表对象
console.log(sheet.cssRules); // 输出该样式表中的所有规则
</script>sheet 属性是 只读 的,返回一个 StyleSheet 对象(更准确地说是 CSSStyleSheet 对象)。如果该 <style> 元素的 type 属性不是 text/css,则 sheet 会返回 null。
这种方法的好处是 精准定位——当页面有多个样式表时,你不需要通过索引去猜测哪个是哪个,直接通过 ID 或选择器获取对应的 <style> 元素,就能拿到它的样式表。
对于 <link> 元素呢?
你可能会问:<link> 元素有没有类似的属性?答案是有的。<link> 元素对应的接口是 HTMLLinkElement,它同样有一个 sheet 属性,用法完全一致:
<link id="main-css" rel="stylesheet" href="styles/main.css">
<script>
const linkEl = document.getElementById('main-css');
const sheet = linkEl.sheet;
console.log(sheet.href); // 输出 "styles/main.css"
</script>所以,无论是 <style> 还是 <link>,你都可以通过元素的 sheet 属性直接获取其关联的样式表对象。
三、样式表对象的基本属性与结构
当你成功获取到一个样式表对象后,它到底长什么样?我们来看看 CSSStyleSheet 接口的核心属性和结构。
CSSStyleSheet 的继承关系
CSSStyleSheet 接口代表一个 CSS 样式表,它 继承自父接口 StyleSheet。也就是说,它既有来自 StyleSheet 的基础属性,也有自己特有的属性和方法。
来自 StyleSheet 的基础属性
属性 | 类型 | 描述 |
|---|---|---|
href | 字符串 | 外部样式表的 URL;如果是内联样式表则返回 null |
title | 字符串 | 样式表的标题(通过 <link title="..."> 或 <style title="..."> 设置) |
type | 字符串 | 样式表的 MIME 类型,通常是 "text/css" |
parentStyleSheet | 对象 | 如果该样式表是通过 @import 导入的,则指向父样式表 |
ownerNode | 节点 | 拥有该样式表的 DOM 节点(<style> 或 <link> 元素) |
disabled | 布尔值 | 表示该样式表是否被禁用(可读写) |
CSSStyleSheet 特有的属性
属性 | 类型 | 描述 |
|---|---|---|
cssRules | CSSRuleList | 包含样式表中所有规则(CSSRule 对象)的实时列表 |
ownerRule | CSSImportRule | 如果该样式表是通过 @import 导入的,返回对应的导入规则;否则返回 null |
其中 cssRules 是最常用的属性。它返回一个 CSSRuleList,里面按顺序存放着样式表中的每一条规则。你可以通过索引访问单条规则:
const sheet = document.styleSheets[0];
const rules = sheet.cssRules;
// 获取第一条规则
const firstRule = rules[0];
console.log(firstRule.cssText); // 输出规则的完整 CSS 文本
// 遍历所有规则
for (const rule of rules) {
console.log(rule.selectorText, rule.style);
}小贴士: cssRules 返回的是一个 实时的列表。也就是说,如果你通过 insertRule() 或 deleteRule() 修改了样式表,cssRules 会自动反映最新的状态,不需要重新获取。
一个完整示例
来看一个综合示例,展示如何获取样式表并读取其中的信息:
<style id="theme">
body { background: #f0f0f0; font-family: Arial, sans-serif; }
.container { max-width: 1200px; margin: 0 auto; }
.btn { padding: 10px 20px; border: none; border-radius: 4px; }
</style><script>
const sheet = document.getElementById('theme').sheet;
// 基础属性
console.log('样式表类型:', sheet.type); // "text/css"
console.log('是否禁用:', sheet.disabled); // false
console.log('所属节点:', sheet.ownerNode); // <style id="theme">
console.log('外部 URL:', sheet.href); // null(因为是内联样式表)
// 读取规则
console.log('规则数量:', sheet.cssRules.length); // 3
for (let i = 0; i < sheet.cssRules.length; i++) {
const rule = sheet.cssRules[i];
console.log(`规则 ${i}: ${rule.selectorText} => ${rule.style.cssText}`);
}
// 输出:
// 规则 0: body => background: rgb(240, 240, 240); font-family: Arial, sans-serif;
// 规则 1: .container => max-width: 1200px; margin: 0px auto;
// 规则 2: .btn => padding: 10px 20px; border: none; border-radius: 4px;
</script>四、如何判断样式表是内联还是外部引入?
在实际开发中,我们经常需要区分样式表是通过 <link> 外部引入的,还是通过 <style> 内联定义的。这可以通过 href 属性和 ownerNode 属性来判断。
方法一:通过 href 属性
外部样式表(通过 <link> 引入)的 href 属性会返回完整的 URL 字符串;而 内联样式表(通过 <style> 定义)的 href 属性则为 null。
const sheets = document.styleSheets;
for (const sheet of sheets) {
if (sheet.href) {
console.log('外部样式表:', sheet.href);
} else {
console.log('内联样式表(<style> 或动态创建的)');
}
}方法二:通过 ownerNode 属性
ownerNode 属性返回拥有该样式表的 DOM 节点。通过检查 ownerNode.tagName,可以精确判断是 <link> 还是 <style>:
for (const sheet of document.styleSheets) {
const node = sheet.ownerNode;
if (node.tagName === 'LINK') {
console.log('通过 <link> 引入的外部样式表:', sheet.href);
} else if (node.tagName === 'STYLE') {
console.log('通过 <style> 定义的内部样式表');
} else {
console.log('其他方式创建的样式表');
}
}实际应用场景
为什么要区分这两者?一个常见的场景是:动态修改样式时,避免误操作外部 CDN 样式表。
假设你的页面引入了第三方 UI 库的样式(来自 CDN),同时也定义了自己的覆盖样式。如果你遍历所有样式表并批量修改规则,可能会意外破坏第三方库的样式。这时候,通过判断 href 是否包含特定域名,或者通过 ownerNode 的 ID 或 class 来筛选,就能精准锁定目标样式表。
// 只修改我们自己定义的样式表(假设我们的 <style> 标签有 id="app-styles")
const targetSheet = document.getElementById('app-styles').sheet;
// 然后安全地操作 targetSheet.cssRules...五、小结
本章我们学习了获取和访问样式表的四种核心方法,以及样式表对象的基本结构:
document.styleSheets:获取文档中所有样式表的统一入口,返回一个 StyleSheetList 类数组对象。
元素的 sheet 属性:通过 <style> 或 <link> 元素的 sheet 属性,精准获取其关联的样式表对象。
样式表对象的结构:CSSStyleSheet 继承自 StyleSheet,核心属性包括 href、ownerNode、disabled、cssRules 和 ownerRule。其中 cssRules 包含了样式表中的所有规则,是我们后续操作的重点。
4. 判断样式表类型:通过 href 是否为 null,以及 ownerNode.tagName 是 LINK 还是 STYLE,可以准确区分外部样式表和内联样式表。
掌握了这些获取样式表的方法,你就拿到了操作 CSSOM 的“入场券”。在下一章中,我们将深入 CSSRule 的世界,学习如何读取、添加、修改和删除样式表中的具体规则——那才是真正“动态操控样式”的核心战场。