详解JavaScript调用栈、尾递归和手动优化

详解JavaScript调用栈、尾递归和手动优化

在 JavaScript 中,当函数被调用时,它们会被添加到一个叫做调用栈(Call Stack)的数据结构中。本文将深入探讨 JavaScript 的调用栈是如何工作的,并通过解释尾递归和手动优化等概念,帮助你更好地理解在代码执行过程中发生了什么。

调用栈

调用栈是一个 LIFO(Last In First Out)结构,即后进先出。在 JavaScript 中,它用来记录当前上下文的信息。当一个函数被调用时,其上下文会被添加到调用栈顶端;当函数执行完成后,会从调用栈中弹出并销毁相应的上下文。

例如,下面的代码演示了调用栈的工作过程。

function foo() {
  console.log("foo");
}

function bar() {
  foo();
  console.log("bar");
}

bar();

bar 函数被调用时,bar 上下文将被添加到调用栈顶端。接着,bar 函数调用 foo 函数,将 foo 上下文添加到调用栈。最后,foo 函数完成后,其上下文从调用栈中弹出并销毁,接下来,bar 函数执行完毕,也被弹出并销毁。

尾递归

尾递归是指在一个函数的最后一步调用自身。当递归函数遇到大量无法完全展开的函数调用时,会导致栈溢出并抛出异常。为了避免这种情况发生,开发者可以使用尾递归来改善这种情况。

例如,下面的代码展示了斐波那契数列的递归实现方式。

function fibonacci(n) {
  if (n === 0) return 0;
  if (n === 1) return 1;
  return fibonacci(n - 1) + fibonacci(n - 2);
}

console.log(fibonacci(5)); // 5

其中,当计算 fibonacci(5) 时,需要展开 fibonacci(4)fibonacci(3),然后逐步展开到 fibonacci(0)fibonacci(1),共需递归调用 fibonacci 函数 13 次。这反映到调用栈中,就会有 13 层栈帧。因此,当计算 fibonacci(10000) 时,程序将会崩溃。

使用尾递归来解决该问题,代码如下所示。

function fibonacci(n, prev = 0, curr = 1) {
  if (n === 0) return prev;
  if (n === 1) return curr;
  return fibonacci(n - 1, curr, prev + curr);
}

console.log(fibonacci(5)); // 5

其中,函数 fibonacci 用三个参数:当前数字 n、前一个斐波那契数字 prev 和当前的斐波那契数字 curr,通过在最后一行使用相应的参数来避免不必要的函数调用。由于 JavaScript 引擎进行了尾递归优化,因此该代码只执行 5 次递归调用,而不会导致栈溢出的问题。

手动优化

手动优化是指通过各种手段来改善代码效率的方式。例如,将递归函数改写成迭代形式,避免使用不必要的循环、避免重复计算等等。手动优化可能会使代码更加复杂,并且可能会损害可读性,因此必须进行权衡。

以下是一些手动优化实例。

短路运算符

在条件语句中,可以使用短路运算符来避免不必要的计算。

例如,下面的代码:

if (x > 0 && y > 0) {
  // ...
}

可以使用短路运算符重写为:

if (x > 0 && y > 0 && x + y > 10) {
  // ...
}

如果 x > 0 && y > 0 返回 false,则不会计算 x + y > 10

变量缓存

为了避免重复计算,可以通过使用变量缓存来将结果存储到变量中。

例如,下面的代码:

function test(n) {
  return fib(n) + fib(n - 1);
}

function fib(n) {
  if (n === 0 || n === 1) return n;
  return fib(n - 1) + fib(n - 2);
}

可以使用变量缓存来避免重复计算:

function test(n) {
  const prev = fib(n - 1);
  const curr = fib(n);
  return curr + prev;
}

function fib(n, memo = {}) {
  if (n === 0 || n === 1) return n;
  if (memo[n]) return memo[n];
  memo[n] = fib(n - 1, memo) + fib(n - 2, memo);
  return memo[n];
}

其中,memo 变量用来保存已经计算过的值,避免重复计算。

结论

以上是关于 JavaScript 调用栈、尾递归和手动优化的详细讲解。了解这些概念,可以帮助你更好地掌握 JavaScript 语言的特性,避免在扩展应用时遇到调用栈溢出等问题,并实现更优雅、高效的代码。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:详解JavaScript调用栈、尾递归和手动优化 - Python技术站

(0)
上一篇 2023年6月27日
下一篇 2023年6月27日

相关文章

  • Android仿今日头条滑动页面导航效果

    一、介绍 在Android开发中,实现滑动页面导航效果是比较常见的需求之一。本文针对如何实现仿今日头条的页面滑动导航效果进行详细讲解。 二、实现步骤 1.在布局文件中定义ViewPager和TabLayout控件,用于展示滑动页面和导航栏; 2.在Java代码中定义FragmentPagerAdapter,ViewPager的适配器;通过适配器承载Fragm…

    other 2023年6月20日
    00
  • Spring源码解析之推断构造方法

    标题:Spring源码解析之推断构造方法 前言 在Spring的IoC容器中,我们可以使用自动装配的方式注入Bean实例,Spring会根据构造方法参数的类型和名称来自动匹配注入对应类型的实例。Spring是如何实现自动装配的呢?从源码层面解析,自动装配的核心就是推断构造方法。 推断构造方法 Spring会尝试推断某个Bean的构造方法,根据该构造方法参数类…

    other 2023年6月27日
    00
  • 关于python:int的最大值和最小值

    以下是关于“关于Python:int的最大值和最小值”的完整攻略,包含两个示例。 关于Python:int的最大值和最小值 在Python中,整数类型int的最大值和最小值取决于所使用的平台和版本。在Python3中,整数类型int的最大值和最小值可以使用sys模块中的maxsize和minsize属性来获取。以下是关于获取int的大值和最小值的详细攻略。 …

    other 2023年5月9日
    00
  • SVN 安装教程之服务器和客户端

    SVN 安装教程之服务器和客户端 概述 Subversion(SVN)是一款开源的版本控制软件,它能够对文件和目录进行版本控制,支持同时访问和版本化文本和图像文件,能够快速而高效地操控大量数据。 本篇文章将提供Subversion(SVN)服务器和客户端的安装教程及配置指南。 服务器端安装指南 1. 安装SVN服务器 首先,使用以下命令来安装SVN: sud…

    other 2023年6月25日
    00
  • cmake简介

    CMake简介 CMake是一个跨平台的开源构建系统,用于管理软件构建过程。它使用CMakeLists.txt文件来描述构建过程,并生成适用于各种平台和编译器的构建文件。本攻略中,我们将介绍CMake的基本概念和用法,并提供两个示例。 CMake的基本概念 CMake的基本概念包括以下内容: CMakeLists.txt文件:描述构建过程的文件,包含项目名称…

    other 2023年5月7日
    00
  • C语言编程深入理解取整取余取模问题示例分析

    C语言编程深入理解取整取余取模问题示例分析 什么是取整、取余、取模? 在C语言中,/ 可以用来进行整除(取整)操作,% 可以用来进行取余或取模操作。 当两个整数相除时,如果能够整除,则结果即为商;否则,结果则包括商和余数,其中商为取整结果,而余数则为取余或取模的结果。 取整:将一个浮点数四舍五入或向下取整成整数,例如: int a = 5.6 / 2; //…

    other 2023年6月26日
    00
  • 完整centos搭建openvpn服务详细教程

    以下是“完整CentOS搭建OpenVPN服务详细教程的完整攻略”,包括过程中的两个示例说明。 完整CentOS搭建OpenVPN服务详细教程 OpenVPN是一种开的虚拟私人网络(VPN)解决方案,它可以在不同的操作系统上运行,并提供了安全的远程访问和通信。以下是一份关于在CentOS上搭建OpenVPN服务的详细教程。 1 安装OpenVPN 在Cent…

    other 2023年5月10日
    00
  • PS将任意形状自定义成画笔笔刷

    让我来为您分享如何将任意形状自定义成画笔笔刷的完整攻略。总体过程可分为以下几步: 步骤一:准备素材 首先需要准备好自己想要使用的形状,可以是从网络上下载,也可以自己手绘并扫描成图像,甚至还可以直接使用ps内置形状。这里以使用ps自带形状为例,打开ps软件并新建一个文件,选择画笔工具,在设置面板中选择笔刷形状,点击下拉菜单并选中“其他形状”,在弹出的窗口中可以…

    other 2023年6月25日
    00
合作推广
合作推广
分享本页
返回顶部