为什么 ECMAScript 2017 中的尾调用优化对函数性能有重要影响

从ECMAScript 6开始,Tail Call Optimization(尾调用优化,TCO)已经成为该规范的一部分。尾调用优化是指JavaScript引擎可以优化函数中的最后一次函数调用,使其不再出现在函数调用栈中。这是通过在返回致命的调用时重用当前帧来实现的。从而可以大大减少函数调用时的内存占用和运行时的时间开销。

尾调用优化在递归函数中特别适用。递归函数调用本身就很消耗内存,因此尾调用优化可以有效地解决这个问题,使递归函数能更安全地使用。

让我们看看尾调用优化如何提高递归函数的性能。下面是一个简单的递归函数,用于计算斐波那契数列的第n项:

-------- ------------ -
  -- -- - -- -
    ------ --
  - ---- -
    ------ ----------- - -- - ----------- - ---
  -
-

该函数非常简单,但由于它是递归的,因此当n的值很大时,它的性能显著下降。这是因为每次递归调用都需要在堆栈中创建新的函数调用帧,将函数返回后,这些框架会保留在堆栈中,直到所有调用完成。

现在,让我们重写这个函数,以便使用尾调用优化:

-------- --------------- ---- - -- ---- - -- -
  -- -- --- -- -
    ------ -----
  - ---- -
    ------ -------------- - -- ----- ---- - ------
  -
-

这里有什么不同之处?首先,我们将第三个参数acc2重命名为斐波那契数列的前一个数字,并将第二个参数acc1重命名为当前的斐波那契数列。这是因为尾调用优化需要将所有先前计算的数字传递给下一次递归调用,并用它们替换当前数字。

这个新的函数现在也处理了一个计算斐波那契数列最后一位的变量n。但是,我们在递归调用中只传递了三个参数,而不是四个。

这就是尾调用优化真正的魔力。当递归调用的函数返回时,它将返回到调用该函数的函数,而不是创建一个新的帧,这将清除调用堆栈,从而减少内存使用和执行时间。

虽然这个例子看起来很简单,但请注意,尾调用最大的好处在于它在处理大型递归函数或长时间运行的递归代码时的表现。但是,这种优化并不总是产生效果,对于某些类型的递归函数,改为迭代可能更快。

下面是一个更多的例子以说明这个问题,接下来我们将修改斐波那契数列函数,使它更长时间运行。我们将使用Measuring Execution Time(算法执行时间)库来测试函数的执行时间(这里我们假设你能够安装和使用该库)。

----- -
  -------
- - -----------------------

-------- ------------ -
  -- -- -- -- -
    ------ --
  - ---- -
    ------ ----------- - -- - ----------- - ---
  -
-

-------- --------------- ---- - -- ---- - -- -
  -- -- --- -- -
    ------ -----
  - ---- -
    ------ -------------- - -- ----- ---- - ------
  -
-

---------- -- -
  --------------
-- -------------------- -- -
  ---------- -- -
    ----------------------
  -- ---- ------------------- -- ----
---

上面的代码将分别测试两个函数斐波那契数列和TCO斐波那契数列的执行时间。在经过几次运行之后,您将看到一个显着的时间差异。尾调用优化(即TCO斐波那契数列)的执行时间要比基本递归(即普通斐波那契数列)要少得多。

总结:

尾调用优化可大大减少函数调用时的内存占用和运行时的时间开销。 它在处理大型递归函数或长时间运行的递归代码时是非常有效的,但并不总是产生效果。某些类型的递归函数可能会有更快的替代方法,例如迭代。

为了在您的JavaScript代码中充分利用尾调用优化,请始终使用Tail Recursive结构来处理递归问题,并通过可用的性能测试库(如Measuring Execution Time)来测试代码的性能。

来源:JavaScript中文网 ,转载请联系管理员! 本文地址:https://www.javascriptcn.com/post/654dc4697d4982a6eb72916b


猜你喜欢

  • 在 Mocha 中使用 Sinon 进行 Stub 和 Spy

    在 Mocha 中使用 Sinon 进行 Stub 和 Spy 随着前端应用的复杂度越来越高,前端测试的重要性也变得不言而喻。Mocha 是一个流行的 JavaScript 测试框架,而 Sinon ...

    1 年前
  • 如何优化 JVM 的性能?

    JVM 是 Java 虚拟机的缩写,是 Java 代码被执行的环境。作为开发人员,在开发和部署 Java 项目时,我们需要对 JVM 进行性能优化,以确保应用程序具有更好的性能和可伸缩性。

    1 年前
  • ECMAScript 2020:建立可维护的模块化 JavaScript 代码

    在前端开发中,模块化是重要的概念之一。它能帮助我们更好地组织我们的代码,提高代码的可维护性和可复用性。ECMAScript 2020(ES2020)为 JavaScript 开发者带来了许多新的特性,...

    1 年前
  • 如何使用 Flexbox 创建一个固定宽度的右侧导航栏

    在网页设计中,经常需要创建一个固定宽度的右侧导航栏。我们可以使用CSS的Flexbox来实现这一功能,而且它还可以带来很多额外的好处。在这篇文章中,我将详细介绍如何使用Flexbox创建一个固定宽度的...

    1 年前
  • SPA 应用中如何解决数据缓存问题?

    在单页面应用(SPA)开发中,经常需要处理组件之间的数据共享问题,特别是对于大型应用,如果每个组件都向服务器请求数据,将会影响应用的性能。因此,使用数据缓存以及处理数据的方法是至关重要的。

    1 年前
  • Fastify 中使用 Mockjs 模拟 API 数据

    前言 在前端开发过程中,我们经常需要模拟 API 数据,以便在本地开发和调试时能够正常运行。本篇文章将介绍如何在 Fastify 中使用 Mockjs 模拟 API 数据。

    1 年前
  • 利用 CSS Grid 实现多列元素等高的技巧

    在前端开发中,经常需要将多个元素排列成多列等高的布局,这种布局方式可以让页面看起来更加整洁美观。但是传统的布局方式往往需要使用 JavaScript 或表格布局等方法来实现,这些方法存在兼容性问题和代...

    1 年前
  • Custom Elements 中如何处理跨组件通信

    前言 在前端开发中,组件化是一种非常常见的模式。而通过使用 Custom Elements,我们可以自定义 HTML 元素,进一步实现组件的封装和复用。但是,组件之间的通信显然是不能被忽略的,同时,如...

    1 年前
  • Google Material Design 框架的指南和类型分类

    Google Material Design 是一种基于“材料”的设计语言,旨在通过清晰的设计和动画来提供直观、自然和舒适的用户体验。该框架被广泛应用于移动端和 web 端应用程序的设计中。

    1 年前
  • TypeScript 中的类和继承:详解和最佳实践

    在前端开发中,使用 TypeScript 的开发者都应该对 TypeScript 中的类和继承有一定的了解。本文将详细介绍 TypeScript 中的类和继承,包括概念、语法、最佳实践等。

    1 年前
  • 如何使用 Chai 测试 Express 路由

    在前端开发中,测试是必不可少的一环,保证代码质量和可靠性。而对于后端开发而言,测试更是必须的。在 Express 中,我们可以使用 Chai 来测试路由是否正确。 Chai 是什么? Chai 是一个...

    1 年前
  • 在 Next.js 中使用 moment.js

    在开发 Web 应用程序时,时间是一个常见而且重要的概念。在 JavaScript 中,表示日期和时间的最基本的方法是使用内置的 Date 对象。但是,它的 API 很简单,而且不太适合处理复杂的时间...

    1 年前
  • 如何在 GraphQL 中处理循环依赖的问题

    什么是循环依赖 在前端开发中,循环依赖常常会出现。循环依赖指的是在几个模块之间互相引用。例如,模块 A 引用了模块 B,同时模块 B 也引用了模块 A。这种情况下,很容易出现死循环,导致应用卡顿或者崩...

    1 年前
  • 使用 Socket.io 进行多个客户端之间的通讯

    随着 Web 技术和移动设备的快速发展,人们对实时通讯和多人协同工作的需求日益增长。而 Socket.io 作为一个实现了 WebSocket 协议并支持多种传输方式的 JavaScript 库,成为...

    1 年前
  • ES9 带来的新特性:for...await...of 循环

    ES9 带来了一个新的特性:for...await...of 循环。这个特性可以极大地改善我们在异步编程时使用迭代器的体验。在这篇文章中,我们将会详细讲解这个特性,并提供示例代码。

    1 年前
  • 在 Less 中使用 padding 方式的注意事项

    Less 是一种动态样式表语言,它扩展了 CSS 的语法,支持变量、混合、函数与嵌套等特性。在 Less 中,我们可以使用 padding 属性来设置元素的内边距,但是使用 padding 属性需要注...

    1 年前
  • 利用 ES10 中的 Object.fromEntries() 方法快速将数组转为对象

    在前端开发中,我们常常需要将数组转换为对象。在 ES10 中,新增的 Object.fromEntries() 方法可以非常方便地实现这个转换过程。 Object.fromEntries() 方法简介...

    1 年前
  • Jest 运行测试时报错 "SyntaxError: Unexpected token import" 的解决方法

    在前端开发中,Jest 是一款非常流行的 JavaScript 测试框架,可以帮助开发者简化测试流程,提高开发效率。但在使用 Jest 进行测试时,有时会遇到 SyntaxError: Unexpec...

    1 年前
  • 否则 CSS Reset? Bootstrap 带有基本 CSS Reset 方法

    在前端开发过程中,我们经常会发现自己写出的页面布局可能与我们期望的有所偏差,这通常是由于浏览器的默认样式造成的。这实际上是浏览器由于历史原因而存在的问题,无论是 old school 浏览器还是最新的...

    1 年前
  • Babel 编译 TypeScript 的最佳实践

    前言 TypeScript 是一种强类型的 JavaScript 超集,在前端开发中得到了广泛的应用。然而,TypeScript 不能直接被浏览器解析,需要通过编译成 JavaScript 才能在浏览...

    1 年前

相关推荐

    暂无文章