JavaScript中Spread语法与生成器实现树转数组的2倍性能差原因探究
二叉树前序遍历两种实现的性能差异原因分析
我在练习JavaScript函数式编程时,测试了两种二叉树前序遍历转数组的实现:基于spread语法的seql方法,以及基于yield关键字的生成器preOrderTraversal方法。测试结果显示前者性能约为后者的2倍,前者每秒操作数在700-1500间波动,后者稳定在400左右。以下是测试代码:
class BinaryTreeNode { constructor(key, value = key, parent = null) { this.key = key; this.value = value; this.parent = parent; this.left = null; this.right = null; } get isLeaf() { return this.left === null && this.right === null; } get hasChildren() { return !this.isLeaf; } } class BinaryTree { constructor(key, value = key) { this.root = new BinaryTreeNode(key, value); } seql(node = this.root) { return [ node, ...(node.left ? this.seql(node.left) : []), ...(node.right ? this.seql(node.right) : []), ]; } *preOrderTraversal(node = this.root) { yield node; if (node.left) yield* this.preOrderTraversal(node.left); if (node.right) yield* this.preOrderTraversal(node.right); } fill() { for (let node of this.seql()) { if (node.left === null) { node.left = new BinaryTreeNode(Math.random(), Math.random(), node); } if (node.right === null) { node.right = new BinaryTreeNode(Math.random(), Math.random(), node); } } } } let i = 1; const tree = new BinaryTree(Math.random, Math.random()); while (i < 13) { tree.fill(); i++; } const test = () => { [...tree.preOrderTraversal()]; //[...tree.seql()]; }; const iterationsC = 50; const main = async () => { const runBaseline = (iterations = iterationsC) => { let startTime = new Date().getTime(); for (let i = 0; i < iterations; i++) { // no operation } let endTime = new Date().getTime(); return endTime - startTime; }; const runTest = (iterations = iterationsC) => { let startTime = new Date().getTime(); for (let i = 0; i < iterations; i++) test(); let endTime = new Date().getTime(); let baselineTime = runBaseline(iterations); let totalTime = endTime - startTime - baselineTime; let fnTime = (totalTime / iterations).toFixed(10); let operations = Math.floor((1000 / totalTime) * iterations); console.log(`total: ${totalTime}, time: ${fnTime}, op/sec: ${operations}`); }; while (true) { runTest(); await sleep(50); } function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } }; main();
性能差异的核心原因:
生成器的状态管理开销:生成器函数每次调用都会创建独立的迭代器对象,
yield和yield*操作需要频繁暂停、保存函数执行上下文,恢复时又要重新加载状态,这一系列操作的开销远高于普通函数的直接执行。Spread语法的引擎优化:
seql方法通过递归+spread构建数组,现代JavaScript引擎对数组字面量和spread操作有深度优化,会将多次数组拼接合并为高效的内存分配与拷贝,即便产生中间数组,引擎优化后的开销也远低于生成器的迭代流程。迭代器转数组的额外步骤:用
[...generator]转换数组时,需要逐个调用迭代器的next()方法,每次调用都会触发生成器的状态恢复、执行到下一个yield,这层迭代协议的调用链比直接递归构建数组多了大量额外操作。递归模式的效率差异:
seql是普通递归函数,每次调用直接返回数组,引擎对普通递归的栈处理更直接高效;而preOrderTraversal的yield*本质是迭代器委托,涉及嵌套迭代器调用,内部逻辑复杂度更高,开销自然更大。
内容的提问来源于stack exchange,提问作者Likepineapple
相关产品推荐
相关产品推荐

