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

Knockout.js 任务调度模块详解

[tasks.js]是 Knockout.js 框架中负责异步任务调度的核心模块。它提供了一个高效的任务队列系统,用于处理 DOM 更新、计算属性重新计算等需要异步执行的操作。通过使用微任务调度机制,Knockout.js 能够批量处理更新操作,提高应用性能。

核心概念

为什么需要任务调度?

在现代 Web 应用中,频繁的 DOM 操作会导致性能问题。Knockout.js 通过任务调度机制将多个更新操作批量处理,避免重复的 DOM 操作。例如:

  1. 批量更新 - 当多个 observable 发生变化时,将相关的 DOM 更新操作合并执行
  2. 避免重复计算 - 计算属性的重新计算可以被批量处理
  3. 优化性能 - 减少浏览器重排和重绘的次数

调度机制

Knockout.js 根据浏览器支持情况选择最优的异步调度机制:

  1. MutationObserver - 现代浏览器首选,性能最佳
  2. script.onreadystatechange - IE 浏览器的备选方案
  3. setTimeout - 兼容性方案,性能相对较差

核心实现

调度器选择

if (window['MutationObserver']) {// Chrome 27+, Firefox 14+, IE 11+, Opera 15+, Safari 6.1+scheduler = (function (callback) {var div = document.createElement("div");new MutationObserver(callback).observe(div, {attributes: true});return function () { div.classList.toggle("foo"); };})(scheduledProcess);
} else if (document && "onreadystatechange" in document.createElement("script")) {// IE 6-10scheduler = function (callback) {var script = document.createElement("script");script.onreadystatechange = function () {script.onreadystatechange = null;document.documentElement.removeChild(script);script = null;callback();};document.documentElement.appendChild(script);};
} else {scheduler = function (callback) {setTimeout(callback, 0);};
}

这段代码根据浏览器支持情况选择最优的调度器:

  1. MutationObserver - 利用 DOM 变化观察器实现微任务调度
  2. script.onreadystatechange - 利用脚本加载事件实现近似微任务调度
  3. setTimeout - 使用宏任务作为备选方案

任务队列管理

var taskQueue = [],taskQueueLength = 0,nextHandle = 1,nextIndexToProcess = 0;

任务队列相关变量:

  • taskQueue - 存储待执行任务的数组
  • taskQueueLength - 当前任务队列长度
  • nextHandle - 下一个任务句柄,用于任务取消
  • nextIndexToProcess - 下一个待处理任务的索引

任务处理函数

processTasks
function processTasks() {if (taskQueueLength) {// Each mark represents the end of a logical group of tasks and the number of these groups is// limited to prevent unchecked recursion.var mark = taskQueueLength, countMarks = 0;// nextIndexToProcess keeps track of where we are in the queue; processTasks can be called recursively without issuefor (var task; nextIndexToProcess < taskQueueLength; ) {if (task = taskQueue[nextIndexToProcess++]) {if (nextIndexToProcess > mark) {if (++countMarks >= 5000) {nextIndexToProcess = taskQueueLength;   // skip all tasks remaining in the queue since any of them could be causing the recursionko.utils.deferError(Error("'Too much recursion' after processing " + countMarks + " task groups."));break;}mark = taskQueueLength;}try {task();} catch (ex) {ko.utils.deferError(ex);}}}}
}

处理任务队列中的所有任务,包含递归保护机制防止无限循环。

scheduledProcess
function scheduledProcess() {processTasks();// Reset the queuenextIndexToProcess = taskQueueLength = taskQueue.length = 0;
}

调度处理函数,在调度器触发时执行,处理完任务后重置队列。

核心 API

schedule
schedule: function (func) {if (!taskQueueLength) {scheduleTaskProcessing();}taskQueue[taskQueueLength++] = func;return nextHandle++;
}

调度一个任务:

  1. 如果任务队列为空,启动任务处理调度
  2. 将任务添加到队列中
  3. 返回任务句柄用于取消
cancel
cancel: function (handle) {var index = handle - (nextHandle - taskQueueLength);if (index >= nextIndexToProcess && index < taskQueueLength) {taskQueue[index] = null;}
}

取消指定的任务,通过将任务设置为 null 来实现。

runEarly
runEarly: processTasks

立即执行所有排队的任务,不等待调度器触发。

在 Knockout.js 中的应用

依赖检测

在依赖检测系统中,当 observable 发生变化时,相关的订阅者会被调度执行:

ko.subscribable.fn.notifySubscribers = function (valueToNotify, event) {event = event || defaultEvent;if (this._subscriptions[event]) {ko.tasks.schedule(() => {ko.utils.arrayForEach(this._subscriptions[event].slice(), function (subscription) {subscription(valueToNotify);});});}
};

DOM 更新

在绑定系统中,DOM 更新操作会被批量处理:

ko.bindingHandlers.text = {update: function (element, valueAccessor) {ko.tasks.schedule(() => {ko.utils.setTextContent(element, valueAccessor());});}
};

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

针对现代浏览器,我们可以简化任务调度模块的实现:

ko.tasks = (function () {let taskQueue = [],taskQueueLength = 0,nextHandle = 1,nextIndexToProcess = 0;// 现代浏览器统一使用 queueMicrotaskconst scheduler = function (callback) {if (typeof queueMicrotask === 'function') {queueMicrotask(callback);} else if (window['MutationObserver']) {// 回退到 MutationObserverconst div = document.createElement("div");new MutationObserver(callback).observe(div, {attributes: true});return () => { div.classList.toggle("foo"); };} else {// 最后的回退方案Promise.resolve().then(callback);}};function processTasks() {if (taskQueueLength) {// 简化递归保护const mark = taskQueueLength;let countMarks = 0;for (let task; nextIndexToProcess < taskQueueLength; ) {if (task = taskQueue[nextIndexToProcess++]) {if (nextIndexToProcess > mark) {if (++countMarks >= 5000) {nextIndexToProcess = taskQueueLength;ko.utils.deferError(new Error("'Too much recursion' after processing " + countMarks + " task groups."));break;}mark = taskQueueLength;}try {task();} catch (ex) {ko.utils.deferError(ex);}}}}}function scheduledProcess() {processTasks();// 重置队列nextIndexToProcess = taskQueueLength = taskQueue.length = 0;}function scheduleTaskProcessing() {ko.tasks['scheduler'](scheduledProcess);}const tasks = {'scheduler': scheduler,schedule(func) {if (!taskQueueLength) {scheduleTaskProcessing();}taskQueue[taskQueueLength++] = func;return nextHandle++;},cancel(handle) {const index = handle - (nextHandle - taskQueueLength);if (index >= nextIndexToProcess && index < taskQueueLength) {taskQueue[index] = null;}},// For testing only'resetForTesting': function () {const length = taskQueueLength - nextIndexToProcess;nextIndexToProcess = taskQueueLength = taskQueue.length = 0;return length;},runEarly: processTasks};return tasks;
})();ko.exportSymbol('tasks', ko.tasks);
ko.exportSymbol('tasks.schedule', ko.tasks.schedule);
ko.exportSymbol('tasks.runEarly', ko.tasks.runEarly);

优化要点

  1. 使用现代 API - 优先使用 queueMicrotask
  2. 简化代码 - 使用 let[/](file:///Users/xianhao/jvy/nodejs/gitee/@licence/Apache-2.0/dist/index.d.ts)const 和箭头函数
  3. 移除兼容性代码 - 删除针对 IE 的特殊处理
  4. 改进错误处理 - 使用现代的 Error 构造函数

使用示例

基本用法

// 调度一个任务
const handle = ko.tasks.schedule(() => {console.log('Task executed');
});// 取消任务
ko.tasks.cancel(handle);// 立即执行所有任务
ko.tasks.runEarly();

实际应用场景

// 在自定义订阅中使用
function MyObservable(initialValue) {let value = initialValue;const subscribers = [];this.subscribe = function(callback) {subscribers.push(callback);return {dispose: () => {const index = subscribers.indexOf(callback);if (index >= 0) {subscribers.splice(index, 1);}}};};this.notify = function(newValue) {// 批量通知订阅者subscribers.forEach(callback => {ko.tasks.schedule(() => callback(newValue));});};this.setValue = function(newValue) {value = newValue;this.notify(newValue);};
}

性能优化示例

// 批量更新 DOM
ko.bindingHandlers.foreach = {update: function(element, valueAccessor) {const items = ko.utils.unwrapObservable(valueAccessor());// 清空元素ko.utils.emptyDomNode(element);// 批量创建元素items.forEach(item => {ko.tasks.schedule(() => {const childElement = document.createElement('div');ko.applyBindingsToNode(childElement, { text: item }, item);element.appendChild(childElement);});});}
};

总结

[tasks.js]是 Knockout.js 中一个关键的性能优化模块,它通过异步任务调度机制实现了高效的批量更新。该模块的设计体现了现代 Web 开发中对性能优化的重视,通过合理的抽象和封装,为开发者提供了简单易用的 API 来处理异步任务。

对于现代浏览器,我们可以进一步简化其实现,利用新的 Web API 提高代码的可读性和性能,同时保持功能的完整性。这种渐进式优化的思路在现代前端开发中非常常见,有助于在保持兼容性的同时提升代码质量。


文章转载自:

http://yNmaHfSx.xpzgg.cn
http://ksrLTA4V.xpzgg.cn
http://ElyWJCe4.xpzgg.cn
http://tzE8rj2x.xpzgg.cn
http://aLQGW9eh.xpzgg.cn
http://B3iT5bfe.xpzgg.cn
http://YoijTK9u.xpzgg.cn
http://qczWbV99.xpzgg.cn
http://I52xXB20.xpzgg.cn
http://opM5zSqD.xpzgg.cn
http://XMwjuiAo.xpzgg.cn
http://xQsQaOtz.xpzgg.cn
http://9QAdxYWF.xpzgg.cn
http://Owuf5iSJ.xpzgg.cn
http://4LoGkzSY.xpzgg.cn
http://HDjVkiVb.xpzgg.cn
http://AE5oYkdY.xpzgg.cn
http://9GxWlKpW.xpzgg.cn
http://8G2y7Tzh.xpzgg.cn
http://lc07nR2z.xpzgg.cn
http://BIU0gblp.xpzgg.cn
http://skGVlWzq.xpzgg.cn
http://k0MxtnBK.xpzgg.cn
http://ICc0oOZp.xpzgg.cn
http://J70cPmDF.xpzgg.cn
http://gcPcLPKV.xpzgg.cn
http://ADOhlErg.xpzgg.cn
http://bz7i7sb4.xpzgg.cn
http://w0ZmNpKb.xpzgg.cn
http://gmoLkkqS.xpzgg.cn
http://www.dtcms.com/a/383714.html

相关文章:

  • LeetCode 2414.最长的字母续连续子字符串的长度
  • 当环保遇上大数据:生态环境大数据技术专业的课程侧重哪些领域?
  • 【Ansible】使用角色和Ansible内容集合简化Playbook知识点
  • init / record / required:让 C# 对象一次成型
  • BigemapPro快速添加历史影像(Arcgis卫星地图历史地图)
  • 树莓派操作第一章常用指令
  • Altium Designer(AD24)工作面板的切换与定制
  • 【WebSocket✨】入门之旅(七):WebSocket 的未来发展趋势
  • MySQL——库的操作
  • Redis缓存的常见问题及其解决方案
  • Conda 安装 CUDA Toolkit 解决 nvcc 找不到的问题
  • (二)Django框架常用配置
  • Android开发-数据库SQLite
  • (附源码)基于springboot的幼儿园管理系统
  • 【从零到公网】本地电脑部署服务并实现公网访问(IPv4/IPv6/DDNS 全攻略)
  • VTK基础(01):VTK中的基本概念
  • Sentinel:微服务架构下的高可用流量防卫兵
  • Unity学习----【进阶】TextMeshPro学习(三)--进阶知识点(TMP基础设置,材质球相关,两个辅助工具类)
  • OpenCV:指纹识别
  • map/multimap容器
  • leetcode 966. 元音拼写检查器 中等
  • esp32程序存储结构--自用笔记版
  • leetcode 21 合并两个有序链表
  • OneCode 移动套件多平台适配详细报告
  • RAGAS新手入门教程:从基础到实践
  • 在 CentOS 中安装 VirtualBox 增强功能的步骤
  • 网络编程-day4
  • 学习笔记:第一个Python程序
  • Docker-compose.yml集群设置
  • 课后作业-2025-09-14