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

Knockout.js 备忘录模块详解

[memoization.js]是 Knockout.js 框架中用于处理 DOM 模板备忘录的核心模块。它提供了一种机制,允许将 JavaScript 函数与 DOM 注释节点关联起来,在适当的时机执行这些函数。这种机制主要用于模板系统中,处理那些需要延迟执行的绑定和逻辑。

核心概念

什么是备忘录(Memoization)?

在 Knockout.js 中,备忘录是一种将函数与 DOM 节点关联的技术。通过在 DOM 中插入特殊的注释节点作为占位符,将需要稍后执行的函数存储起来,等到合适的时机再执行这些函数。

应用场景

  1. 模板渲染 - 在模板渲染过程中,某些绑定需要在 DOM 节点插入后再执行
  2. 延迟绑定 - 对于还没有 DOM 节点的绑定,可以先备忘录化,等节点可用时再执行
  3. 复杂绑定处理 - 处理嵌套或条件绑定时的复杂逻辑

核心实现

备忘录存储

var memos = {};

使用一个全局对象来存储所有备忘录,键为随机生成的 ID,值为对应的函数。

ID 生成

function randomMax8HexChars() {return (((1 + Math.random()) * 0x100000000) | 0).toString(16).substring(1);
}
function generateRandomId() {return randomMax8HexChars() + randomMax8HexChars();
}

通过生成随机的 16 位十六进制字符串作为备忘录的唯一标识符。

备忘录节点查找

function findMemoNodes(rootNode, appendToArray) {if (!rootNode)return;if (rootNode.nodeType == 8) {var memoId = ko.memoization.parseMemoText(rootNode.nodeValue);if (memoId != null)appendToArray.push({ domNode: rootNode, memoId: memoId });} else if (rootNode.nodeType == 1) {for (var i = 0, childNodes = rootNode.childNodes, j = childNodes.length; i < j; i++)findMemoNodes(childNodes[i], appendToArray);}
}

递归遍历 DOM 树,查找所有包含备忘录的注释节点。

核心 API

memoize
memoize: function (callback) {if (typeof callback != "function")throw new Error("You can only pass a function to ko.memoization.memoize()");var memoId = generateRandomId();memos[memoId] = callback;return "<!--[ko_memo:" + memoId + "]-->";
}

将函数存储到备忘录中,并返回对应的注释节点 HTML 字符串。

unmemoize
unmemoize: function (memoId, callbackParams) {var callback = memos[memoId];if (callback === undefined)throw new Error("Couldn't find any memo with ID " + memoId + ". Perhaps it's already been unmemoized.");try {callback.apply(null, callbackParams || []);return true;}finally { delete memos[memoId]; }
}

执行指定 ID 的备忘录函数,并从存储中删除。

unmemoizeDomNodeAndDescendants
unmemoizeDomNodeAndDescendants: function (domNode, extraCallbackParamsArray) {var memos = [];findMemoNodes(domNode, memos);for (var i = 0, j = memos.length; i < j; i++) {var node = memos[i].domNode;var combinedParams = [node];if (extraCallbackParamsArray)ko.utils.arrayPushAll(combinedParams, extraCallbackParamsArray);ko.memoization.unmemoize(memos[i].memoId, combinedParams);node.nodeValue = ""; // Neuter this node so we don't try to unmemoize it againif (node.parentNode)node.parentNode.removeChild(node); // If possible, erase it totally}
}

查找并执行指定 DOM 节点及其后代中的所有备忘录。

parseMemoText
parseMemoText: function (memoText) {var match = memoText.match(/^\[ko_memo\:(.*?)\]$/);return match ? match[1] : null;
}

解析注释节点文本,提取备忘录 ID。

在 Knockout.js 中的应用

模板系统

在模板系统中,当还没有可用的 DOM 节点时,使用备忘录机制:

return ko.memoization.memoize(function (domNode) {ko.renderTemplate(template, dataOrBindingContext, options, domNode, "replaceNode");
});

绑定处理

在处理绑定时,先应用绑定再执行备忘录:

invokeForEachNodeInContinuousRange(firstNode, lastNode, function(node) {if (node.nodeType === 1 || node.nodeType === 8)ko.applyBindings(bindingContext, node);
});
invokeForEachNodeInContinuousRange(firstNode, lastNode, function(node) {if (node.nodeType === 1 || node.nodeType === 8)ko.memoization.unmemoizeDomNodeAndDescendants(node, [bindingContext]);
});

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

针对现代浏览器,我们可以简化备忘录模块的实现:

ko.memoization = (function () {const memos = new Map();function generateRandomId() {return crypto.randomUUID ? crypto.randomUUID() : `${Math.random().toString(36).substr(2, 9)}-${Date.now().toString(36)}`;}function findMemoNodes(rootNode, appendToArray) {if (!rootNode) return;if (rootNode.nodeType == 8) {const memoId = ko.memoization.parseMemoText(rootNode.nodeValue);if (memoId != null)appendToArray.push({ domNode: rootNode, memoId });} else if (rootNode.nodeType == 1) {// 使用现代遍历方法[...rootNode.childNodes].forEach(childNode => findMemoNodes(childNode, appendToArray));}}return {memoize(callback) {if (typeof callback != "function")throw new Error("You can only pass a function to ko.memoization.memoize()");const memoId = generateRandomId();memos.set(memoId, callback);return `<!--[ko_memo:${memoId}]-->`;},unmemoize(memoId, callbackParams) {const callback = memos.get(memoId);if (callback === undefined)throw new Error(`Couldn't find any memo with ID ${memoId}. Perhaps it's already been unmemoized.`);try {callback.apply(null, callbackParams || []);return true;} finally {memos.delete(memoId);}},unmemoizeDomNodeAndDescendants(domNode, extraCallbackParamsArray) {const memoNodes = [];findMemoNodes(domNode, memoNodes);memoNodes.forEach(({ domNode: node, memoId }) => {const combinedParams = [node];if (extraCallbackParamsArray)combinedParams.push(...extraCallbackParamsArray);ko.memoization.unmemoize(memoId, combinedParams);node.nodeValue = "";node.parentNode?.removeChild(node);});},parseMemoText(memoText) {const match = memoText.match(/^\[ko_memo\:(.*?)\]$/);return match ? match[1] : null;}};
})();ko.exportSymbol('memoization', ko.memoization);
ko.exportSymbol('memoization.memoize', ko.memoization.memoize);
ko.exportSymbol('memoization.unmemoize', ko.memoization.unmemoize);
ko.exportSymbol('memoization.parseMemoText', ko.memoization.parseMemoText);
ko.exportSymbol('memoization.unmemoizeDomNodeAndDescendants', ko.memoization.unmemoizeDomNodeAndDescendants);

优化要点

  1. 使用现代数据结构 - 使用 Map 替代普通对象存储备忘录
  2. 使用现代 ID 生成 - 利用 crypto.randomUUID API
  3. 简化代码 - 使用 const[/](file:///Users/xianhao/jvy/nodejs/gitee/@licence/Apache-2.0/dist/index.d.ts)let 和箭头函数
  4. 使用现代数组方法 - 使用展开语法和 forEach
  5. 可选链操作符 - 使用 ?. 安全地访问属性

使用示例

基本用法

// 创建备忘录
const memoHtml = ko.memoization.memoize(function(domNode, context) {console.log('Memo executed on node:', domNode);// 执行一些需要 DOM 节点的操作
});// memoHtml 现在包含类似 <!--ko_memo:abcd1234--> 的字符串
console.log(memoHtml);// 执行备忘录(通常由 Knockout.js 内部处理)
// ko.memoization.unmemoize(memoId, [domNode, context]);

实际应用场景

// 在自定义模板引擎中使用
ko.customTemplateEngine = function() {this.renderTemplateSource = function(templateSource, bindingContext, options) {const templateText = templateSource.text();// 如果还没有 DOM 节点,创建备忘录if (!options.targetNode) {return ko.memoization.memoize(function(domNode) {// 当 DOM 节点可用时执行实际的渲染const nodes = ko.utils.parseHtmlFragment(templateText);ko.utils.setDomNodeChildren(domNode, nodes);ko.applyBindings(bindingContext, domNode);});}// 如果有 DOM 节点,直接渲染const nodes = ko.utils.parseHtmlFragment(templateText);ko.utils.setDomNodeChildren(options.targetNode, nodes);ko.applyBindings(bindingContext, options.targetNode);return nodes;};
};

与组件系统的集成

// 在组件加载中使用备忘录
ko.components.register('my-component', {template: '<div data-bind="text: message"></div>',viewModel: function(params) {this.message = ko.observable('Hello World');// 对于异步加载的组件,可以使用备忘录机制return ko.memoization.memoize(function(element) {ko.applyBindingsToDescendants(this, element);}.bind(this));}
});

总结

[memoization.js]是 Knockout.js 中一个巧妙的模块,它通过将函数与 DOM 注释节点关联,实现了延迟执行的机制。这种设计解决了模板系统中没有可用 DOM 节点时的绑定处理问题,是 Knockout.js 能够灵活处理各种复杂绑定场景的关键技术之一。

该模块的设计体现了在现代 Web 开发中对延迟执行和异步处理的重视。通过合理的抽象和封装,为开发者提供了简单易用的 API 来处理复杂的 DOM 操作场景。对于现代浏览器,我们可以利用新的 Web API 进一步简化其实现,提高代码的可读性和性能。

备忘录机制虽然在 Knockout.js 的现代使用中可能不如早期版本那么常见,但它仍然是框架处理复杂模板和绑定场景的重要工具,体现了 Knockout.js 设计的灵活性和强大功能。


文章转载自:

http://lDsEKQ1u.xLbtz.cn
http://U4RhNG4S.xLbtz.cn
http://QYq0RhNa.xLbtz.cn
http://84VbATle.xLbtz.cn
http://9lTD5Kax.xLbtz.cn
http://Zibh0Bt6.xLbtz.cn
http://H80CURDy.xLbtz.cn
http://ux9K9wW7.xLbtz.cn
http://Kln7Pm9H.xLbtz.cn
http://mnHmyguV.xLbtz.cn
http://L39WMlyH.xLbtz.cn
http://XTG0cuDn.xLbtz.cn
http://SJGXlnvB.xLbtz.cn
http://L3jrNecb.xLbtz.cn
http://vcDi7Tqu.xLbtz.cn
http://oyLicZ9x.xLbtz.cn
http://TBKZVEi1.xLbtz.cn
http://kDPbQGUW.xLbtz.cn
http://UoJ46VGk.xLbtz.cn
http://KyoEmTFM.xLbtz.cn
http://Mbxy0kwO.xLbtz.cn
http://4II6wiFA.xLbtz.cn
http://NkayEg2j.xLbtz.cn
http://rTLFHJ8o.xLbtz.cn
http://SLuJQRiY.xLbtz.cn
http://dMaHzR1E.xLbtz.cn
http://lKp7XTua.xLbtz.cn
http://4yaLNqHA.xLbtz.cn
http://YgvQc4DK.xLbtz.cn
http://jNXch9K4.xLbtz.cn
http://www.dtcms.com/a/383095.html

相关文章:

  • VS2022下载+海康SDK环境配置实现实时预览
  • 前端基础 —— C / JavaScript基础语法
  • 手搓一个 DELL EMC Unity存储系统健康检查清单
  • 字节M3-Agent:如何实现一个支持多模态长期记忆与推理的Agent
  • TCL华星计划投建第8.6代印刷OLED产线
  • Qt学习:moc生成的元对象信息
  • Java—JDBC 和数据库连接池
  • 软件工程实践四:MyBatis-Plus 教程(连接、分页、查询)
  • 用 Go 快速上手 Protocol Buffers
  • Java Stream 流学习笔记
  • Linux线程id与简易封装线程实现
  • 公链分析报告 - Secret Network
  • JavaScript 简单链表题目试析
  • 【ZYNQ开发篇】Petalinux和电脑端的静态ip地址配置
  • 电商AI导购系统的模型部署架构:TensorFlow Serving在实时推荐中的实践
  • 光射三缝实验
  • K8s部署 Redis 主从集群
  • Android点击桌面图库应用启动流程trace分析
  • 【抗量子安全】全球视角下 PQC 与 QKD 技术洞察:政策引领与产业演进
  • 代码随想录学习摘抄day9(回溯1-11)
  • 数据处理指令
  • SpringBoot 中 ZK 与 Kafka 节点选择逻辑:底层原理与实践解析
  • 事务与mysql数据库锁的关系
  • 继承类模板:函数未在模板定义上下文中声明,只能通过实例化上下文中参数相关的查找找到
  • 07-Redis 基础操作全攻略:从键管理到数据类型判断
  • 【linux】特殊权限
  • [数据结构] 排序
  • Python网络与多任务编程:TCP/UDP实战指南
  • Elasticsearch面试精讲 Day 17:查询性能调优实践
  • Go-zero 构建 RPC 与 API 服务全流程