图形学前端【免费下载链接】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),仅供参考
