当前位置: 首页 > news >正文

Knockout.js DOM 操作模块详解

[utils.domManipulation.js]是 Knockout.js 框架中负责处理 HTML 片段解析和 DOM 操作的核心模块。它提供了将 HTML 字符串转换为 DOM 节点的功能,以及设置元素 HTML 内容的工具函数。

核心概念

HTML 解析的挑战

在 Web 开发中,将 HTML 字符串转换为 DOM 节点并不是一个简单的过程,特别是对于一些特殊的 HTML 元素:

  1. 表格相关元素 - <tr>, <td>, <th> 等元素不能直接通过 innerHTML 设置
  2. 表单相关元素 - <option>, <optgroup> 等元素有特殊要求
  3. 注释节点 - 在某些浏览器中处理不当可能会丢失
  4. 跨浏览器兼容性 - 不同浏览器对 HTML 解析的行为可能不同

解决方案

Knockout.js 通过包装和扩展浏览器原生的 HTML 解析功能,提供了一套统一的 API 来处理这些复杂情况。

核心实现

HTML 包装映射

var none = [0, "", ""],table = [1, "<table>", "</table>"],tbody = [2, "<table><tbody>", "</tbody></table>"],tr = [3, "<table><tbody><tr>", "</tr></tbody></table>"],select = [1, "<select multiple='multiple'>", "</select>"],lookup = {'thead': table,'tbody': table,'tfoot': table,'tr': tbody,'td': tr,'th': tr,'option': select,'optgroup': select};

这个映射表定义了如何包装特殊 HTML 元素以确保它们能被正确解析。例如,<td> 元素需要包装在 <table><tbody><tr> 中才能正确解析。

HTML 解析函数

simpleHtmlParse
function simpleHtmlParse(html, documentContext) {documentContext || (documentContext = document);var windowContext = documentContext['parentWindow'] || documentContext['defaultView'] || window;// Trim whitespace, otherwise indexOf won't work as expectedvar tags = ko.utils.stringTrim(html).toLowerCase(), div = documentContext.createElement("div"),wrap = getWrap(tags),depth = wrap[0];// Go to html and back, then peel off extra wrappers// Note that we always prefix with some dummy text, because otherwise, IE<9 will strip out leading comment nodes in descendants. Total madness.var markup = "ignored<div>" + wrap[1] + html + wrap[2] + "</div>";if (typeof windowContext['innerShiv'] == "function") {// Note that innerShiv is deprecated in favour of html5shiv. We should consider adding// support for html5shiv (except if no explicit support is needed, e.g., if html5shiv// somehow shims the native APIs so it just works anyway)div.appendChild(windowContext['innerShiv'](markup));} else {if (mayRequireCreateElementHack) {// The document.createElement('my-element') trick to enable custom elements in IE6-8// only works if we assign innerHTML on an element associated with that document.documentContext.body.appendChild(div);}div.innerHTML = markup;if (mayRequireCreateElementHack) {div.parentNode.removeChild(div);}}// Move to the right depthwhile (depth--)div = div.lastChild;return ko.utils.makeArray(div.lastChild.childNodes);
}

该函数通过将 HTML 片段包装在适当的标签中,然后使用 innerHTML 解析,最后提取出所需的节点。

jQueryHtmlParse
function jQueryHtmlParse(html, documentContext) {// jQuery's "parseHTML" function was introduced in jQuery 1.8.0 and is a documented public API.if (jQueryInstance['parseHTML']) {return jQueryInstance['parseHTML'](html, documentContext) || []; // Ensure we always return an array and never null} else {// For jQuery < 1.8.0, we fall back on the undocumented internal "clean" function.var elems = jQueryInstance['clean']([html], documentContext);// As of jQuery 1.7.1, jQuery parses the HTML by appending it to some dummy parent nodes held in an in-memory document fragment.// Unfortunately, it never clears the dummy parent nodes from the document fragment, so it leaks memory over time.// Fix this by finding the top-most dummy parent element, and detaching it from its owner fragment.if (elems && elems[0]) {// Find the top-most parent element that's a direct child of a document fragmentvar elem = elems[0];while (elem.parentNode && elem.parentNode.nodeType !== 11 /* i.e., DocumentFragment */)elem = elem.parentNode;// ... then detach itif (elem.parentNode)elem.parentNode.removeChild(elem);}return elems;}
}

当页面中引入了 jQuery 时,Knockout.js 会优先使用 jQuery 的 HTML 解析功能,因为它更成熟和强大。

核心 API

parseHtmlFragment
ko.utils.parseHtmlFragment = function(html, documentContext) {return jQueryInstance ?jQueryHtmlParse(html, documentContext) :   // As below, benefit from jQuery's optimisations where possiblesimpleHtmlParse(html, documentContext);  // ... otherwise, this simple logic will do in most common cases.
};

解析 HTML 片段并返回 DOM 节点数组。如果页面中引入了 jQuery,则使用 jQuery 的解析功能,否则使用内置的解析逻辑。

setHtml
ko.utils.setHtml = function(node, html) {ko.utils.emptyDomNode(node);// There's no legitimate reason to display a stringified observable without unwrapping it, so we'll unwrap ithtml = ko.utils.unwrapObservable(html);if ((html !== null) && (html !== undefined)) {if (typeof html != 'string')html = html.toString();// jQuery contains a lot of sophisticated code to parse arbitrary HTML fragments,// for example <tr> elements which are not normally allowed to exist on their own.// If you've referenced jQuery we'll use that rather than duplicating its code.if (jQueryInstance) {jQueryInstance(node)['html'](html);} else {// ... otherwise, use KO's own parsing logic.var parsedNodes = ko.utils.parseHtmlFragment(html, node.ownerDocument);for (var i = 0; i < parsedNodes.length; i++)node.appendChild(parsedNodes[i]);}}
};

设置节点的 HTML 内容。如果引入了 jQuery,则使用 jQuery 的 html() 方法,否则使用 Knockout.js 自己的解析逻辑。

优化方案(针对现代浏览器)

针对现代浏览器,我们可以大幅简化 DOM 操作模块的实现:

(function () {// 现代浏览器中的 HTML 包装映射const lookup = {'thead': [1, "<table>", "</table>"],'tbody': [1, "<table>", "</table>"],'tfoot': [1, "<table>", "</table>"],'tr': [2, "<table><tbody>", "</tbody></table>"],'td': [3, "<table><tbody><tr>", "</tr></tbody></table>"],'th': [3, "<table><tbody><tr>", "</tr></tbody></table>"],'option': [1, "<select multiple='multiple'>", "</select>"],'optgroup': [1, "<select multiple='multiple'>", "</select>"]};function getWrap(tagName) {// 提取标签名const match = tagName.match(/<([a-z]+)/i);const tag = match ? match[1].toLowerCase() : '';return lookup[tag] || [0, "", ""];}function parseHtmlFragment(html, documentContext) {documentContext = documentContext || document;// 使用现代浏览器的 DOMParser APIif (typeof DOMParser !== 'undefined') {const parser = new DOMParser();const doc = parser.parseFromString(`<div>${html}</div>`, 'text/html');return Array.from(doc.body.firstChild.childNodes);}// 回退到模板元素方法const template = documentContext.createElement('template');template.innerHTML = html;return Array.from(template.content.childNodes);}ko.utils.parseHtmlFragment = function(html, documentContext) {// 如果引入了 jQuery,仍然可以使用它if (jQueryInstance && jQueryInstance.parseHTML) {return jQueryInstance.parseHTML(html, documentContext) || [];}return parseHtmlFragment(html, documentContext);};ko.utils.parseHtmlForTemplateNodes = function(html, documentContext) {const nodes = ko.utils.parseHtmlFragment(html, documentContext);return (nodes.length && nodes[0].parentElement) || ko.utils.moveCleanedNodesToContainerElement(nodes);};ko.utils.setHtml = function(node, html) {ko.utils.emptyDomNode(node);// 解包 observablehtml = ko.utils.unwrapObservable(html);if ((html !== null) && (html !== undefined)) {if (typeof html != 'string')html = html.toString();// 如果引入了 jQuery,使用 jQuery 的 html 方法if (jQueryInstance) {jQueryInstance(node).html(html);} else {// 使用现代浏览器的实现const parsedNodes = ko.utils.parseHtmlFragment(html, node.ownerDocument);parsedNodes.forEach(parsedNode => node.appendChild(parsedNode));}}};
})();ko.exportSymbol('utils.parseHtmlFragment', ko.utils.parseHtmlFragment);
ko.exportSymbol('utils.setHtml', ko.utils.setHtml);

优化要点

  1. 使用现代 API - 利用 DOMParser<template> 元素
  2. 简化包装逻辑 - 移除 IE 兼容性代码
  3. 使用现代 JavaScript 语法 - 使用 const/let、箭头函数、Array.from
  4. 移除过时的兼容代码 - 删除针对 IE6-8 的特殊处理

使用示例

基本用法

// 解析 HTML 片段
const nodes = ko.utils.parseHtmlFragment('<p>Hello, world!</p><div>Content</div>');
console.log(nodes.length); // 输出节点数量// 设置元素的 HTML 内容
const element = document.getElementById('content');
ko.utils.setHtml(element, '<h1>Title</h1><p>Paragraph</p>');

在绑定处理器中使用

ko.bindingHandlers.html = {init: function() {// 阻止默认的后代绑定return { controlsDescendantBindings: true };},update: function(element, valueAccessor) {// 使用 Knockout.js 的 HTML 设置功能ko.utils.setHtml(element, valueAccessor());}
};

处理特殊元素

// 解析表格相关元素
const tableCells = ko.utils.parseHtmlFragment('<td>Cell 1</td><td>Cell 2</td>');
// 这些单元格会被正确包装在表格结构中// 解析表单选项
const options = ko.utils.parseHtmlFragment('<option value="1">Option 1</option><option value="2">Option 2</option>');
// 这些选项会被正确包装在 select 元素中

总结

[utils.domManipulation.js]是 Knockout.js 中一个重要的 DOM 操作模块,它解决了 HTML 解析和 DOM 操作中的复杂问题。通过提供统一的 API,它隐藏了浏览器差异,使得开发者可以安全地处理各种 HTML 片段。

对于现代浏览器,我们可以利用新的 Web API(如 DOMParser<template> 元素)来简化实现,同时保持与 jQuery 等库的兼容性。这种渐进式增强的设计模式使得 Knockout.js 既能在旧环境中正常工作,又能在现代浏览器中发挥最佳性能。


文章转载自:

http://iI9tWyY6.ktmpw.cn
http://02vvMqV0.ktmpw.cn
http://GB8HNvv6.ktmpw.cn
http://7IAAtar1.ktmpw.cn
http://7PAQ7jfF.ktmpw.cn
http://rRAuwxip.ktmpw.cn
http://bgIWyIiX.ktmpw.cn
http://4rhtaT9K.ktmpw.cn
http://3vQ2H2wj.ktmpw.cn
http://nxKv8LVI.ktmpw.cn
http://SSBHtlZC.ktmpw.cn
http://jmMcQ1mr.ktmpw.cn
http://d4hkmokC.ktmpw.cn
http://qm4ifq5b.ktmpw.cn
http://l9URlqa4.ktmpw.cn
http://M6cjZyex.ktmpw.cn
http://SQYKMpwA.ktmpw.cn
http://wzYTxPKw.ktmpw.cn
http://W7ztPWqg.ktmpw.cn
http://o1gIldTC.ktmpw.cn
http://Dd9n7sxa.ktmpw.cn
http://WF14kyb3.ktmpw.cn
http://OQWyIhP5.ktmpw.cn
http://ITTRYTgO.ktmpw.cn
http://sOGmZpNy.ktmpw.cn
http://nSeXs1f5.ktmpw.cn
http://W1AG2h5d.ktmpw.cn
http://hSgwpWZ1.ktmpw.cn
http://F6G7rbyG.ktmpw.cn
http://scfPiDNA.ktmpw.cn
http://www.dtcms.com/a/384020.html

相关文章:

  • 面试题知识-NodeJS系列
  • 【层面一】C#语言基础和核心语法-02(反射/委托/事件)
  • Jmeter性能测试实战
  • CSP-S 2021 提高级 第一轮(初赛) 阅读程序(3)
  • TTC定时器中断——MPSOC实战3
  • [数据结构——lesson10.2堆排序以及TopK问题]
  • Maven 本地仓库的 settings.xml 文件
  • 绑定数据管理
  • RTU 全面科普:从入门到 AI 时代的智能化演进
  • lxml对于xml文件的操作
  • 第23课:行业解决方案设计
  • 深入理解 Java 内存模型与 volatile 关键字
  • Alibaba Lens:阿里巴巴推出的 AI 图像搜索浏览器扩展,助力B2B采购
  • I.MX6UL:主频和时钟配置实验
  • 【前端知识】package-lock.json 全面解析:作用、原理与最佳实践
  • 计算机视觉(opencv)实战二十——SIFT提取图像特征
  • Android开发-SharedPreferences
  • SpringBoot的自动配置原理及常见注解
  • Java内部类内存泄漏解析:`this$0`引用的隐秘风险
  • 快速掌握Dify+Chrome MCP:打造网页操控AI助手
  • 【cpp Trip第1栈】vector
  • 详解 new 和 delete
  • 基于PassGAN的密码训练系统设计与实现
  • 避开Java日期格式化陷阱:`yyyy`与`YYYY`的正确使用
  • SpringCloud与Dubbo实战对决:从协议到治理的全维度选型指南(一)
  • SAP HANA Scale-out 04:CalculationView优化
  • 删除文件夹里的网盘图标
  • MPC模型预测控制:一种先进的控制策略
  • 【数据集】基于观测的全球月度网格化海表pCO₂与海气CO₂通量产品及其月气候平均值
  • RS485简介