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

Knockout.js DOM 数据存储模块详解

概述

[utils.domData.js] 是 Knockout.js 框架中用于在 DOM 节点上存储数据的核心模块。它提供了一种跨浏览器的机制,允许开发者将任意数据与 DOM 节点关联起来,这对于框架内部管理节点状态和清理资源至关重要。

核心概念

为什么需要 DOM 数据存储?

在 Web 应用开发中,经常需要将数据与 DOM 节点关联起来。例如:

  • 存储与节点相关的 Knockout 绑定上下文
  • 保存节点的清理回调函数
  • 关联模板渲染相关数据

DOM 数据存储模块提供了一种标准化的方法来实现这一需求。

设计挑战

在设计 DOM 数据存储时,需要考虑以下问题:

  1. 内存泄漏 - 避免因循环引用导致的内存泄漏
  2. 跨浏览器兼容性 - 不同浏览器对 DOM 扩展的支持不同
  3. 性能 - 数据存取操作需要高效
  4. 清理机制 - 节点移除时需要正确清理相关数据

核心实现

数据存储策略

在原始实现中,Knockout.js 根据浏览器类型采用不同的存储策略:

var getDataForNode, clear;
if (!ko.utils.ieVersion) {// 现代浏览器:直接在节点上存储数据getDataForNode = function (node, createIfNotFound) {var dataForNode = node[dataStoreKeyExpandoPropertyName];if (!dataForNode && createIfNotFound) {dataForNode = node[dataStoreKeyExpandoPropertyName] = {};}return dataForNode;};clear = function (node) {if (node[dataStoreKeyExpandoPropertyName]) {delete node[dataStoreKeyExpandoPropertyName];return true;}return false;};
} else {// 旧版 IE:使用单独的数据存储对象getDataForNode = function (node, createIfNotFound) {var dataStoreKey = node[dataStoreKeyExpandoPropertyName];var hasExistingDataStore = dataStoreKey && (dataStoreKey !== "null") && dataStore[dataStoreKey];if (!hasExistingDataStore) {if (!createIfNotFound)return undefined;dataStoreKey = node[dataStoreKeyExpandoPropertyName] = "ko" + uniqueId++;dataStore[dataStoreKey] = {};}return dataStore[dataStoreKey];};clear = function (node) {var dataStoreKey = node[dataStoreKeyExpandoPropertyName];if (dataStoreKey) {delete dataStore[dataStoreKey];node[dataStoreKeyExpandoPropertyName] = null;return true;}return false;};
}

这种设计主要是为了解决旧版 IE 浏览器中存在的内存泄漏问题。

唯一标识符生成

var uniqueId = 0;
var dataStoreKeyExpandoPropertyName = "__ko__" + (new Date).getTime();
var dataStore = {};

通过时间戳和递增计数器确保每个数据存储键的唯一性。

核心 API

get 方法
get: function (node, key) {var dataForNode = getDataForNode(node, false);return dataForNode && dataForNode[key];
}

获取与指定节点关联的特定键的数据,如果数据不存在则返回 undefined

set 方法
set: function (node, key, value) {// Make sure we don't actually create a new domData key if we are actually deleting a valuevar dataForNode = getDataForNode(node, value !== undefined /* createIfNotFound */);dataForNode && (dataForNode[key] = value);
}

设置与指定节点关联的数据。当值为 undefined 时,不会创建新的数据存储对象。

getOrSet 方法
getOrSet: function (node, key, value) {var dataForNode = getDataForNode(node, true /* createIfNotFound */);return dataForNode[key] || (dataForNode[key] = value);
}

获取与节点关联的数据,如果不存在则设置默认值。

clear 方法
clear: clear

清理与节点关联的所有数据,防止内存泄漏。

nextKey 方法
nextKey: function () {return (uniqueId++) + dataStoreKeyExpandoPropertyName;
}

生成唯一的键名,用于在节点上存储不同类型的数据。

在 Knockout.js 中的应用

DOM 节点清理系统

在 [utils.domNodeDisposal.js] 中,DOM 数据存储用于管理节点的清理回调:

var domDataKey = ko.utils.domData.nextKey();
function getDisposeCallbacksCollection(node, createIfNotFound) {var allDisposeCallbacks = ko.utils.domData.get(node, domDataKey);if ((allDisposeCallbacks === undefined) && createIfNotFound) {allDisposeCallbacks = [];ko.utils.domData.set(node, domDataKey, allDisposeCallbacks);}return allDisposeCallbacks;
}

模板系统

在模板系统中,DOM 数据存储用于跟踪模板计算:

var templateComputedDomDataKey = ko.utils.domData.nextKey();
function disposeOldComputed(element) {var oldComputed = ko.utils.domData.get(element, templateComputedDomDataKey);if (oldComputed && (typeof(oldComputed.dispose) == 'function'))oldComputed.dispose();
}

虚拟元素

在虚拟元素系统中,用于标记已匹配的结束注释:

var matchedEndCommentDataKey = "__ko_matchedEndComment__"
// ...
ko.utils.domData.set(currentNode, matchedEndCommentDataKey, true);

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

针对现代浏览器,我们可以大幅简化 DOM 数据存储的实现:

ko.utils.domData = new (function () {var uniqueId = 0;var dataStoreKeyExpandoPropertyName = "__ko__" + (new Date).getTime();return {get: function (node, key) {if (!node[dataStoreKeyExpandoPropertyName]) {return undefined;}return node[dataStoreKeyExpandoPropertyName][key];},set: function (node, key, value) {// 如果要删除值且节点上没有数据存储,则直接返回if (value === undefined && !node[dataStoreKeyExpandoPropertyName]) {return;}// 确保节点上有数据存储对象if (!node[dataStoreKeyExpandoPropertyName]) {node[dataStoreKeyExpandoPropertyName] = {};}// 设置或删除值if (value !== undefined) {node[dataStoreKeyExpandoPropertyName][key] = value;} else {delete node[dataStoreKeyExpandoPropertyName][key];}},getOrSet: function (node, key, value) {if (!node[dataStoreKeyExpandoPropertyName]) {node[dataStoreKeyExpandoPropertyName] = {};}if (!(key in node[dataStoreKeyExpandoPropertyName])) {node[dataStoreKeyExpandoPropertyName][key] = value;}return node[dataStoreKeyExpandoPropertyName][key];},clear: function (node) {if (node[dataStoreKeyExpandoPropertyName]) {delete node[dataStoreKeyExpandoPropertyName];return true;}return false;},nextKey: function () {return (uniqueId++) + dataStoreKeyExpandoPropertyName;}};
})();

优化要点

  1. 移除 IE 兼容代码 - 现代浏览器可以直接在节点上存储对象
  2. 简化逻辑 - 不再需要额外的数据存储对象
  3. 提高性能 - 减少了间接访问层
  4. 降低内存占用 - 不再需要全局数据存储对象

使用示例

基本用法

// 设置数据
ko.utils.domData.set(element, 'myKey', { name: 'John', age: 30 });// 获取数据
var data = ko.utils.domData.get(element, 'myKey');
console.log(data.name); // 输出: John// 获取或设置默认值
var config = ko.utils.domData.getOrSet(element, 'config', { theme: 'default' });// 清理数据
ko.utils.domData.clear(element);

实际应用场景

// 存储绑定上下文
ko.utils.domData.set(node, 'bindingContext', bindingContext);// 存储清理回调
var disposeKey = ko.utils.domData.nextKey();
ko.utils.domData.set(node, disposeKey, function() {// 清理资源的代码console.log('Node is being cleaned up');
});// 在节点清理时执行回调
ko.utils.domNodeDisposal.addDisposeCallback(node, function() {var callback = ko.utils.domData.get(node, disposeKey);if (callback) callback();ko.utils.domData.clear(node);
});

总结

[utils.domData.js]是 Knockout.js 框架中一个关键但低调的模块,它为框架提供了在 DOM 节点上存储数据的能力。通过考虑不同浏览器的兼容性问题,该模块确保了框架在各种环境下的稳定运行。

对于现代浏览器,我们可以大幅简化其实现,移除历史兼容代码,从而提高性能并降低维护成本。无论采用哪种实现方式,DOM 数据存储模块都体现了在 Web 开发中处理 DOM 扩展数据的重要性和复杂性。


文章转载自:

http://WG7v9yRV.LLtdf.cn
http://oKANVTWF.LLtdf.cn
http://fHTeckSy.LLtdf.cn
http://xoaLut2D.LLtdf.cn
http://lmwTaMrE.LLtdf.cn
http://7k2Tfyav.LLtdf.cn
http://0NEOPzvk.LLtdf.cn
http://lw5btEnw.LLtdf.cn
http://ZARulBtI.LLtdf.cn
http://Jq9CdVjw.LLtdf.cn
http://H0JacWck.LLtdf.cn
http://pjBvGM9E.LLtdf.cn
http://JdCnr4Bx.LLtdf.cn
http://9riLjWv4.LLtdf.cn
http://bYRObonu.LLtdf.cn
http://cEUR6Kox.LLtdf.cn
http://ybOT1yoV.LLtdf.cn
http://tseHxSlQ.LLtdf.cn
http://mb2vFCan.LLtdf.cn
http://aO2LjdRf.LLtdf.cn
http://7KsWL5br.LLtdf.cn
http://f23nIo3I.LLtdf.cn
http://fKLWkmcf.LLtdf.cn
http://OI2abz4S.LLtdf.cn
http://hkS5QW03.LLtdf.cn
http://7h3UK3pq.LLtdf.cn
http://5mLMKeH1.LLtdf.cn
http://Of1cZ4Fn.LLtdf.cn
http://1YCJAJgc.LLtdf.cn
http://W8w3wDCA.LLtdf.cn
http://www.dtcms.com/a/382913.html

相关文章:

  • js趣味游戏 贪吃蛇
  • Ajax-day2(图书管理)-弹框显示和隐藏
  • 低代码平台-开发SDK设计
  • Java 线程池面试高频问题全解析
  • 【HarmonyOS】MVVM与三层架构
  • 算法—双指针1.2
  • hcl ac ap 本地转发学习篇
  • Velox:数据界的超级发动机
  • 嵌入式系统启动流程
  • TRAE通用6A规则+敏捷开发5S规则
  • 【Java后端】Spring Boot 集成雪花算法唯一 ID
  • 【知识管理】【科普】新概念的学习路径
  • flask入门(五)WSGI及其Python实现
  • 第17课:自适应学习与优化
  • 详解安卓开发andorid中重要的agp和gradle的关系以及版本不匹配不兼容问题的处理方法-优雅草卓伊凡
  • Linux应用开发(君正T23):三网智能切换及配网功能
  • 华为HarmonyOS开发文档
  • Java 文件io
  • 在Android Studio中配置Gradle涉及到几个关键的文件
  • 基于OpenCV的答题卡自动识别与评分系统
  • 贪心算法应用:出租车调度问题详解
  • 【RK3576】【Android14】如何在Android14下单独编译kernel-6.1?
  • FlashAttention(V2)深度解析:从原理到工程实现
  • ​Prometheus+Grafana监控系统配置与部署全解
  • 电路调试过程中辨认LED正负极并焊接
  • ubuntu24.04 缺少libwebkit2gtk-4.0和libssl.so.1.1
  • eslint-config-encode 使用指南
  • MySQL高阶查询语句与视图实战指南
  • 金融数学与应用数学(金融方向)课程重合度高吗?
  • 知识沉淀过于碎片化如何形成体系化框架