From a4375cb53265fb2481a2b57271e58395bb85a5b2 Mon Sep 17 00:00:00 2001 From: Cheton Wu Date: Sun, 21 Nov 2021 08:59:32 +0800 Subject: [PATCH] feat: remove array items without consuming extra memory (#59) * feat: add remove-array-items that can remove items from an array without generating memory garbage * chore: replace arr.splice(start, deleteCount) with removeArrayItems(arr, start, deleteCount) --- src/infinite-tree.js | 17 +++++++------ src/remove-array-items.js | 27 ++++++++++++++++++++ test/remove-array-items.js | 51 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 87 insertions(+), 8 deletions(-) create mode 100644 src/remove-array-items.js create mode 100644 test/remove-array-items.js diff --git a/src/infinite-tree.js b/src/infinite-tree.js index f7fead4..eefd015 100644 --- a/src/infinite-tree.js +++ b/src/infinite-tree.js @@ -8,9 +8,10 @@ import { flatten, Node } from 'flattree'; import Clusterize from './clusterize'; import ensureArray from './ensure-array'; import extend from './extend'; -import { get } from './utilities'; import LookupTable from './lookup-table'; +import removeArrayItems from './remove-array-items'; import { defaultRowRenderer } from './renderer'; +import { get } from './utilities'; import { preventDefault, addEventListener, @@ -749,8 +750,8 @@ class InfiniteTree extends events.EventEmitter { } // Update nodes & rows - this.nodes.splice(nodeIndex + 1, total); - this.rows.splice(nodeIndex + 1, total); + removeArrayItems(this.nodes, nodeIndex + 1, total); + removeArrayItems(this.rows, nodeIndex + 1, total); // Toggle the collapsing state node.state.collapsing = false; @@ -1361,8 +1362,8 @@ class InfiniteTree extends events.EventEmitter { if (parentNodeIndex >= 0) { // Update nodes & rows - this.nodes.splice(parentNodeIndex + 1, deleteCount); - this.rows.splice(parentNodeIndex + 1, deleteCount); + removeArrayItems(this.nodes, parentNodeIndex + 1, deleteCount); + removeArrayItems(this.rows, parentNodeIndex + 1, deleteCount); // Update the row corresponding to the parent node this.rows[parentNodeIndex] = this.options.rowRenderer(parentNode, this.options); @@ -1441,15 +1442,15 @@ class InfiniteTree extends events.EventEmitter { } // Update parent node - parentNode.children.splice(parentNode.children.indexOf(node), 1); + removeArrayItems(parentNode.children, parentNode.children.indexOf(node), 1); if (parentNode !== this.state.rootNode) { parentNode.state.open = parentNode.state.open && (parentNode.children.length > 0); } if (nodeIndex >= 0) { // Update nodes & rows - this.nodes.splice(nodeIndex, deleteCount); - this.rows.splice(nodeIndex, deleteCount); + removeArrayItems(this.nodes, nodeIndex, deleteCount); + removeArrayItems(this.rows, nodeIndex, deleteCount); } // Update the row corresponding to the parent node diff --git a/src/remove-array-items.js b/src/remove-array-items.js new file mode 100644 index 0000000..f5ca393 --- /dev/null +++ b/src/remove-array-items.js @@ -0,0 +1,27 @@ +/** + * Remove a range of items from an array. + * + * @function removeItems + * @param {Array<*>} arr The target array. + * @param {number} startIndex The index to begin removing from (inclusive). + * @param {number} removeCount How many items to remove. + */ +const removeArrayItems = (arr, startIndex, removeCount) => { + const length = arr.length; + + if (startIndex >= length || removeCount <= 0 || startIndex < 0) { + return; + } + + removeCount = (startIndex + removeCount > length ? length - startIndex : removeCount); + + const len = length - removeCount; + + for (let i = startIndex; i < len; ++i) { + arr[i] = arr[i + removeCount]; + } + + arr.length = len; +}; + +export default removeArrayItems; diff --git a/test/remove-array-items.js b/test/remove-array-items.js new file mode 100644 index 0000000..db787dd --- /dev/null +++ b/test/remove-array-items.js @@ -0,0 +1,51 @@ +import { test } from 'tap'; +import removeArrayItems from '../src/remove-array-items'; + +test('should return if the start index is greater than or equal to the length of the array', (t) => { + const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + removeArrayItems(arr, arr.length + 1, 5); + t.equals(arr.length, 10); + t.end(); +}) + +test('should return if the remove count is 0', (t) => { + const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + removeArrayItems(arr, 2, 0); + t.equals(arr.length, 10); + t.end(); +}); + +test('should remove the number of elements specified from the array, starting from the start index', (t) => { + const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + removeArrayItems(arr, 3, 4); + t.deepEquals(arr, [1, 2, 3, 8, 9, 10]); + t.end(); +}); + +test('should remove other elements if delete count is larger than the number of elements after start index', (t) => { + const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + removeArrayItems(arr, 7, 10); + t.deepEquals(arr, [1, 2, 3, 4, 5, 6, 7]); + t.end(); +}); + +test('should remove no element if count is less than or equal to zero', function (t) { + const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + removeArrayItems(arr, 7, -2); + t.deepEquals(arr, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + t.end(); +}); + +test('should remove no element if start is less than or equal to zero', (t) => { + const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + removeArrayItems(arr, -7, 5); + t.deepEquals(arr, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + t.end(); +}); + +test("should remove the remaining elements start with 'start' if count is greater than arr.length", (t) => { + const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + removeArrayItems(arr, 4, 100); + t.deepEquals(arr, [1, 2, 3, 4]); + t.end(); +});