关于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的淡入淡出可自动切换的幻灯插件打包下载
Sep 15 Javascript
XMLHttpRequest处理xml格式的返回数据(示例代码)
Nov 21 Javascript
JQuery的Ajax请求实现局部刷新的简单实例
Feb 11 Javascript
javascript ajax的5种状态介绍
Aug 18 Javascript
JQuery创建DOM节点的方法
Jun 11 Javascript
基于jQuery实现文本框只能输入数字(小数、整数)
Jan 14 Javascript
jquery插件autocomplete用法示例
Jul 01 Javascript
Angular.js指令学习中一些重要属性的用法教程
May 24 Javascript
微信小程序自定义键盘 内部虚拟支付
Dec 20 Javascript
深入浅析nuxt.js基于ssh的vue通用框架
May 21 Javascript
JavaScript实现的滚动公告特效【基于jQuery】
Jul 10 jQuery
vant组件中 dialog的确认按钮的回调事件操作
Nov 04 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个人网站架设连环讲(二)
2006/10/09 PHP
PHP实现采集中国天气网未来7天天气
2014/10/15 PHP
CodeIgniter针对lighttpd服务器URL重写的方法
2015/06/10 PHP
thinkPHP+mysql+ajax实现的仿百度一下即时搜索效果详解
2019/07/15 PHP
php适配器模式简单应用示例
2019/10/23 PHP
Javascript操纵Cookie实现购物车程序
2007/02/15 Javascript
一个可以兼容IE FF的加为首页与加入收藏实现代码
2009/11/02 Javascript
JQuery Ajax通过Handler访问外部XML数据的代码
2010/06/01 Javascript
纯javascript实现图片延时加载方法
2015/08/21 Javascript
详解js中构造流程图的核心技术JsPlumb
2015/12/08 Javascript
Angularjs 创建可复用组件实例代码
2016/10/09 Javascript
用 js 的 selection range 操作选择区域内容和图片
2017/04/18 Javascript
基于BootStrap的前端分页带省略号和上下页效果
2017/05/18 Javascript
详解Angular5/Angular6项目如何添加热更新(HMR)功能
2018/10/10 Javascript
layui数据表格重载实现往后台传参
2019/11/15 Javascript
微信小程序 获取手机号 JavaScript解密示例代码详解
2020/05/14 Javascript
vue动态加载SVG文件并修改节点数据的操作代码
2020/08/17 Javascript
[01:37]DOTA2超级联赛专访ChuaN 传奇般的电竞之路
2013/06/19 DOTA
python解析xml文件实例分享
2013/12/04 Python
pytorch 实现模型不同层设置不同的学习率方式
2020/01/06 Python
python中的split、rsplit、splitlines用法说明
2020/10/23 Python
巴黎卡诗美国官方网站:始于1964年的头发头皮护理专家
2017/07/10 全球购物
通信工程专业个人找工作求职信范文
2013/09/21 职场文书
教育系毕业生中文求职信范文
2013/10/06 职场文书
历史学专业大学生找工作的自我评价
2013/10/16 职场文书
建筑个人求职信范文
2014/01/25 职场文书
中专毕业生个人职业生涯规划
2014/02/19 职场文书
英语自我介绍演讲稿
2014/09/01 职场文书
律师授权委托书范本
2014/10/07 职场文书
合同审查法律意见书
2015/06/04 职场文书
教学副校长工作总结
2015/08/13 职场文书
美德少年主要事迹材料
2015/11/04 职场文书
装修安全责任协议书
2016/03/22 职场文书
写一个Python脚本自动爬取Bilibili小视频
2021/04/24 Python
Vue详细的入门笔记
2021/05/10 Vue.js
基于Redis6.2.6版本部署Redis Cluster集群的问题
2022/04/01 Redis