tree.ts 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628
  1. import { NodeContext } from './nodeCtx';
  2. export interface TreeRaw {
  3. ID: string;
  4. parentID: string;
  5. seq: number;
  6. [props: string]: any;
  7. }
  8. interface Node<T extends TreeRaw = TreeRaw> extends TreeRaw {
  9. getCtx: () => NodeContext<T>; // ctx对象改为getCtx(),因为handsontable数据循环引用会报错
  10. }
  11. export type TreeNode<T extends TreeRaw = TreeRaw> = Node<T> & T;
  12. export interface TreeIDMap<T extends TreeRaw = TreeRaw> {
  13. [propName: string]: TreeNode<T>;
  14. }
  15. export interface CtxIDMap<T extends TreeRaw = TreeRaw> {
  16. [propName: string]: NodeContext<T>;
  17. }
  18. export interface TreeParentMap<T extends TreeRaw = TreeRaw> {
  19. [propName: string]: TreeNode<T>[];
  20. }
  21. export interface ParentMap<T extends TreeRaw = TreeRaw> {
  22. [propName: string]: (TreeRaw | TreeNode<T>)[];
  23. }
  24. export interface UpdateItem {
  25. parentID?: string;
  26. seq?: number;
  27. [propName: string]: any;
  28. }
  29. export interface UpdateData {
  30. ID: string;
  31. update: UpdateItem;
  32. }
  33. export type None = null | undefined;
  34. export class Tree<T extends TreeRaw = TreeRaw> {
  35. // 原始数据,不经过排序的数据
  36. rawData: TreeNode<T>[];
  37. // 按照树结构拼装好、排好序的数据。实际只是原始数据进行排序,内部元素跟原始数据内部元素的引用是一致的
  38. data: TreeNode<T>[];
  39. rootID: string;
  40. // 默认初始顺序号。在对树结构进行操作后,没必要保证seq连号,只需保证正确排序就行,以减少需要更新的节点。
  41. readonly seqStartIndex = 0;
  42. // ID与原始数据条目映射
  43. IDMap: TreeIDMap<T>;
  44. // parentID与多条同parentID的原始数据条目映射,parentMap内部数组要始终保持正确排序
  45. parentMap: TreeParentMap<T>;
  46. // 节点上下文与节点ID映射
  47. ctxMap: CtxIDMap<T>;
  48. constructor(rawData: TreeRaw[], rootID = '-1', expanded?: boolean) {
  49. this.rootID = rootID;
  50. this.rawData = this.genNodeContext(rawData);
  51. this.rawData = this.sort(this.rawData);
  52. this.data = [];
  53. this.IDMap = {};
  54. this.parentMap = {};
  55. this.ctxMap = {};
  56. this.genMap(this.rawData, expanded);
  57. this.genData();
  58. }
  59. // 根据seq进行排序
  60. sort(nodes: TreeNode<T>[]): TreeNode<T>[] {
  61. return nodes.sort((a, b) => a.seq - b.seq);
  62. }
  63. // 获取节点的所有parentID
  64. getParentIDList(nodes: TreeNode<T>[]): Set<string> {
  65. const parentIDList: Set<string> = new Set();
  66. nodes.forEach(node => parentIDList.add(node.parentID));
  67. return parentIDList;
  68. }
  69. // 生成节点getCtx方法(由于handsontable循环引用bug,将ctx对象改为getCtx方法),用于获取节点上下文,挂载相关节点方法,TreeRaw转换为TreeNode
  70. private genNodeContext(rawData: TreeRaw[]): TreeNode<T>[] {
  71. rawData.forEach(item => {
  72. item.getCtx = () => this.ctxMap[item.ID];
  73. });
  74. return rawData as TreeNode<T>[];
  75. }
  76. // 生成映射表
  77. private genMap(data: TreeNode<T>[], expanded?: boolean): void {
  78. data.forEach(item => {
  79. this.IDMap[item.ID] = item;
  80. this.ctxMap[item.ID] = new NodeContext(item, this, expanded);
  81. (
  82. this.parentMap[item.parentID] || (this.parentMap[item.parentID] = [])
  83. ).push(item);
  84. });
  85. }
  86. // 获取顶层原始数据
  87. getRoots(): TreeNode<T>[] {
  88. return this.parentMap[this.rootID] || [];
  89. }
  90. // 生成按照树结构排好序的数据
  91. private genData(): void {
  92. // genData时不需要排序,因为rawData已经排好序
  93. const roots = this.getRoots();
  94. const pushNodesToData = (nodes: TreeNode<T>[]): void => {
  95. nodes.forEach(node => {
  96. this.data.push(node);
  97. const children = this.parentMap[node.ID];
  98. if (children && children.length) {
  99. pushNodesToData(children);
  100. }
  101. });
  102. };
  103. pushNodesToData(roots);
  104. }
  105. // 重新生成排序序好的数据,不改变原引用
  106. private reGenData(): void {
  107. this.data.splice(0, this.data.length);
  108. this.genData();
  109. }
  110. // 将相关parentMap内的数据、data进行重新排序。新增、删除等操作进行完后,数据需要重新排序
  111. reSortData(nodes: TreeNode<T>[]): void {
  112. const toSortList = this.getParentIDList(nodes);
  113. toSortList.forEach(parentID => {
  114. if (this.parentMap[parentID] && this.parentMap[parentID].length) {
  115. this.sort(this.parentMap[parentID]);
  116. }
  117. });
  118. this.reGenData();
  119. }
  120. // 根据parentID对parentMap进行重新排序,并重新排序生成data
  121. resortDataByID(parentIDList: string[]): void {
  122. parentIDList.forEach(parentID => {
  123. if (this.parentMap[parentID] && this.parentMap[parentID].length) {
  124. this.sort(this.parentMap[parentID]);
  125. }
  126. });
  127. this.reGenData();
  128. }
  129. // 查找ID节点
  130. find(ID: string): TreeNode<T> | null {
  131. return this.IDMap[ID] || null;
  132. }
  133. // 查找ID节点的父节点
  134. findParent(ID: string): TreeNode<T> | null {
  135. const node = this.find(ID);
  136. if (!node) {
  137. return null;
  138. }
  139. return this.find(node.parentID);
  140. }
  141. // 查找ID节点的下一个节点
  142. findNext(ID: string): TreeNode<T> | null {
  143. const node = this.find(ID);
  144. if (!node) {
  145. return null;
  146. }
  147. const nodes = this.parentMap[node.parentID];
  148. const nodeIndex = nodes.findIndex(item => item.ID === node.ID);
  149. if (nodeIndex < 0) {
  150. return null;
  151. }
  152. return nodes[nodeIndex + 1] || null;
  153. }
  154. // 查找ID节点的上一个节点
  155. findPrev(ID: string): TreeNode<T> | null {
  156. const node = this.find(ID);
  157. if (!node) {
  158. return null;
  159. }
  160. const nodes = this.parentMap[node.parentID];
  161. const nodeIndex = nodes.findIndex(item => item.ID === node.ID);
  162. if (nodeIndex < 0) {
  163. return null;
  164. }
  165. return nodes[nodeIndex - 1] || null;
  166. }
  167. // 查询ID节点的子节点
  168. findChildren(ID: string): TreeNode<T>[] {
  169. return this.parentMap[ID] || [];
  170. }
  171. updateValue(updateData: UpdateData[]): void {
  172. if (updateData.length) {
  173. updateData.forEach(updateItem => {
  174. const node = this.find(updateItem.ID);
  175. if (node) {
  176. Object.assign(node, updateItem.update);
  177. }
  178. });
  179. }
  180. }
  181. // 递归获取节点
  182. getNodesPosterity(
  183. treeNodes: TreeNode<T>[],
  184. includeSelf = true
  185. ): TreeNode<T>[] {
  186. const rst: TreeNode<T>[] = [];
  187. const pushNodes = (nodes: TreeNode<T>[]): void => {
  188. nodes.forEach(node => {
  189. if (includeSelf || !treeNodes.includes(node)) {
  190. rst.push(node);
  191. }
  192. const children = this.findChildren(node.ID);
  193. if (children.length) {
  194. pushNodes(children);
  195. }
  196. });
  197. };
  198. pushNodes(treeNodes);
  199. return rst;
  200. }
  201. // 节选中点块是否是可操作的节点块,选中一批节点进行升降、上下移的时候可能需要用到这个判断。
  202. // 以第一个节点为基准,如果后续的节点深度均大于于于等于第一个节点的深度,且节点间是连续的,则此为可操作节点块
  203. isOperable(nodes: TreeNode<T>[]): boolean {
  204. const baseDepth = nodes[0].getCtx().depth();
  205. for (let i = 0; i < nodes.length; i++) {
  206. const node = nodes[i];
  207. const depth = node.getCtx().depth();
  208. if (depth < baseDepth) {
  209. return false;
  210. }
  211. const nextRowNode = nodes[i + 1];
  212. if (
  213. nextRowNode &&
  214. node.getCtx().row() + 1 !== nextRowNode.getCtx().row()
  215. ) {
  216. return false;
  217. }
  218. }
  219. return true;
  220. }
  221. // 从节点块中获取相同深度的节点
  222. sameDepthNodes(nodes: TreeNode<T>[], depth?: number): TreeNode<T>[] {
  223. if (!depth) {
  224. depth = nodes[0].getCtx().depth();
  225. }
  226. return nodes.filter(node => node.getCtx().depth() === depth);
  227. }
  228. // 准备插入节点,插入节点前,计算出需要更新的数据。(可能会直接覆盖插入数据的seq)
  229. // 可调用完此方法后,将需要更新、插入的数据提交至数据库,成功响应后调用插入节点更新缓存的方法
  230. prepareInsert(rawData: TreeRaw[]): UpdateData[] {
  231. const updateData: UpdateData[] = [];
  232. const insertParentMap: ParentMap<T> = {};
  233. // 将相同父项的插入数据和已存在数据进行合并
  234. rawData.forEach(item => {
  235. if (typeof item.seq === 'undefined') {
  236. item.seq = this.seqStartIndex;
  237. }
  238. (
  239. insertParentMap[item.parentID] || (insertParentMap[item.parentID] = [])
  240. ).push(item);
  241. });
  242. const combineMap: ParentMap<T> = {};
  243. Object.entries(insertParentMap).forEach(([parentID, insertItems]) => {
  244. const items = this.parentMap[parentID];
  245. combineMap[parentID] = [...insertParentMap[parentID]];
  246. if (items) {
  247. combineMap[parentID].push(...items);
  248. }
  249. // 重新排序
  250. const combineItems = combineMap[parentID];
  251. this.sort(combineItems as any);
  252. combineItems.forEach((item, index) => {
  253. // 插入数据重新赋值
  254. if (insertItems.includes(item)) {
  255. item.seq = index;
  256. } else if (item.seq !== index) {
  257. // 需要更新的原数据
  258. updateData.push({
  259. ID: item.ID,
  260. update: {
  261. seq: index,
  262. },
  263. });
  264. }
  265. });
  266. });
  267. return updateData;
  268. }
  269. // 插入节点数据
  270. insert(items: TreeRaw[], updateData: UpdateData[] = []): TreeNode<T>[] {
  271. // 更新需要更新的节点(一般为更新seq)
  272. this.updateValue(updateData);
  273. // 建立映射、插入数据
  274. const nodes = this.genNodeContext(items);
  275. this.genMap(nodes);
  276. this.rawData.push(...nodes);
  277. // 排序
  278. this.reSortData(nodes);
  279. return nodes;
  280. }
  281. // 准备删除,返回所有需要删除的节点,包括嵌套节点
  282. prepareDelete(deleteNodes: TreeNode<T>[]): TreeNode<T>[] {
  283. return this.getNodesPosterity(deleteNodes);
  284. }
  285. /**
  286. * 删除节点
  287. * @param treeNodes - 要删除的节点,不需要包含嵌套节点
  288. */
  289. delete(treeNodes: TreeNode<T>[]): TreeNode<T>[] {
  290. const allDeletedNodes: TreeNode<T>[] = [];
  291. // 递归删除节点
  292. const deleteNodes = (nodes: TreeNode<T>[]): void => {
  293. // 删除映射、删除数据
  294. const toDels: { nodes: TreeNode<T>[]; delNode: TreeNode<T> }[] = [];
  295. nodes.forEach(node => {
  296. allDeletedNodes.push(node);
  297. delete this.IDMap[node.ID];
  298. delete this.ctxMap[node.ID];
  299. const children = this.parentMap[node.ID];
  300. delete this.parentMap[node.ID];
  301. const nodesInParentMap = this.parentMap[node.parentID];
  302. if (nodesInParentMap && nodesInParentMap.length) {
  303. const nIndex = nodesInParentMap.findIndex(
  304. item => item.ID === node.ID
  305. );
  306. if (nIndex >= 0) {
  307. toDels.push({ nodes: nodesInParentMap, delNode: node });
  308. }
  309. }
  310. const index = this.rawData.findIndex(item => item.ID === node.ID);
  311. if (index >= 0) {
  312. this.rawData.splice(index, 1);
  313. }
  314. if (children && children.length) {
  315. deleteNodes(children);
  316. }
  317. });
  318. // 删除parentMap的数据
  319. toDels.forEach(delItem => {
  320. const delIndex = delItem.nodes.findIndex(
  321. item => item.ID === delItem.delNode.ID
  322. );
  323. if (delIndex >= 0) {
  324. delItem.nodes.splice(delIndex, 1);
  325. }
  326. });
  327. };
  328. deleteNodes(treeNodes);
  329. // 排序
  330. this.reSortData(allDeletedNodes);
  331. return allDeletedNodes;
  332. }
  333. // IDList 返回所有需要删除的节点,包括嵌套节点
  334. prepareDeleteByID(IDList: string[]): TreeNode<T>[] {
  335. const deleteNodes: TreeNode<T>[] = [];
  336. IDList.forEach(ID => {
  337. const node = this.find(ID);
  338. if (node) {
  339. deleteNodes.push(node);
  340. }
  341. });
  342. return this.prepareDelete(deleteNodes);
  343. }
  344. /**
  345. * 根据ID删除节点
  346. * @param IDList - 要删除的节点的ID列表(不包含嵌套节点ID)
  347. */
  348. deleteByID(IDList: string[]): TreeNode<T>[] {
  349. const deleteNodes: TreeNode<T>[] = [];
  350. IDList.forEach(ID => {
  351. const node = this.find(ID);
  352. if (node) {
  353. deleteNodes.push(node);
  354. }
  355. });
  356. this.delete(deleteNodes);
  357. return deleteNodes;
  358. }
  359. // 准备上移节点块(连续的兄弟节点),注意节点的seq可能不连号
  360. prepareUpMove(nodes: TreeNode<T>[]): UpdateData[] {
  361. const updateData: UpdateData[] = [];
  362. const firstNode = nodes[0];
  363. const firstNodePrev = this.findPrev(firstNode.ID);
  364. if (!firstNodePrev) {
  365. return [];
  366. }
  367. let tempSeq = firstNodePrev.seq;
  368. nodes.forEach(node => {
  369. const orgSeq = node.seq;
  370. updateData.push({
  371. ID: node.ID,
  372. update: { seq: tempSeq },
  373. });
  374. tempSeq = orgSeq;
  375. });
  376. updateData.push({
  377. ID: firstNodePrev.ID,
  378. update: { seq: tempSeq },
  379. });
  380. return updateData;
  381. }
  382. // 准备下移节点块(连续的兄弟节点),注意节点的seq可能不连号
  383. prepareDownMove(nodes: TreeNode<T>[]): UpdateData[] {
  384. const updateData: UpdateData[] = [];
  385. const lastNode = nodes[nodes.length - 1];
  386. const lastNodeNext = this.findNext(lastNode.ID);
  387. if (!lastNodeNext) {
  388. return [];
  389. }
  390. let tempSeq = lastNodeNext.seq;
  391. for (let i = nodes.length - 1; i >= 0; i--) {
  392. const node = nodes[i];
  393. const orgSeq = node.seq;
  394. updateData.push({
  395. ID: node.ID,
  396. update: { seq: tempSeq },
  397. });
  398. tempSeq = orgSeq;
  399. }
  400. updateData.push({
  401. ID: lastNodeNext.ID,
  402. update: { seq: tempSeq },
  403. });
  404. return updateData;
  405. }
  406. // 上下移
  407. move(nodes: TreeNode<T>[], updateData: UpdateData[]): void {
  408. this.updateValue(updateData);
  409. this.reSortData(nodes);
  410. }
  411. // 准备升级节点块(连续的兄弟节点),不维护seq连号
  412. prepareUpLevel(nodes: TreeNode<T>[]): UpdateData[] {
  413. const updateData: UpdateData[] = [];
  414. const firstNode = nodes[0];
  415. const lastNode = nodes[nodes.length - 1];
  416. const parent = this.findParent(firstNode.ID);
  417. if (!parent) {
  418. return [];
  419. }
  420. const baseSeq = parent.seq + 1;
  421. nodes.forEach((node, index) => {
  422. updateData.push({
  423. ID: node.ID,
  424. update: { parentID: parent.parentID, seq: baseSeq + index },
  425. });
  426. });
  427. const parentNextBrothers = parent.getCtx().nextBrothers();
  428. // 因为seq可能是不连号的,如果升级块的最末节点seq,小于下一节点的seq,那就不更新所有下兄弟节点的seq,减少更新的数据量
  429. const lastNodeCurSeq = updateData[updateData.length - 1].update.seq;
  430. const firstBrother = parentNextBrothers[0];
  431. if (lastNodeCurSeq && firstBrother && lastNodeCurSeq >= firstBrother.seq) {
  432. parentNextBrothers.forEach((node, index) => {
  433. updateData.push({
  434. ID: node.ID,
  435. update: { seq: baseSeq + index + nodes.length },
  436. });
  437. });
  438. }
  439. // 最末节点的所有后兄弟节点,成为最末节点的子节点
  440. const lastNodeNextBrothers = lastNode.getCtx().nextBrothers();
  441. const lastNodeLastChild = lastNode.getCtx().lastChild();
  442. const lastNodeLastChildSeq = lastNodeLastChild ? lastNodeLastChild.seq : 0;
  443. lastNodeNextBrothers.forEach((node, index) => {
  444. updateData.push({
  445. ID: node.ID,
  446. update: { parentID: lastNode.ID, seq: index + lastNodeLastChildSeq },
  447. });
  448. });
  449. return updateData;
  450. }
  451. upLevel(nodes: TreeNode<T>[], updateData: UpdateData[]): void {
  452. const firstNode = nodes[0];
  453. const lastNode = nodes[nodes.length - 1];
  454. if (!firstNode.getCtx().canUpLevel()) {
  455. return;
  456. }
  457. const orgParentID = firstNode.parentID;
  458. const orgBrothers = this.parentMap[orgParentID];
  459. const lastNodeNextBrothers = lastNode.getCtx().nextBrothers();
  460. const index = orgBrothers.findIndex(item => item.ID === firstNode.ID);
  461. orgBrothers.splice(index, nodes.length + lastNodeNextBrothers.length);
  462. (this.parentMap[lastNode.ID] || (this.parentMap[lastNode.ID] = [])).push(
  463. ...lastNodeNextBrothers
  464. );
  465. this.updateValue(updateData);
  466. const newParentID = firstNode.parentID;
  467. this.parentMap[newParentID].push(...nodes);
  468. this.resortDataByID([orgParentID, newParentID, lastNode.ID]);
  469. }
  470. // 准备降级节点块(连续的兄弟节点),不维护seq连号
  471. prepareDownLevel(nodes: TreeNode<T>[]): UpdateData[] {
  472. const updateData: UpdateData[] = [];
  473. const firstNode = nodes[0];
  474. const prevNode = this.findPrev(firstNode.ID);
  475. if (!prevNode) {
  476. return [];
  477. }
  478. // 节点块成为前节点的子节点
  479. const prevNodeLastChild = prevNode.getCtx().lastChild();
  480. const baseSeq = prevNodeLastChild
  481. ? prevNodeLastChild.seq + 1
  482. : this.seqStartIndex;
  483. nodes.forEach((node, index) => {
  484. updateData.push({
  485. ID: node.ID,
  486. update: { parentID: prevNode.ID, seq: baseSeq + index },
  487. });
  488. });
  489. return updateData;
  490. }
  491. downLevel(nodes: TreeNode<T>[], updateData: UpdateData[]): void {
  492. const firstNode = nodes[0];
  493. if (!firstNode.getCtx().canDownLevel()) {
  494. return;
  495. }
  496. const prevNode = this.findPrev(firstNode.ID);
  497. if (!prevNode) {
  498. return;
  499. }
  500. const orgBrothers = this.parentMap[firstNode.parentID];
  501. const index = orgBrothers.findIndex(item => item.ID === firstNode.ID);
  502. orgBrothers.splice(index, nodes.length);
  503. (this.parentMap[prevNode.ID] || (this.parentMap[prevNode.ID] = [])).push(
  504. ...nodes
  505. );
  506. this.updateValue(updateData);
  507. this.reGenData();
  508. }
  509. // 准备移动节点块(连续的兄弟节点),不维护seq连号
  510. prepareMoveTo(
  511. nodes: TreeNode<T>[],
  512. parent: TreeNode<T> | None,
  513. next: TreeNode<T> | None
  514. ): UpdateData[] {
  515. const updateData: UpdateData[] = [];
  516. let prev: TreeNode<T> | null;
  517. if (next) {
  518. prev = next.getCtx().prev();
  519. } else {
  520. const roots = this.getRoots();
  521. prev = parent
  522. ? parent.getCtx().lastChild()
  523. : roots[roots.length - 1] || null;
  524. }
  525. const baseSeq = prev ? prev.seq + 1 : this.seqStartIndex;
  526. updateData.push(
  527. ...nodes.map((node, index) => ({
  528. ID: node.ID,
  529. update: {
  530. parentID: (parent && parent.ID) || this.rootID,
  531. seq: baseSeq + index,
  532. },
  533. }))
  534. );
  535. const curBaseSeq = baseSeq + nodes.length;
  536. // eslint-disable-next-line no-nested-ternary
  537. const nextBrothers = prev
  538. ? prev.getCtx().nextBrothers()
  539. : next
  540. ? [next, ...next.getCtx().nextBrothers()]
  541. : [];
  542. updateData.push(
  543. ...nextBrothers.map((node, index) => ({
  544. ID: node.ID,
  545. update: {
  546. seq: curBaseSeq + index,
  547. },
  548. }))
  549. );
  550. return updateData;
  551. }
  552. moveTo(
  553. nodes: TreeNode<T>[],
  554. parent: TreeNode<T> | None,
  555. updateData: UpdateData[]
  556. ): void {
  557. const firstNode = nodes[0];
  558. const newParentID = parent ? parent.ID : this.rootID;
  559. const orgParentID = firstNode.parentID;
  560. const orgBrothers = this.parentMap[orgParentID];
  561. const index = orgBrothers.findIndex(item => item.ID === firstNode.ID);
  562. orgBrothers.splice(index, nodes.length);
  563. (this.parentMap[newParentID] || (this.parentMap[newParentID] = [])).push(
  564. ...nodes
  565. );
  566. this.updateValue(updateData);
  567. this.resortDataByID([orgParentID, newParentID]);
  568. }
  569. // 清除所有节点(不变更data引用)
  570. clear(): void {
  571. this.rawData.splice(0, this.rawData.length);
  572. this.data.splice(0, this.data.length);
  573. Object.keys(this.parentMap).forEach(key => delete this.parentMap[key]);
  574. Object.keys(this.IDMap).forEach(key => delete this.IDMap[key]);
  575. Object.keys(this.ctxMap).forEach(key => delete this.ctxMap[key]);
  576. }
  577. }