跟着 MDN 学 HTML day_52:(深入 XPathExpression 接口)
在前一篇文章中我们详细学习了 XPathEvaluator 接口了解了如何创建 XPath 表达式求值器并执行查询。今天我们将继续深入探讨 XPathExpression 接口。这个接口代表一个已经编译好的 XPath 表达式可以被重复用于在文档或特定节点上进行求值。XPathExpression 的核心价值在于预编译。当我们需要在应用中多次执行同一个 XPath 查询时使用预编译的表达式对象可以避免每次重新解析 XPath 字符串从而提升性能。同时表达式中出现的所有命名空间前缀也会被预先解析进一步提高了效率。一、XPathExpression 概述XPathExpression 是一个基线广泛可用的接口它代表一个经过编译的 XPath 表达式。编译后的表达式可以在文档或特定节点上求值以从 DOM 树中获取所需的信息。这个接口特别适用于表达式会被重复使用的场景。由于表达式只编译一次所有内部的前缀和路径解析工作都会在编译阶段完成后续每次求值时就可以直接使用大幅减少了重复计算的开销。示例XPathExpression 的创建和基本使用divXPath example/divdivNumber ofdivs:output/output/divconstxpath//div;constevaluatornewXPathEvaluator();// 通过 createExpression 创建编译后的表达式对象constexpressionevaluator.createExpression(xpath);// 在文档上执行编译后的表达式constresultexpression.evaluate(document,XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);// 显示匹配的 div 数量document.querySelector(output).textContentresult.snapshotLength;在这个例子中我们首先创建了一个 XPathEvaluator 实例然后通过 createExpression() 方法将字符串 “//div” 编译为一个 XPathExpression 对象。之后调用该对象的 evaluate() 方法即可在文档上执行查询。二、XPathExpression.evaluate() 方法定义evaluate() 是 XPathExpression 接口唯一的实例方法。它用于在给定的节点或文档上执行编译好的 XPath 表达式并返回一个 XPathResult 对象。语法evaluate(contextNode)evaluate(contextNode,type)evaluate(contextNode,type,result)参数说明contextNode作为求值上下文的节点表达式将相对于此节点进行求值type可选指定返回结果的类型必须是 XPathResult 的常量之一result可选允许指定一个可复用的结果对象。如果传入 null 或实现不支持复用将返回新的结果对象示例在不同上下文中使用 evaluatedivclasscontainerpclassitem第一项/ppclassitem第二项/p/divdivclasscontainerpclassitem第三项/p/divdiv总计outputidtotal/output/divdiv第一个容器outputidfirst/output/divconstevaluatornewXPathEvaluator();// 编译表达式查找所有 class 为 item 的 p 元素constexpressionevaluator.createExpression(//p[classitem]);// 在整个文档上求值consttotalResultexpression.evaluate(document,XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);document.getElementById(total).textContenttotalResult.snapshotLength;// 输出3// 在第一个容器上求值constfirstContainerdocument.querySelector(.container);constfirstResultexpression.evaluate(firstContainer,XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);document.getElementById(first).textContentfirstResult.snapshotLength;// 输出2这个例子清晰地展示了编译后的表达式可以在不同的上下文节点上执行每次都能得到相对于该上下文的正确结果。这种灵活性使得 XPathExpression 在处理复杂 DOM 结构时非常实用。三、evaluate 方法的返回值evaluate() 方法始终返回一个 XPathResult 对象该对象的具体结构取决于 type 参数的设置。了解不同的结果类型能帮助我们更准确地获取所需数据。常用结果类型回顾XPathResult.NUMBER_TYPE返回数值通过 numberValue 属性获取XPathResult.STRING_TYPE返回字符串通过 stringValue 属性获取XPathResult.BOOLEAN_TYPE返回布尔值通过 booleanValue 属性获取XPathResult.ORDERED_NODE_SNAPSHOT_TYPE返回节点快照列表XPathResult.FIRST_ORDERED_NODE_TYPE返回第一个匹配节点XPathResult.ANY_TYPE由表达式自然决定类型示例使用不同类型获取结果divclassproductdata-price50键盘/divdivclassproductdata-price120鼠标/divdivclassproductdata-price80耳机/divdivp商品数量outputidcount/output/pp总价格outputidsum/output/pp第一个商品outputidfirst-product/output/pp有超过100元的商品outputidhas-expensive/output/p/divconstevaluatornewXPathEvaluator();// 编译多个表达式用于不同目的constcountExpressionevaluator.createExpression(count(//div[classproduct]));constsumExpressionevaluator.createExpression(sum(//div[classproduct]/data-price));constfirstExpressionevaluator.createExpression(//div[classproduct][1]);constboolExpressionevaluator.createExpression(boolean(//div[data-price 100]));// 获取商品数量constcountResultcountExpression.evaluate(document,XPathResult.NUMBER_TYPE);document.getElementById(count).textContentcountResult.numberValue;// 输出3// 获取总价格constsumResultsumExpression.evaluate(document,XPathResult.NUMBER_TYPE);document.getElementById(sum).textContentsumResult.numberValue;// 输出250// 获取第一个商品的文本constfirstResultfirstExpression.evaluate(document,XPathResult.STRING_TYPE);document.getElementById(first-product).textContentfirstResult.stringValue;// 输出键盘// 判断是否存在超过100元的商品constboolResultboolExpression.evaluate(document,XPathResult.BOOLEAN_TYPE);document.getElementById(has-expensive).textContentboolResult.booleanValue?是:否;// 输出是通过预编译不同的表达式并指定合适的结果类型我们可以高效地获取各种形式的结果。这种预编译加多类型返回的模式让 XPath 查询在实际应用中非常灵活。四、异常处理在调用 evaluate() 方法时如果传入的参数不符合要求可能会抛出多种类型的 DOMException 异常。了解这些异常有助于我们编写更健壮的代码。常见异常类型INVALID_EXPRESSION_ERR表达式不合法TYPE_ERR结果无法转换为指定的类型NAMESPACE_ERR表达式中的命名空间前缀无法解析WRONG_DOCUMENT_ERR提供的上下文节点来自不被支持的文档NOT_SUPPORTED_ERR上下文节点类型不被允许或请求类型不被允许示例捕获 evaluate 方法的异常constevaluatornewXPathEvaluator();constexpressionevaluator.createExpression(//div);// 正常执行try{constresultexpression.evaluate(document,XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);console.log(执行成功找到节点数,result.snapshotLength);}catch(e){console.error(执行失败,e.name,e.message);}// 传入非法类型参数try{constresultexpression.evaluate(document,999// 非法的类型值);}catch(e){console.error(类型错误,e.name);// 输出类型错误NotSupportedError 或 TypeError}在实际开发中特别是当 XPath 表达式来自用户输入或外部配置时做好异常捕获非常重要可以防止程序因为无效的表达式而崩溃。五、结果对象复用evaluate() 方法的第三个参数允许我们传入一个已有的 XPathResult 对象。如果浏览器实现支持这个方法可以复用该对象来存储结果从而减少垃圾回收的压力在需要频繁执行查询的场景下能够提升性能。示例尝试复用结果对象divclassitemA/divdivclassitemB/divdivclassitemC/divdivclassitemD/divdiv当前数量outputidresult-display/output/divconstevaluatornewXPathEvaluator();constexpressionevaluator.createExpression(//div[classitem]);// 创建一个可复用的结果对象letreusableResultnull;// 第一次求值reusableResultexpression.evaluate(document,XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,reusableResult);console.log(第一次结果对象,reusableResult);// 第二次求值尝试复用同一个结果对象reusableResultexpression.evaluate(document,XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,reusableResult);console.log(第二次结果对象,reusableResult);// 显示结果document.getElementById(result-display).textContentreusableResult.snapshotLength;// 输出4需要注意的是并非所有浏览器实现都会真正复用传入的结果对象。如果浏览器不支持复用方法会创建一个新的结果对象并返回。因此在编写代码时应始终使用返回值来获取结果而不是假设传入的对象已经被修改。六、XPathExpression 与动态数据在实际应用中我们经常需要在数据发生变化后重新查询 DOM。预编译的 XPathExpression 对象特别适合这种场景因为表达式本身不会改变只需要在新的上下文中重新调用 evaluate() 即可。示例动态添加元素后重新求值buttonidadd-btn添加项目/buttonbuttonidcount-btn统计项目数/buttonulidlistliclasstask初始任务/li/uldiv任务总数outputidtask-count/output/divconstevaluatornewXPathEvaluator();constexpressionevaluator.createExpression(//li[classtask]);// 更新任务统计functionupdateTaskCount(){constresultexpression.evaluate(document,XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);document.getElementById(task-count).textContentresult.snapshotLength;}// 添加新任务document.getElementById(add-btn).addEventListener(click,(){constnewTaskdocument.createElement(li);newTask.classNametask;newTask.textContent任务${Date.now()};document.getElementById(list).appendChild(newTask);});// 点击按钮时统计任务数document.getElementById(count-btn).addEventListener(click,updateTaskCount);// 初始统计updateTaskCount();在这个示例中我们只在开始时编译了一次 XPath 表达式。之后每次点击统计按钮时都使用同一个表达式对象重新求值获取最新的任务列表。这种模式在需要频繁查询 DOM 的应用中非常高效。七、XPathExpression 的创建与生命周期XPathExpression 对象通过 XPathEvaluator.createExpression() 方法创建。一旦创建该对象就可以在整个应用的生命周期中被反复使用直到不再需要时由垃圾回收机制自动清理。示例封装可复用的查询工具// 创建一个 XPath 查询工具类classXPathQuery{constructor(xpath){this.evaluatornewXPathEvaluator();this.expressionthis.evaluator.createExpression(xpath);}// 执行查询并返回节点列表queryNodes(contextNodedocument){constresultthis.expression.evaluate(contextNode,XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);constnodes[];for(leti0;iresult.snapshotLength;i){nodes.push(result.snapshotItem(i));}returnnodes;}// 执行查询并返回第一个匹配节点queryFirstNode(contextNodedocument){constresultthis.expression.evaluate(contextNode,XPathResult.FIRST_ORDERED_NODE_TYPE);returnresult.singleNodeValue;}}// 使用工具类constdivQuerynewXPathQuery(//div);constallDivsdivQuery.queryNodes();console.log(所有 div 数量,allDivs.length);constfirstDivdivQuery.queryFirstNode();console.log(第一个 div,firstDiv);这种封装方式将编译后的表达式保存在实例中对外提供简洁的查询接口。在大型应用中这种方式能够提升代码的可维护性和执行效率。八、与 XPathEvaluator.evaluate 的对比在上一篇文章中我们学习了直接使用 XPathEvaluator.evaluate() 方法执行查询。而 XPathExpression 则提供了一种面向对象的替代方案。两者在功能上是等价的但在使用方式上有不同的侧重点。示例两种方式的对比divclassboxA/divdivclassboxB/divdiv方式一outputidmethod1/output/divdiv方式二outputidmethod2/output/divconstxpath//div[classbox];// 方式一直接使用 XPathEvaluator.evaluate()constevaluatornewXPathEvaluator();constresult1evaluator.evaluate(xpath,document,null,XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);document.getElementById(method1).textContentresult1.snapshotLength;// 方式二使用 XPathExpressionconstexpressionevaluator.createExpression(xpath);constresult2expression.evaluate(document,XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);document.getElementById(method2).textContentresult2.snapshotLength;// 两种方式结果相同console.log(result1.snapshotLengthresult2.snapshotLength);// true方式一适合一次性查询代码更简洁。方式二适合需要多次执行的查询因为它将表达式的解析和求值分离可以重复使用编译结果。在性能敏感的场景中特别是循环或高频操作中应优先使用 XPathExpression。九、浏览器兼容性XPathExpression 接口属于基线广泛可用的特性在现代主流浏览器中都得到了良好支持包括 Chrome、Firefox、Safari 和 Edge 的最新版本。在开发中使用时如果目标环境包含较旧的浏览器建议先进行功能检测或使用 try-catch 包裹相关代码以确保程序的健壮性。对于绝大多数现代 Web 应用来说可以放心地直接使用这个接口。十、总结今天的学习内容聚焦于 XPathExpression 接口我们从它的定义和创建方式开始深入学习了 evaluate() 方法的语法、参数、返回值以及异常处理。通过丰富的示例代码我们看到了预编译表达式在不同上下文中的应用以及结果对象复用的可能性。XPathExpression 的核心价值在于将 XPath 查询的编译和求值两个阶段分离为需要重复查询的场景提供了性能优化。结合 XPathEvaluator 和 XPathResult这三个接口共同构成了浏览器中完整的 XPath 处理能力。在实际开发中当 CSS 选择器无法满足复杂的 DOM 查询需求时XPathExpression 为我们提供了一个强大而高效的替代方案。掌握这个接口能够让我们在处理复杂文档结构时更加游刃有余。想要解锁更多HTML 核心标签实战、前端零基础入门干货、开发避坑全指南吗持续关注后续将更新CSS 布局实战、JavaScript 交互基础、全站导航开发等硬核内容带你从新手快速进阶轻松搞定前端开发