关于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数组
May 11 Javascript
浅析Javascript使用include/require
Nov 13 Javascript
解析JavaScript中delete操作符不能删除的对象
Dec 03 Javascript
JavaScript将数据转换成整数的方法
Jan 04 Javascript
解决jquery版本冲突的有效方法
Sep 02 Javascript
解决vue2.x中数据渲染以及vuex缓存的问题
Jul 13 Javascript
Vue仿支付宝支付功能
May 25 Javascript
微信小程序实现基于三元运算验证手机号/姓名功能示例
Jan 19 Javascript
五分钟搞懂Vuex实用知识(小结)
Aug 12 Javascript
浅谈vue 组件中的setInterval方法和window的不同
Jul 30 Javascript
js实现盒子移动动画效果
Aug 09 Javascript
jQuery实现朋友圈查看图片
Sep 11 jQuery
在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 缓冲的免费实现方法
2006/10/09 PHP
如何在PHP中使用Oracle数据库(4)
2006/10/09 PHP
PHP新手上路(十一)
2006/10/09 PHP
隐性调用php程序的方法
2009/03/09 PHP
PhpStorm本地断点调试的方法步骤
2018/05/21 PHP
Javascript客户端脚本的设计和应用
2006/08/21 Javascript
js解析xml字符串和xml文档实现原理及代码(针对ie与火狐)
2013/02/02 Javascript
浅析JavaScript Array和string的转换(推荐)
2016/05/20 Javascript
requireJS模块化实现返回顶部功能的方法详解
2017/10/16 Javascript
vue.js过滤器+ajax实现事件监听及后台php数据交互实例
2018/05/22 Javascript
浅析Vue 生命周期
2018/06/21 Javascript
获取layer.open弹出层的返回值方法
2018/08/20 Javascript
为vue项目自动设置请求状态的配置方法
2019/06/09 Javascript
vue实现下拉加载其实没那么复杂
2019/08/13 Javascript
从Node.js事件触发器到Vue自定义事件的深入讲解
2020/06/26 Javascript
swiperjs实现导航与tab页的联动
2020/12/13 Javascript
vue 在单页面应用里使用二级套嵌路由
2020/12/19 Vue.js
[05:31]DOTA2英雄梦之声_第04期_光之守卫
2014/06/23 DOTA
[00:35]可解锁地面特效
2018/12/20 DOTA
零基础写python爬虫之HTTP异常处理
2014/11/05 Python
python删除服务器文件代码示例
2018/02/09 Python
学生信息管理系统python版
2018/10/17 Python
Django中的用户身份验证示例详解
2019/08/07 Python
Python异步编程之协程任务的调度操作实例分析
2020/02/01 Python
python随机模块random使用方法详解
2020/02/14 Python
python爬虫利用selenium实现自动翻页爬取某鱼数据的思路详解
2020/12/22 Python
伦敦新晋轻奢耳饰潮牌:Tada & Toy
2020/05/25 全球购物
大学毕业后的十年规划
2014/01/07 职场文书
婚礼新郎父母答谢词
2014/01/16 职场文书
教师党性分析材料
2014/02/04 职场文书
公司晚会主持词
2014/03/22 职场文书
项目合作协议书
2014/04/16 职场文书
社区国庆节活动总结
2015/03/23 职场文书
python实现黄金分割法的示例代码
2021/04/28 Python
nginx实现动静分离的方法示例
2021/11/07 Servers
java.util.NoSuchElementException原因及两种解决方法
2022/06/28 Java/Android