关于ES6尾调用优化的使用


Posted in Javascript onSeptember 11, 2020

ES6包含了一个性能领域的特殊要求。这与一个涉及函数调用的特定优化形式相关:即尾调用优化(Tail Call Optimization,TCO)。简单地说,尾调用就是一个出现在另一个函数“结尾”处的函数调用。这个调用结束之后就没有其余事情要做了(除了可能要返回结果值)

什么尾调用

举个例子,下面是一个非递归的尾调用:

function foo(x) {
 return x
}

// 尾调用
function bar(y) {
 return foo(y + 1)
}

// 非尾调用
function baz() {
 return 1 + bar(40)
}

baz()  // 输出42

说明: foo(y+1) 是 bar(...) 中的尾调用,因为在 foo(...) 完成后, bar(...) 也完成了,并且只需要返回 foo(...) 调用的结果。然而, bar(40) 不是尾调用,因为在它完成后,它的结果需要加上1才能由 baz() 返回。

在JavaScript里,调用一个新的函数需要额外的一块预留内容来管理调用栈,成为栈帧。所以前面的代码一般会同时需要为每个 baz() 、 bar(...) 、 foo(...) 保留一个栈帧。

然而,如果支持TCO的引擎能够意识到 foo(y+1) 调用位于尾部,这意味着 bar(...) 基本上已经完成了,那么在调用 foo(...) 时,它就不需要创建一个新的帧栈,而是可以重用已有的 bar(...) 的帧栈。这样不仅速度快,而且节省内存。

什么是尾递归

在计算机科学里,尾调用是指一个函数里的最后一个动作是一个函数调用的情形:即这个调用的返回值直接被当前函数返回的情形。这种情形下称该调用位置为尾位置。若这个函数在尾位置调用本身(或是一个尾调用本身的其他函数等等),则称这种情况为尾递归,是递归的一种特殊情形。尾调用不一定是递归调用,但是尾递归特别有用,也比较容易实现。

TCO的意义

在程序运行时,计算机会为应用程序分配一定的内存空间;应用程序则会自行分配所获得的内存空间,其中一部分被用于记录程序中正在调用的各个函数的运行情况,这就是函数的调用栈。常规的函数调用总是会在调用栈最上层添加一个新的堆栈帧(stack frame,也翻译为“栈帧”或简称为“帧”),这个过程被称作“入栈”或“压栈”(意即把新的帧压在栈顶)。当函数的调用层数非常多时,调用栈会消耗不少内存,甚至会撑爆内存空间(栈溢出),造成程序严重卡顿或意外崩溃。尾调用的调用栈则特别易于优化,从而可减少内存空间的使用,也能提高运行速度。其中,对尾递归情形的优化效果最为明显,尤其是递归算法非常复杂的情形。

在简单的代码片段中,这类优化算不了什么,但是在处理递归时,这就解决了大问题,特别是如果递归可能会导致成千上百个栈帧的时候。有了TCO,引擎可以用同一个栈帧执行所有的这类调用!

递归是 JavaScript 中一个纷繁复杂的主题。因为如果没有TCO的话,引擎需要实现一个随意的限制来界定递归栈的深度,达到了就得停止,以防止内存耗尽。有了TCO,尾调用的递归函数本质上就可以任意运行,因为再也不需要使用额外的内存,也没有了内存溢出的问题。

下面用尾递归实现一个典型的阶乘函数:

// 用循环实现
function factorial(n) {
 if (n<2) return 1

 var res = 1
 for (var i = n; i > 1; i--) {
  res *= i
 }
 return res
}

// 用尾递归实现
function factorial(n) {
 function fact(n, res) {
  if (n < 2) return res 
  return fact(n-1, n*res)
 }
 return fact(n, 1)
}

factorial(5)  // 输出120

注意:TCO只用于有实际的尾调用的情况,如果你写了一个没有尾递调用的函数,那么性能还是会回到普通帧栈分配的情形,引擎对这样的递归调用栈的限制也仍然有效。

总结

一般来说,尾调用消除是可选的,可以用,也可以不用。然而,在函数编程语言中,语言标准通常会要求编译器或运行平台实现尾调用消除。这让程序员可以用递归取代循环而不丧失性能。ES6之所以要求引擎实现TCO而不是将其留给引擎自由决定,一个原因是缺乏TCO会导致一些JavaScript算法因为害怕调用栈限制而降低了通过递归实现的概率。

如果在所有的情况下引擎缺乏TCO只是降低了性能,那它就不会成为ES6所要求的东西。但是,由于缺乏TCO确实可以使一些程序变得无法实现,所以它就成为了一个重要的语言特性而不是隐藏的实现细节。ES6确保了JavaScript开发者从现在开始可以在所有符合ES6+的浏览器中依赖这个优化。这对JavaScript性能来说是一个胜利。

参考文献

《你不知道的JavaScript-中卷》

到此这篇关于关于ES6尾调用优化的使用的文章就介绍到这了,更多相关ES6尾调用优化内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Javascript 相关文章推荐
Javascript操纵Cookie实现购物车程序
Nov 23 Javascript
幻灯片带网页设计中的20个奇妙应用示例小结
May 27 Javascript
javascrpt绑定事件之匿名函数无法解除绑定问题
Dec 06 Javascript
iframe窗口高度自适应的又一个巧妙实现思路
Apr 04 Javascript
与Math.pow 相反的函数使用介绍
Aug 04 Javascript
教你如何使用node.js制作代理服务器
Nov 26 Javascript
z-blog SyntaxHighlighter 长代码无法换行解决办法(基于jquery)
Nov 18 Javascript
jQuery soColorPacker 网页拾色器
Jun 22 Javascript
JavaScript实现两个select下拉框选项左移右移
Mar 09 Javascript
react-native-tab-navigator组件的基本使用示例代码
Sep 07 Javascript
通过实例学习React中事件节流防抖
Jun 17 Javascript
vuex存储token示例
Nov 11 Javascript
在js文件中引入(调用)另一个js文件的三种方法
Sep 11 #Javascript
Vue项目开发常见问题和解决方案总结
Sep 11 #Javascript
JavaScript实现简单日历效果
Sep 11 #Javascript
vue $mount 和 el的区别说明
Sep 11 #Javascript
JavaScript 判断数据类型的4种方法
Sep 11 #Javascript
jQuery实现日历效果
Sep 11 #jQuery
JS实现密码框效果
Sep 10 #Javascript
You might like
php 特殊字符处理函数
2008/09/05 PHP
PHP中使用foreach和引用导致程序BUG的问题介绍
2012/09/05 PHP
深入解析PHP内存管理之谁动了我的内存
2013/06/20 PHP
Ubuntu中搭建Nginx、PHP环境最简单的方法
2015/03/05 PHP
thinkphp5.0自定义验证规则使用方法
2017/11/16 PHP
对JavaScript的eval()中使用函数的进一步讨论
2008/07/26 Javascript
jQuery获取地址栏参数插件(模仿C#)
2010/10/26 Javascript
基于jQuery的动态表格插件
2011/03/28 Javascript
jquery实现简单易懂的图片展示小例子
2013/11/21 Javascript
jquery 字符串切割函数substring的用法说明
2014/02/11 Javascript
node.js中的emitter.on方法使用说明
2014/12/10 Javascript
javascript实现随时变化着的背景颜色
2015/04/02 Javascript
Javascript实现的简单右键菜单类
2015/09/23 Javascript
JavaScript中removeChild 方法开发示例代码
2016/08/15 Javascript
jquery实现下拉框左右选择功能
2017/02/21 Javascript
JavaScript对象拷贝与Object.assign用法实例分析
2018/06/20 Javascript
layui 表格操作列按钮动态显示的实现方法
2019/09/06 Javascript
JavaScript实现简单的弹窗效果
2020/05/19 Javascript
[01:09]DOTA2次级职业联赛 - ishow.HMM战队宣传片
2014/12/01 DOTA
[56:00]DOTA2上海特级锦标赛主赛事日 - 4 胜者组决赛Secret VS Liquid第一局
2016/03/05 DOTA
Python获取命令实时输出-原样彩色输出并返回输出结果的示例
2019/07/11 Python
python在不同条件下的输入与输出
2020/02/13 Python
Python 读取xml数据,cv2裁剪图片实例
2020/03/10 Python
Python3中FuzzyWuzzy库实例用法
2020/11/18 Python
python drf各类组件的用法和作用
2021/01/12 Python
python学习之使用Matplotlib画实时的动态折线图的示例代码
2021/02/25 Python
HTML5高仿微信聊天、微信聊天表情|对话框|编辑器功能
2018/04/23 HTML / CSS
html5的pushstate以及监听浏览器返回事件的实现
2020/08/11 HTML / CSS
新西兰演唱会和体育门票网站:Ticketmaster新西兰
2017/10/07 全球购物
Unix控制后台进程都有哪些进程
2016/09/22 面试题
营业员实习自我鉴定
2013/12/07 职场文书
竞选大队长演讲稿
2014/04/29 职场文书
爱护花草树木的标语
2014/06/11 职场文书
物业总经理助理岗位职责
2014/06/29 职场文书
教学副校长工作总结
2015/08/13 职场文书
祝福语集锦:送给闺蜜的生日祝福语
2019/10/08 职场文书