虚拟节点是普通JS对象,通过对象属性和递归遍历模拟DOM操作:用tag/props/children描述结构,querySelectorAll支持#id/.class/标签/后代选择器,setAttribute/appendChild等直接修改属性或数组。

在 JavaScript 中,虚拟节点(Virtual Node)本身不是真实 DOM,无法直接用 querySelector 或 appendChild 操作。但可以通过构建轻量级的虚拟 DOM 结构(如用普通对象或类模拟节点树),再配合自定义方法来模拟 DOM 查询与操作逻辑。核心思路是:**用数据结构描述节点,用递归/遍历实现查询,用对象属性变更模拟修改行为**。
用 Plain Object 构建可查询的虚拟节点
不依赖框架,可用简洁对象表示虚拟节点,包含类型、属性、子节点等关键字段:
-
tag:标签名(如
"div"、"span") -
props:对象,存
id、class、dataset等属性 - children:数组,元素为字符串(文本)或其它虚拟节点对象
例如:
vnode = {
tag: "div",
props: { id: "app", class: "container", "data-role": "main" },
children: [
{ tag: "h1", props: {}, children: ["Hello"] },
{ tag: "p", props: { class: "desc" }, children: [] }
]
}模拟 querySelector / querySelectorAll
写一个递归查找函数,支持简单选择器(如 #id、.class、div、div p):
立即学习“Java免费学习笔记(深入)”;
- 对单层选择器(如
.btn),遍历所有节点,检查props.class是否含该类名 - 对 ID(
#header),检查props.id === 值 - 对标签名(
li),检查tag === 值 - 对后代选择器(
ul li),先找所有ul,再在其子树中找li(需支持深度优先遍历)
示例(简化版 querySelectorAll):
在调试 Java 后端 API 故障时使用此技能,尤其适用于以下任务:分析日志、跨 Controller、Service 和 Mapper 层追踪请求、定位异常根因、检查 SQL 执行或参数流转,以及生成简洁的诊断结论与修复方案。
function querySelectorAll(vnode, selector) {
const results = [];
const tokens = selector.trim().split(/\s+/); // 支持空格分隔的后代选择器
<p>function walk(node, depth = 0) {
if (!node || typeof node !== 'object' || !node.tag) return;</p><pre class='brush:php;toolbar:false;'>const token = tokens[depth];
if (!token) return;
if (match(node, token)) {
if (depth === tokens.length - 1) {
results.push(node);
} else {
node.children?.forEach(child => {
if (typeof child === 'object') walk(child, depth + 1);
});
}
}
// 即使当前不匹配,也继续搜索子树(用于后代选择器)
node.children?.forEach(child => {
if (typeof child === 'object') walk(child, depth);
});}
function match(node, token) { if (token.startsWith('#')) return node.props?.id === token.slice(1); if (token.startsWith('.')) { const cls = node.props?.class || ''; return cls.split(/\s+/).includes(token.slice(1)); } return node.tag === token; }
walk(vnode); return results; }
模拟 setAttribute / getTextContent / appendChild
虚拟节点是普通 JS 对象,所有“操作”本质是修改其属性:
-
setAttribute:直接赋值到
vnode.props[key] = value - getTextContent:递归收集所有字符串子节点并拼接
-
appendChild:向
vnode.children数组push新节点或字符串 -
removeChild:用
filter或splice从children中移除指定项(注意需判断引用相等)
例如:
// 添加 class
vnode.props.class = (vnode.props.class || "") + " active";
<p>// 获取文本内容
function getTextContent(vnode) {
if (typeof vnode === 'string') return vnode;
if (!vnode.children) return "";
return vnode.children.map(c => typeof c === 'string' ? c : getTextContent(c)).join("");
}</p><p>// 追加子节点
vnode.children.push({ tag: "span", props: {}, children: ["new"] });进阶:封装成类提升可用性
把上述能力封装为 VNode 类,让调用更自然:
class VNode {
constructor(tag, props = {}, children = []) {
this.tag = tag;
this.props = props;
this.children = Array.isArray(children) ? children : [children];
}
<p>querySelector(selector) {
const res = querySelectorAll(this, selector);
return res[0] || null;
}</p><p>querySelectorAll(selector) {
return querySelectorAll(this, selector);
}</p><p>setAttribute(key, value) {
this.props[key] = value;
}</p><p>appendChild(child) {
this.children.push(child);
}</p><p>toString() {
// 可选:生成 HTML 字符串用于调试或渲染
}
}之后即可链式调用:node.querySelector("p").setAttribute("hidden", "")。

















