在前端开发中,我们经常需要处理 HTML 字符串或 XML 数据。直接使用 innerHTML 存在安全和性能隐患,而 DOMParser API 提供了更安全、更可控的解析方式。本文将从基础用法到实战案例,全面讲解 DOMParser API。
什么是 DOMParser?
DOMParser 是一个浏览器内置的 API,可以将 HTML 或 XML 字符串解析为 DOM 文档(Document 对象)。解析后的文档可以通过标准的 DOM API(如 querySelector、getElementById)进行操作。
// 基本用法
const parser = new DOMParser();
const doc = parser.parseFromString('<h1>Hello World</h1>', 'text/html');
console.log(doc.querySelector('h1').textContent); // "Hello World"vs innerHTML
大多数开发者习惯用 innerHTML 来解析 HTML:
const container = document.createElement('div');
container.innerHTML = '<p>Hello World</p>';但这有几个问题:
- 副作用:
innerHTML会修改当前文档的 DOM - 性能:频繁操作真实 DOM 开销大
- 安全:插入的内容可能包含脚本执行
DOMParser 则解析到一个全新的、独立的文档中,不会影响当前页面。
| 特性 | innerHTML | DOMParser |
|---|---|---|
| 解析成独立文档 | ❌ 需创建临时元素 | ✅ 返回完整 Document |
| 影响页面 | ✅ 直接插入当前 DOM | ❌ 不影响当前页面 |
| 解析 XML | ❌ 不支持 | ✅ 支持 |
| SVG 解析 | ⚠️ 部分支持 | ✅ image/svg+xml |
| 错误处理 | ❌ 静默失败 | ✅ parseError 元素 |
| 性能 | 一般(操作真实 DOM) | 🟢 更好(虚拟文档) |
支持的 MIME 类型
DOMParser.parseFromString() 的第二个参数指定 MIME 类型,决定了如何解析输入的字符串:
| MIME 类型 | 解析方式 | 返回文档类型 | 使用场景 |
|---|---|---|---|
text/html |
HTML 解析器 | HTMLDocument |
解析 HTML 片段或完整页面 |
text/xml |
XML 解析器 | XMLDocument |
解析 XML 数据 |
application/xml |
XML 解析器 | XMLDocument |
与 text/xml 相同 |
application/xhtml+xml |
XHTML 解析器 | XMLDocument |
解析 XHTML |
image/svg+xml |
XML 解析器 | XMLDocument |
解析 SVG |
const parser = new DOMParser();
// 解析 HTML
const htmlDoc = parser.parseFromString('<p>Hello</p>', 'text/html');
// 解析 XML
const xmlDoc = parser.parseFromString('<root><item id="1"/></root>', 'text/xml');
// 解析 SVG
const svgDoc = parser.parseFromString(
'<svg xmlns="http://www.w3.org/2000/svg"><circle r="50"/></svg>',
'image/svg+xml'
);核心用法
1. 解析 HTML 片段
const parser = new DOMParser();
const htmlString = `
<div class="card">
<img src="photo.jpg" alt="照片">
<h2 class="title">标题</h2>
<p class="desc">描述文字</p>
</div>
`;
const doc = parser.parseFromString(htmlString, 'text/html');
// 注意:HTML 文档会自动包含 <html><head><body> 等元素
// 直接从 body 中取内容
const card = doc.body.firstElementChild;
console.log(card.querySelector('.title').textContent); // "标题"2. 解析完整的 HTML 文档
const parser = new DOMParser();
const fullHtml = `
<!DOCTYPE html>
<html>
<head>
<title>测试页面</title>
<meta charset="UTF-8">
</head>
<body>
<main>
<h1>页面标题</h1>
<p>页面内容</p>
</main>
</body>
</html>
`;
const doc = parser.parseFromString(fullHtml, 'text/html');
console.log(doc.title); // "测试页面"
console.log(doc.querySelector('h1').textContent); // "页面标题"3. 解析 XML 数据
const parser = new DOMParser();
const xmlString = `
<?xml version="1.0" encoding="UTF-8"?>
<bookstore>
<book category="编程">
<title lang="zh">JavaScript 高级程序设计</title>
<author>张三</author>
<price>59.00</price>
</book>
<book category="前端">
<title lang="zh">CSS 权威指南</title>
<author>李四</author>
<price>49.00</price>
</book>
</bookstore>
`;
const xmlDoc = parser.parseFromString(xmlString, 'text/xml');
// 使用 DOM API 访问 XML 数据
const books = xmlDoc.querySelectorAll('book');
books.forEach(book => {
const title = book.querySelector('title').textContent;
const author = book.querySelector('author').textContent;
console.log(`《${title}》- ${author}`);
});
// 《JavaScript 高级程序设计》- 张三
// 《CSS 权威指南》- 李四4. 错误处理
const parser = new DOMParser();
// 故意传入格式错误的 XML
const badXml = '<root><item>broken</roo';
const doc = parser.parseFromString(badXml, 'text/xml');
// 检查是否解析成功
const parseError = doc.querySelector('parsererror');
if (parseError) {
console.error('XML 解析失败:', parseError.textContent);
// 包含错误信息和位置
} else {
console.log('解析成功');
}
// 对于 HTML,解析器非常宽容,几乎不会失败
const htmlDoc = parser.parseFromString('<p>unclosed', 'text/html');
// HTML 解析器会自动补全标签,结果是一个完整的文档DOMParser vs innerHTML 安全性对比
XSS 风险对比
const malicious = '<img src=x onerror=alert("hacked")>';
// ❌ innerHTML —— 高危!会执行 onerror 中的脚本
const div = document.createElement('div');
div.innerHTML = malicious;
// 脚本被执行!alert("hacked")
// ✅ DOMParser —— 安全!不会执行脚本
const parser = new DOMParser();
const doc = parser.parseFromString(malicious, 'text/html');
// 解析成功,但脚本不会执行
// 但当把解析结果插入到 DOM 时,要特别注意
document.body.appendChild(doc.body.firstElementChild);
// ⚠️ 如果使用 append 或 appendChild,还是会触发脚本!如何安全地使用解析结果?
// 安全做法:只提取文本内容
const safeHtml = '<p onclick="alert(1)">Hello <strong>World</strong></p>';
const doc = parser.parseFromString(safeHtml, 'text/html');
// ✅ 安全:只提取文本
const text = doc.body.textContent;
console.log(text); // "Hello World"
// ✅ 安全:移除事件属性后再使用
const cleanHtml = doc.body.innerHTML.replace(/ on\w+="[^"]*"/g, '');
// 手动创建 DOM 元素(安全)
const el = document.createElement('div');
el.innerHTML = cleanHtml;
// ⚠️ 相对安全但需注意:限制允许的标签
function sanitize(html) {
const doc = parser.parseFromString(html, 'text/html');
// 移除所有脚本和事件
const walker = doc.createTreeWalker(doc.body, NodeFilter.SHOW_ELEMENT);
while (walker.nextNode()) {
const node = walker.currentNode;
// 移除事件属性
Array.from(node.attributes).forEach(attr => {
if (/^on/i.test(attr.name)) {
node.removeAttribute(attr.name);
}
});
}
return doc.body.innerHTML;
}
// ❌ 不安全的标签
['script', 'iframe', 'embed', 'object'].forEach(tag => {
// 在 DOMParser 解析后这些标签存在但不会执行
// 只有插入 DOM 才会执行
});最佳实践:只提取需要的内容
function extractTextFromHtml(html) {
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
return doc.body.textContent || '';
}
function extractLinks(html) {
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
return Array.from(doc.querySelectorAll('a[href]')).map(a => ({
text: a.textContent,
href: a.getAttribute('href')
}));
}实战:RSS 解析器
下面实现一个完整的 RSS 解析器:
class RSSParser {
/**
* 解析 RSS XML 字符串
* @param {string} xmlString - RSS 订阅源 XML
* @returns {Array} 文章列表
*/
static parse(xmlString) {
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(xmlString, 'text/xml');
// 检查解析错误
const parseError = xmlDoc.querySelector('parsererror');
if (parseError) {
throw new Error(`XML 解析失败: ${parseError.textContent}`);
}
// 获取频道信息
const channel = xmlDoc.querySelector('channel');
const feedTitle = channel.querySelector('title')?.textContent || '未知源';
const feedDesc = channel.querySelector('description')?.textContent || '';
// 解析文章列表
const items = channel.querySelectorAll('item');
const articles = Array.from(items).map(item => ({
title: item.querySelector('title')?.textContent || '',
link: item.querySelector('link')?.textContent || '',
description: this._cleanHtml(item.querySelector('description')?.textContent || ''),
pubDate: new Date(item.querySelector('pubDate')?.textContent || Date.now()),
author: item.querySelector('author')?.textContent || '',
categories: Array.from(item.querySelectorAll('category')).map(c => c.textContent),
guid: item.querySelector('guid')?.textContent || ''
}));
return {
title: feedTitle,
description: feedDesc,
articles: articles.sort((a, b) => b.pubDate - a.pubDate)
};
}
/**
* 从 URL 获取并解析 RSS
* @param {string} url - RSS 订阅源 URL
* @returns {Promise<Object>} 解析结果
*/
static async fetchAndParse(url) {
const response = await fetch(url, {
headers: {
'Accept': 'application/rss+xml, application/xml, text/xml'
}
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: 获取 RSS 失败`);
}
const xmlText = await response.text();
return this.parse(xmlText);
}
/**
* 清理 HTML(仅保留文本)
*/
static _cleanHtml(html) {
if (!html) return '';
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
return doc.body.textContent?.trim() || '';
}
}
// 使用示例
async function displayRSSFeed(url) {
const container = document.getElementById('rss-feed');
container.innerHTML = '<p>加载中...</p>';
try {
const feed = await RSSParser.fetchAndParse(url);
container.innerHTML = `
<h2>${feed.title}</h2>
<p>${feed.description}</p>
<ul>
${feed.articles.slice(0, 10).map(article => `
<li>
<a href="${article.link}" target="_blank" rel="noopener">
${article.title}
</a>
<small>${article.pubDate.toLocaleDateString('zh-CN')}</small>
</li>
`).join('')}
</ul>
`;
} catch (error) {
container.innerHTML = `<p style="color: red">错误: ${error.message}</p>`;
}
}
// 调用
displayRSSFeed('https://example.com/feed.xml');DOMParser 的其他应用场景
场景 1:模板渲染
function renderTemplate(template, data) {
const parser = new DOMParser();
let html = template;
// 替换模板变量
for (const [key, value] of Object.entries(data)) {
html = html.replace(new RegExp(`\\$\\{${key}\\}`, 'g'), value);
}
const doc = parser.parseFromString(html, 'text/html');
return doc.body.children;
}
const template = '<p>你好,${name}!你的订单 ${orderId} 已确认。</p>';
const result = renderTemplate(template, {
name: '张三',
orderId: 'ORD-2023-001'
});
// <p>你好,张三!你的订单 ORD-2023-001 已确认。</p>场景 2:提取页面元数据
function extractMetaData(htmlString) {
const parser = new DOMParser();
const doc = parser.parseFromString(htmlString, 'text/html');
return {
title: doc.title,
description: doc.querySelector('meta[name="description"]')?.content || '',
keywords: doc.querySelector('meta[name="keywords"]')?.content?.split(',').map(s => s.trim()) || [],
ogImage: doc.querySelector('meta[property="og:image"]')?.content || '',
links: Array.from(doc.querySelectorAll('a[href]')).map(a => a.href),
images: Array.from(doc.querySelectorAll('img[src]')).map(img => img.src)
};
}场景 3:SVG 动态生成
function createSVGIcon(name, size = 24) {
const parser = new DOMParser();
const svgString = `
<svg xmlns="http://www.w3.org/2000/svg"
width="${size}" height="${size}" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="10" fill="#4A90D9"/>
<text x="12" y="16" text-anchor="middle"
fill="white" font-size="12">${name[0]}</text>
</svg>
`;
const doc = parser.parseFromString(svgString, 'image/svg+xml');
return doc.documentElement; // 返回 SVG 元素
}
// 使用
const svgIcon = createSVGIcon('A', 32);
document.body.appendChild(svgIcon);场景 4:Markdown 转 HTML(纯前端)
// 简单 Markdown 转 HTML(使用 DOMParser 安全解析结果)
function simpleMarkdownToHTML(md) {
let html = md
.replace(/^### (.*$)/gm, '<h3>$1</h3>')
.replace(/^## (.*$)/gm, '<h2>$1</h2>')
.replace(/^# (.*$)/gm, '<h1>$1</h1>')
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
.replace(/\*(.*?)\*/g, '<em>$1</em>')
.replace(/!\[(.*?)\]\((.*?)\)/g, '<img alt="$1" src="$2">')
.replace(/\[(.*?)\]\((.*?)\)/g, '<a href="$2">$1</a>')
.replace(/\n\n/g, '</p><p>')
.replace(/\n/g, '<br>');
html = '<p>' + html + '</p>';
// 用 DOMParser 验证和清理
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
return doc.body.innerHTML;
}浏览器兼容性
所有现代浏览器都支持 DOMParser,包括 IE10+。
// 简易 polyfill(非 IE 时代已不太需要)
if (!window.DOMParser) {
throw new Error('当前浏览器不支持 DOMParser API');
}总结
DOMParser API 是浏览器中解析 HTML/XML 的强大工具:
- 独立解析:在虚拟文档中解析,不影响当前页面
- 多格式支持:HTML、XML、SVG、XHTML
- 安全性:比
innerHTML更安全(不会执行脚本) - 错误处理:XML 解析失败时有明确的错误信息
- 标准 DOM API:解析结果可以通过标准方法操作
使用场景包括但不限于:
- HTML 片段解析与清理
- RSS/XML 数据解析
- SVG 动态生成
- 模板渲染
- 元数据提取
扩展阅读
- MDN: DOMParser
- MDN: XMLSerializer —— DOMParser 的逆操作
- HTML 清理 API
- XSS 防御指南