关于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 相关文章推荐
jquery ajax 同步异步的执行示例代码
Jun 23 Javascript
Javascript中的默认参数详解
Oct 22 Javascript
JQuery使用$.ajax和checkbox实现下次不在通知功能
Apr 16 Javascript
$.extend 的一个小问题
Jun 18 Javascript
JS实现3D图片旋转展示效果代码
Sep 22 Javascript
jQuery实现可关闭固定于底(顶)部的工具条菜单效果
Nov 06 Javascript
js实现页面a向页面b传参的方法
May 29 Javascript
为什么我们要做三份 Webpack 配置文件
Sep 18 Javascript
ActiveX控件的使用-js实现打印超市小票功能代码详解
Nov 22 Javascript
关于vue面试题汇总
Mar 20 Javascript
JavaScript作用域链实例详解
Jan 21 Javascript
js实现轮播图特效
May 28 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
smarty半小时快速上手入门教程
2014/10/27 PHP
PHP+Ajax实现的检测用户名功能简单示例
2019/02/12 PHP
php利用array_search与array_column实现二维数组查找
2019/07/08 PHP
laravel框架使用极光推送消息操作示例
2020/02/15 PHP
学习jquery之一
2007/04/27 Javascript
基于jquery实现状态限定编辑的代码
2012/02/11 Javascript
基于jQuery实现表单提交验证
2014/11/24 Javascript
jquery中表单 多选框的一种巧妙写法
2015/09/06 Javascript
javascript模块化简单解析
2016/04/07 Javascript
jquery按回车键实现表单提交的简单实例
2016/05/25 Javascript
jQuery实现加入收藏夹功能(主流浏览器兼职)
2016/12/24 Javascript
jQuery实现CheckBox全选、全不选功能
2017/01/11 Javascript
js仿搜狐视频记录片列表展示效果
2020/05/30 Javascript
JS前端开发判断是否是手机端并跳转操作(小结)
2017/02/05 Javascript
bootstrap vue.js实现tab效果
2017/02/07 Javascript
Vue.js手风琴菜单组件开发实例
2017/05/16 Javascript
Bootstrap 模态对话框只加载一次 remote 数据的完美解决办法
2017/07/09 Javascript
vue-router2.0 组件之间传参及获取动态参数的方法
2017/11/10 Javascript
微信小程序时间轴实现方法示例
2019/01/14 Javascript
js实现多个倒计时并行 js拼团倒计时
2019/02/25 Javascript
微信小程序通过js实现瀑布流布局详解
2019/08/28 Javascript
three.js利用gpu选取物体并计算交点位置的方法示例
2019/11/25 Javascript
[03:24]2014DOTA2国际邀请赛 神秘商店生意火爆
2014/07/18 DOTA
[00:12]DAC2018 no[o]ne亮相SOLO赛 他是否如他的id一样无人可挡?
2018/04/06 DOTA
Python3.6 Schedule模块定时任务(实例讲解)
2017/11/09 Python
Tensorflow之构建自己的图片数据集TFrecords的方法
2018/02/07 Python
python内存动态分配过程详解
2019/07/15 Python
Pandas之read_csv()读取文件跳过报错行的解决
2020/04/21 Python
Python使用pycharm导入pymysql教程
2020/09/16 Python
纯css3实现思维导图样式示例
2018/11/01 HTML / CSS
24个canvas基础知识小结
2014/12/17 HTML / CSS
研究生毕业鉴定
2014/01/29 职场文书
期末自我鉴定
2014/02/02 职场文书
2016年大学生就业指导课心得体会
2015/10/09 职场文书
叶县这家生产军用电台的兵工厂,人称“四机部”,走出一上将
2022/02/18 无线电
WINDOWS 64位 下安装配置mysql8.0.25最详细的教程
2022/03/22 MySQL