
本文介绍一种不依赖正则表达式的稳健方法,通过遍历 dom 文本节点识别 `@media(...)` 指令,并自动关联其后紧邻的 html 元素,适用于自定义模板引擎中基于内容的元素靶向场景。
在构建轻量级响应式模板引擎时,常需将类似 @media(770) hide 这样的声明式指令直接嵌入 HTML 结构中,并让运行时准确捕获「指令文本」与其作用的目标 DOM 节点(如
核心思路:利用 DOM 树的天然顺序性
@media 指令始终以文本节点(Text Node) 形式存在,且按 HTML 书写顺序,它紧邻其作用的目标元素(Element Node)之前(即目标元素是它的 nextSibling)。因此,我们应放弃字符串解析,转而执行一次深度优先的 DOM 遍历,仅关注 nodeType === 3(文本节点),检查其 nodeValue 是否以 @media 开头,再安全提取指令与目标节点。
以下为生产就绪的实现方案:
/**
* 提取所有 @media(...) 指令及其关联的下一个兄弟元素
* @returns {Array<[string, Element]>} 指令字符串与对应 DOM 元素的二元组数组
*/
function extractMediaDirectives(root = document.body) {
const results = [];
function walk(node) {
for (const child of node.childNodes) {
// 仅处理文本节点
if (child.nodeType === Node.TEXT_NODE) {
const text = child.nodeValue.trim();
if (text.startsWith('@media(')) {
const next = child.nextSibling;
// 确保 nextSibling 是有效元素(跳过空白文本、注释等)
if (next && next.nodeType === Node.ELEMENT_NODE) {
results.push([text, next]);
}
}
}
// 递归遍历子树(包括元素节点的子节点)
else if (child.nodeType === Node.ELEMENT_NODE && child.hasChildNodes()) {
walk(child);
}
}
}
walk(root);
return results;
}
// 使用示例
const directives = extractMediaDirectives();
console.log(directives);
// 输出形如:
// [
// ['@media(1080) mmw-400 mmh-300', ],
// ['@media(770) mmw-300', ],
// ['@media(770) hide', ✅ 优势说明:
- ✅ 零正则依赖:规避 innerHTML 字符串化带来的格式失真(如自动闭合标签、属性重排序、CDATA 处理异常);
- ✅ 语义精准:严格依据 DOM 树结构,确保 @media 文本节点与目标元素在父子/兄弟关系上真实相邻;
- ✅ 健壮容错:自动跳过空白文本、注释节点,仅当 nextSibling 确为元素节点时才收录;
- ✅ 可扩展性强:返回原始指令字符串,便于后续解析(如提取媒体断点值、类名列表)。
? 进阶:转换为目标数据结构
根据问题中期望的嵌套对象格式,可进一步处理 directives 数组:
function buildMediaMap(directives) {
const map = new Map(); // key: element, value: { [breakpoint]: { classes: [] } }
for (const [text, el] of directives) {
// 解析 @media(770) hide → { breakpoint: '770', classes: ['hide'] }
const match = text.match(/@media\((\d+)\)\s+(.+)/);
if (!match) continue;
const [, bp, classStr] = match;
const classes = classStr.trim().split(/\s+/).filter(Boolean);
if (!map.has(el)) map.set(el, {});
const elMap = map.get(el);
elMap[bp] = { classes };
}
// 转为普通对象(可选)
return Object.fromEntries(
Array.from(map, ([el, data]) => [el.tagName, data])
);
}
console.log(buildMediaMap(directives));
// 输出符合要求的结构(键为大写标签名,值为 media 映射)⚠️ 注意事项:
- 确保 @media 指令与目标元素在同一父容器内且无其他元素插入中间(如 @media(770) x中的
会阻断关联);
- 若需支持同一元素多个 @media 指令(如示例中
关联两条),上述 buildMediaMap 已天然支持覆盖合并; - 如需支持指令写在元素内部(如
@media(770) hide... ),需扩展逻辑为查找最近的后续非文本兄弟元素,但需权衡复杂度与模板约束。
此方法将 DOM 视为结构化数据而非字符串,是前端模板引擎、CSS-in-JS 工具及 SSR 预处理中实现“内容即指令”的可靠范式。










