Two.Collection 源码解析:two.js 中带事件传播的类数组容器
图形学前端【免费下载链接】two.jsA renderer agnostic two-dimensional drawing api for the web项目地址https://gitcode.com/gh_mirrors/tw/two.js点击查看免费下载Two.Collection 是 two.js 内置的类Array容器它在原生数组行为之上加入了基于 Two.Events 的事件传播机制。凡是使用push、pop、shift、unshift、splice、sort、reverse修改集合内容的操作都会自动触发insert、remove、order三类事件从而让外部监听者实时感知集合变化。在 two.js 中Two.Group的子元素列表、渐变色的stops、点的vertices集合均由它驱动是理解 two.js 对象模型与事件体系的关键一环。读完本文你将掌握 Two.Collection 的完整 API、事件触发规则以及它在源码内部的实际使用模式。什么是 Two.Collection根据 collection 文档 的定义AnArraylike object with additional event propagation on actions.pop,shift, andsplicetriggerremovedevents.push,unshift, andsplicewith more than 2 arguments trigger inserted. Finally,sortandreversetriggerorderevents.即它是一个Array子类任何修改集合结构的操作都会向外广播事件。它继承自 Two.Events因此天然具备 two.js 全套事件能力on/off/bind/unbind/trigger等。从类型声明src/collection.d.ts可以看出其泛型设计declare module two.js/src/collection { export class CollectionT any extends ArrayT { constructor(...args: any[]); on(...args: any[]): any; bind(...args: any[]): any; off(...args: any[]): any; trigger(...args: any[]): any; listen(...args: any[]): any; ignore(...args: any[]): any; } }默认元素类型为any也可显式指定例如CollectionTwo.Shape。方法、事件与触发时机对照Two.Collection 复写了 7 个数组方法见 src/collection.js下表总结了每个方法与对应事件的完整关系方法触发事件事件负载传给监听器的参数说明push(...items)insert被推入的所有元素对应Events.Types.insertunshift(...items)insert被插入的所有元素对应Events.Types.insertpop()remove被弹出移除的单个元素对应Events.Types.removeshift()remove被移除的单个元素对应Events.Types.removesplice(start, deleteCount, ...items)removeinsertorderremove 携带被删除元素insert 携带新增元素仅当参数多于 2 个即存在插入项时才会额外触发insert与ordersort(compareFn)order无对应Events.Types.orderreverse()order无对应Events.Types.order这些事件名定义在 src/events.js 的Events.Types常量中static Types { play: play, pause: pause, update: update, render: render, resize: resize, change: change, remove: remove, insert: insert, order: order, load: load }各方法的源码实现细节push/unshift—— 插入事件src/collection.jspush() { const pushed super.push.apply(this, arguments); this.trigger(Events.Types.insert, arguments); return pushed; } unshift() { const unshifted super.unshift.apply(this, arguments); this.trigger(Events.Types.insert, arguments); return unshifted; }注意这里的实现技巧先调用原生super.push / super.unshift完成数组变更再以arguments而非返回值作为事件负载触发insert保证监听器拿到的是完整的新增元素列表。pop/shift—— 移除事件src/collection.jspop() { const popped super.pop.apply(this, arguments); this.trigger(Events.Types.remove, [popped]); return popped; } shift() { const shifted super.shift.apply(this, arguments); this.trigger(Events.Types.remove, [shifted]); return shifted; }移除事件负载是单元素数组监听器可以从中取回被删掉的对象。splice—— 复合事件src/collection.jssplice() { const spliced super.splice.apply(this, arguments); this.trigger(Events.Types.remove, spliced); if (arguments.length 2) { const inserted this.slice( arguments[0], arguments[0] arguments.length - 2 ); this.trigger(Events.Types.insert, inserted); this.trigger(Events.Types.order); } return spliced; }arguments.length 2判断的依据是splice的签名是(start, deleteCount, ...items)当传入的实参超过 2 个就意味着存在新增元素...items非空。此时先触发remove携带被删元素再通过slice(start, start items.length)提取实际插入的元素段并触发insert最后触发order通知渲染层元素顺序可能发生变化。sort/reverse—— 顺序事件src/collection.jssort() { super.sort.apply(this, arguments); this.trigger(Events.Types.order); return this; } reverse() { super.reverse.apply(this, arguments); this.trigger(Events.Types.order); return this; }两者均返回this与原生Array.prototype.sort一致以便链式调用。保留原生行为的其他方法indexOf仍然委托给原生实现src/collection.js而map则被改写为返回普通数组resultssrc/collection.js并支持传入可选的scope作为回调的this上下文与原生map的第二参数语义保持一致map(func, scope) { const results []; for (let key 0; key this.length; key) { const value this[key]; let result; if (scope) { result func.call(scope, value, key); } else { result func(value, key); } results.push(result); } return results; }构造函数与初始化方式构造函数src/collection.js支持两种初始化输入传入一个数组new Two.Collection([a, b, c])此时会展开数组并依次push因此会触发一次insert事件传入多个散落参数new Two.Collection(a, b, c)等价于逐个添加。constructor() { super(); if (arguments[0] Array.isArray(arguments[0])) { if (arguments[0].length 0) { this.push.apply(this, arguments[0]); } } else if (arguments.length 0) { this.push.apply(this, arguments); } }事件系统如何注入多继承技巧Collection继承自Array无法再直接继承Events因此源码采用「组合 委托」实现事件能力src/collection.js通过私有字段#events new Events()持有真正的 Events 实例用 getter/setter 将_events、_bound代理到内部实例getter 天然不可枚举因此这些状态不会污染数组的枚举/序列化行为把addEventListener、on、bind、removeEventListener、off、unbind、dispatchEvent、trigger、listen、ignore全部转发给内部 Events 实例。export class Collection extends Array { #events new Events(); get _events() { return this.#events; } set _events(e) { this.#events e; } addEventListener() { return this.#events.addEventListener?.apply(this, arguments); } on() { return this.#events.on?.apply(this, arguments); } bind() { return this.#events.bind?.apply(this, arguments); } trigger() { return this.#events.trigger?.apply(this, arguments); } // ... 其余方法同理 }这意味着每个Collection实例的this就是事件分发的作用域监听器回调中的this指向集合本身。实战监听集合变化综合以上 API一个典型的监听示例假设页面已通过script src.../two.js引入或在模块环境中import { Two } from two.jsconst collection new Two.Collection(); // 监听插入 collection.on(Two.Events.Types.insert, function(items) { console.log(inserted:, items); }); // 监听移除 collection.on(Two.Events.Types.remove, function(items) { console.log(removed:, items); }); // 监听顺序变化 collection.on(Two.Events.Types.order, function() { console.log(order changed); }); collection.push(a, b); // 触发 insert参数 [a, b] collection.push(c); // 触发 insert参数 [c] collection.pop(); // 触发 remove参数 [c] collection.unshift(x); // 触发 insert参数 [x] collection.sort(); // 触发 order collection.reverse(); // 触发 order collection.splice(0, 1, y, z);// 先 remove携带被删元素再 insert携带 [y,z]最后 order事件名insert、remove、order也可以直接以字符串形式传入如collection.on(insert, cb)但建议使用Two.Events.Types常量以保证拼写正确。在 two.js 内部的实际应用Two.Collection 并非孤立的工具类而是构成了 two.js 场景图scene graph与资源管理的基础。以下均为可从当前仓库源码直接验证的使用点。1.Two.Group.Children子元素集合src/children.jsTwo.Group的子元素列表Children extends Collection在insert/remove事件中同步维护按id索引的ids映射实现「既可按索引访问又可按对象 id 查找」的混合访问export class Children extends Collection { #ids {}; constructor(children) { super(children); this.attach(children); this.on(Events.Types.insert, this.attach); this.on(Events.Types.remove, this.detach); } attach(children) { for (let i 0; i children.length; i) { const child children[i]; if (child child.id) { this.ids[child.id] child; } } return this; } detach(children) { for (let i 0; i children.length; i) { delete this.ids[children[i].id]; } return this; } }这正是「事件驱动索引同步」的教科书式应用向 Group 添加/移除子元素时ids映射通过insert/remove事件自动保持一致无需在Group各处手动维护。2. 渐变stops与Points的vertices集合Gradient 用new Collection((stops || []).slice(0))管理颜色控制点stops并监听集合变化以重算渐变纹理Points 用new Collection(vertices || [])管理顶点集合Points内部还专门写了「针对不完整 Collection 对象的 unbind 错误忽略」逻辑src/shapes/points.js#L326从侧面印证了「集合自身可被销毁/替换」这一使用模式。3. 作为静态导出挂载src/two.jsTwo.Collection通过静态属性对外暴露src/two.js#L329static Collection Collection;因此用户可以直接使用new Two.Collection()或在Two.Group、Two.Gradient、Two.Points等对象的源码中继续沿用这一类型。小结Two.Collection 是 two.js 中连接「数据结构」与「事件体系」的枢纽它继承了原生Array的完整数组能力又借助Events混入为每一次结构变更提供insert/remove/order三种事件通知。理解这张 [方法 → 事件] 对照表以及Group.Children借助事件维护ids索引的实现思路能帮助你更深入地把控 two.js 场景图的生命周期也便于在自定义组件中复用它进行「可被监听」的集合管理。相关代码可继续阅读 src/collection.js、src/events.js 与 src/children.js。赞分享图形学前端【免费下载链接】two.jsA renderer agnostic two-dimensional drawing api for the web项目地址https://gitcode.com/gh_mirrors/tw/two.js点击查看免费下载上一篇vim-airline语法高亮性能优化设置设置下一篇android-sunflower中的基准测试结果解释指标含义创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

Codex流式中断根因与TaoToken协议适配指南

Codex流式中断根因与TaoToken协议适配指南

1. 项目概述:这不是简单的URL替换,而是Codex服务链路的底层重定向改造Codex不是普通插件,它是把本地编辑器(VS Code、Cursor)变成AI原生开发环境的核心协议桥。当用户看到“stream disconnected”报错时,90…

2026/9/25 3:09:36 阅读更多 →
RTX 4060 8GB显存跑7B模型:llama.cpp调参实战与FlashAttention优化

RTX 4060 8GB显存跑7B模型:llama.cpp调参实战与FlashAttention优化

1. 为什么偏偏是 RTX 4060 跑 7B 模型这件事值得聊手里有张 RTX 4060,8GB 显存,笔记本端还是桌面端其实差别不小,但核心矛盾是一样的:想跑 7B 级别的模型,显存刚好卡在“能跑但跑不快”的尴尬位置。我前后折腾了差不多…

2026/9/25 3:08:36 阅读更多 →
CentOS 7/8/9 源码编译安装 MySQL 8.0 完整指南

CentOS 7/8/9 源码编译安装 MySQL 8.0 完整指南

聊到在 Linux 上装 MySQL 8.0,很多人第一反应就是yum install mysql-server,或者去官网拖一个二进制包解压完事。但我实际接触过的不少环境,最后还是老老实实走了一遍源码编译安装,倒不是故意跟自己的时间过不去,而是定…

2026/9/25 3:08:36 阅读更多 →

最新新闻

低功耗电压检测电路:MOS管开关控制电阻分压,将待机电流降至nA级

低功耗电压检测电路:MOS管开关控制电阻分压,将待机电流降至nA级

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/25 4:59:53 阅读更多 →
Arduino IDE安装ESP8266卡在99%?换源与离线包方案彻底解决

Arduino IDE安装ESP8266卡在99%?换源与离线包方案彻底解决

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/25 4:59:53 阅读更多 →
Flowbite 搜索输入框组件完全指南:从基础搜索栏到语音搜索与高级筛选

Flowbite 搜索输入框组件完全指南:从基础搜索栏到语音搜索与高级筛选

UI组件前端 【免费下载链接】flowbite Open-source UI component library and front-end development framework based on Tailwind CSS 项目地址: https://gitcode.com/gh_mirrors/fl/flowbite 点击查看 免费下载 搜索框是每个站点的“入口级”交互组件。本篇基于…

2026/9/25 4:59:53 阅读更多 →
ESP32-C3 当管家:软件模拟 SWD 实现 RP2040 固件下载与日志采集

ESP32-C3 当管家:软件模拟 SWD 实现 RP2040 固件下载与日志采集

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/25 4:59:53 阅读更多 →
产线烧录良率排查全攻略:从硬件连接到固件格式的链路诊断

产线烧录良率排查全攻略:从硬件连接到固件格式的链路诊断

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/25 4:59:53 阅读更多 →
从 Codex CLI 到知识库:TaoToken 统一 Key 驱动的 AI 代理个人知识管理全流程

从 Codex CLI 到知识库:TaoToken 统一 Key 驱动的 AI 代理个人知识管理全流程

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/25 4:58:52 阅读更多 →

日新闻

AI元人文:从工具使用到思维重构的深度探索

AI元人文:从工具使用到思维重构的深度探索

最近半年我一直在琢磨一件事:AI元人文到底是什么?说白了,就是“用元视角重新审视人与AI的关系”,也在“探索AI如何反向逼着我们发现自己的思考边界”。标题里的“元探索”,在我看就是一层套一层的追问——当你用AI解决…

2026/9/25 0:00:41 阅读更多 →
Python+CNN车牌识别实战:从数据预处理到模型训练与部署

Python+CNN车牌识别实战:从数据预处理到模型训练与部署

简介:基于Python与卷积神经网络的车牌识别项目,面向计算机视觉初学者及智能交通开发者,目标是帮助用户掌握从数据预处理、模型构建到实际部署的完整流程。压缩包共25个文件,包含jpg/png图像样本、py训练脚本、md说明文档、dat数据…

2026/9/25 0:00:41 阅读更多 →
Vim基础操作全攻略:保存退出、模式切换与高频命令实战

Vim基础操作全攻略:保存退出、模式切换与高频命令实战

1. 项目概述1.1 核心需求解析今天聊聊Vim。写这个题目的原因是:几乎每个后端开发者、运维人员、数据工程师某天都会遇到一个场景——深夜加班,服务器登录界面只有黑底白字,编辑器只有vi/vim,你必须在五分钟内完成一次配置修改并保…

2026/9/25 0:00:41 阅读更多 →

周新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡…

2026/9/24 14:34:13 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/24 9:10:42 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&…

2026/9/24 14:33:56 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/24 12:50:34 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/24 14:33:48 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/24 12:49:17 阅读更多 →