Efficient tree and linked list data structure using ES6 Symbols for DOM tree backing
—
Convert tree structures to arrays with optional filtering and custom target arrays.
Convert all children of a parent object to an array.
/**
* Convert all children of parent to an array
* Time Complexity: O(n) where n is the number of children
* @param {Object} parent - Parent object
* @param {Object} [options] - Conversion options
* @param {Array} [options.array=[]] - Target array to append to
* @param {Function} [options.filter] - Filter function, receives (object) and should return boolean
* @param {*} [options.thisArg] - Value to use as 'this' when executing filter
* @returns {Object[]} Array of children (filtered if filter provided)
*/
childrenToArray(parent: Object, options?: {
array?: Object[];
filter?: (object: Object) => boolean;
thisArg?: any;
}): Object[];Convert all inclusive ancestors of an object to an array.
/**
* Convert all inclusive ancestors of object to an array
* Time Complexity: O(n) where n is the number of ancestors
* @param {Object} object - Starting object
* @param {Object} [options] - Conversion options
* @param {Array} [options.array=[]] - Target array to append to
* @param {Function} [options.filter] - Filter function, receives (object) and should return boolean
* @param {*} [options.thisArg] - Value to use as 'this' when executing filter
* @returns {Object[]} Array of ancestors including the starting object (filtered if filter provided)
*/
ancestorsToArray(object: Object, options?: {
array?: Object[];
filter?: (object: Object) => boolean;
thisArg?: any;
}): Object[];Convert an entire tree to an array in tree order.
/**
* Convert entire tree to an array in tree order (depth-first)
* Time Complexity: O(n) where n is the number of objects in the subtree
* @param {Object} root - Root object of the tree/subtree
* @param {Object} [options] - Conversion options
* @param {Array} [options.array=[]] - Target array to append to
* @param {Function} [options.filter] - Filter function, receives (object) and should return boolean
* @param {*} [options.thisArg] - Value to use as 'this' when executing filter
* @returns {Object[]} Array of tree objects in tree order (filtered if filter provided)
*/
treeToArray(root: Object, options?: {
array?: Object[];
filter?: (object: Object) => boolean;
thisArg?: any;
}): Object[];const SymbolTree = require("symbol-tree");
const tree = new SymbolTree();
// Build a tree structure
const parent = { name: "parent", type: "container" };
const child1 = { name: "child1", type: "item" };
const child2 = { name: "child2", type: "item" };
const child3 = { name: "child3", type: "item" };
const grandchild = { name: "grandchild", type: "item" };
tree.appendChild(parent, child1);
tree.appendChild(parent, child2);
tree.appendChild(parent, child3);
tree.appendChild(child1, grandchild);
// Convert children to array
const children = tree.childrenToArray(parent);
console.log(children.map(c => c.name)); // ["child1", "child2", "child3"]
// Get all ancestors of grandchild
const ancestors = tree.ancestorsToArray(grandchild);
console.log(ancestors.map(a => a.name)); // ["grandchild", "child1", "parent"]
// Convert entire tree to array
const allNodes = tree.treeToArray(parent);
console.log(allNodes.map(n => n.name));
// ["parent", "child1", "grandchild", "child2", "child3"]// Filter children by type
const itemChildren = tree.childrenToArray(parent, {
filter: (child) => child.type === "item"
});
console.log(itemChildren.map(c => c.name)); // ["child1", "child2", "child3"]
// Filter ancestors (exclude the starting object)
const actualAncestors = tree.ancestorsToArray(grandchild, {
filter: (obj) => obj !== grandchild
});
console.log(actualAncestors.map(a => a.name)); // ["child1", "parent"]
// Filter tree nodes by name pattern
const childNodes = tree.treeToArray(parent, {
filter: (obj) => obj.name.includes("child")
});
console.log(childNodes.map(n => n.name));
// ["child1", "grandchild", "child2", "child3"]// Append to existing array
const existingArray = [{ name: "external", type: "other" }];
const combined = tree.childrenToArray(parent, {
array: existingArray
});
console.log(combined.map(c => c.name));
// ["external", "child1", "child2", "child3"]
// Using thisArg for filter context
class NodeAnalyzer {
constructor(targetType) {
this.targetType = targetType;
}
isTargetType(obj) {
return obj.type === this.targetType;
}
getFilteredChildren(parent, tree) {
return tree.childrenToArray(parent, {
filter: this.isTargetType,
thisArg: this
});
}
}
const analyzer = new NodeAnalyzer("item");
const filteredChildren = analyzer.getFilteredChildren(parent, tree);
console.log(filteredChildren.map(c => c.name)); // ["child1", "child2", "child3"]// Complex filter: only nodes with specific attributes
const specialNodes = tree.treeToArray(parent, {
filter: (obj) => {
return obj.type === "item" &&
obj.name.length > 5 &&
tree.hasChildren(obj);
}
});
// Filter with index-like functionality (get every 2nd child)
let childIndex = 0;
const everySecondChild = tree.childrenToArray(parent, {
filter: () => {
const include = childIndex % 2 === 0;
childIndex++;
return include;
}
});
// Build hierarchy information while converting
const hierarchyInfo = [];
tree.treeToArray(parent, {
filter: (obj) => {
const depth = tree.ancestorsToArray(obj).length - 1;
hierarchyInfo.push({
node: obj,
depth: depth,
hasChildren: tree.hasChildren(obj)
});
return true; // Include all nodes
}
});
console.log(hierarchyInfo);
// Results in array with depth and children info for each node// Pre-allocate array for known size (performance optimization)
const childCount = tree.childrenCount(parent);
const preallocated = new Array(childCount);
const children = tree.childrenToArray(parent, {
array: preallocated
});
// Batch multiple conversions
const results = {
children: [],
ancestors: [],
tree: []
};
// Reuse the same result object
tree.childrenToArray(parent, { array: results.children });
tree.ancestorsToArray(grandchild, { array: results.ancestors });
tree.treeToArray(parent, { array: results.tree });// Simulating DOM operations
function getElementsByType(root, targetType) {
return tree.treeToArray(root, {
filter: (obj) => obj.type === targetType
});
}
function getDirectChildren(parent, tagName) {
return tree.childrenToArray(parent, {
filter: (child) => child.tagName === tagName
});
}
function getAncestorsByClass(node, className) {
return tree.ancestorsToArray(node, {
filter: (ancestor) => {
return ancestor !== node &&
ancestor.className &&
ancestor.className.includes(className);
}
});
}
// Usage
const divs = getElementsByType(parent, "div");
const buttons = getDirectChildren(parent, "button");
const containers = getAncestorsByClass(grandchild, "container");// Chain array operations after conversion
const processedChildren = tree.childrenToArray(parent)
.filter(child => child.type === "item")
.map(child => ({ ...child, processed: true }))
.sort((a, b) => a.name.localeCompare(b.name));
// Reduce tree to summary
const treeSummary = tree.treeToArray(parent)
.reduce((summary, node) => {
summary.totalNodes++;
summary.byType[node.type] = (summary.byType[node.type] || 0) + 1;
if (tree.hasChildren(node)) {
summary.containers++;
}
return summary;
}, { totalNodes: 0, containers: 0, byType: {} });
console.log(treeSummary);
// { totalNodes: 5, containers: 2, byType: { container: 1, item: 4 } }Install with Tessl CLI
npx tessl i tessl/npm-symbol-tree