关于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 相关文章推荐
网页常用特效代码整理
Jun 23 Javascript
js获取键盘按键响应事件(兼容各浏览器)
May 16 Javascript
uploadify在Firefox下丢失session问题的解决方法
Aug 07 Javascript
js string 转 int 注意的问题小结
Aug 15 Javascript
jQuery中map()方法用法实例
Jan 06 Javascript
jquery实现select下拉框美化特效代码分享
Aug 18 Javascript
jquery validate demo 基础
Oct 29 Javascript
理解javascript对象继承
Apr 17 Javascript
Vue-cli 使用json server在本地模拟请求数据的示例代码
Nov 02 Javascript
axios post提交formdata的实例
Mar 16 Javascript
微信小程序 调用远程接口 给全局数组赋值代码实例
Aug 13 Javascript
详解js location.href和window.open的几种用法和区别
Dec 02 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 压缩文件夹的类代码
2009/11/05 PHP
php中的观察者模式
2010/03/24 PHP
编译PHP报错configure error Cannot find libmysqlclient under usr的解决方法
2014/06/27 PHP
微信支付开发订单查询实例
2016/07/12 PHP
php preg_match的匹配不同国家语言实例
2016/12/29 PHP
jQuery 全选效果实现代码
2009/03/23 Javascript
javascript实现瀑布流自适应遇到的问题及解决方案
2015/01/28 Javascript
JS基于myFocus库实现各种功能的tab选项卡切换效果
2015/09/19 Javascript
基于jQuery实现仿QQ空间送礼物功能代码
2016/05/24 Javascript
浅谈JavaScript前端开发的MVC结构与MVVM结构
2016/06/03 Javascript
微信小程序 UI布局常用技巧整理总结
2016/12/05 Javascript
JavaScript基于Dom操作实现查找、修改HTML元素的内容及属性的方法
2017/01/20 Javascript
jQuery Mobile漏洞会有跨站脚本攻击风险
2017/02/12 Javascript
vue.js简单配置axios的方法详解
2017/12/13 Javascript
浅谈webpack4 图片处理汇总
2018/09/12 Javascript
详解Vue中的基本语法和常用指令
2019/07/23 Javascript
layui table 复选框跳页后再回来保持原来选中的状态示例
2019/10/26 Javascript
基于vue的tab-list类目切换商品列表组件的示例代码
2020/02/14 Javascript
解决echarts 一条柱状图显示两个值,类似进度条的问题
2020/07/20 Javascript
解决vuecli3中img src 的引入问题
2020/08/04 Javascript
python设置windows桌面壁纸的实现代码
2013/01/28 Python
Python实现简单的获取图片爬虫功能示例
2017/07/12 Python
TensorFlow的权值更新方法
2018/06/14 Python
Python简单基础小程序的实例代码
2019/04/28 Python
Python多线程多进程实例对比解析
2020/03/12 Python
对pytorch中x = x.view(x.size(0), -1) 的理解说明
2021/03/03 Python
HTML5学习笔记之History API
2015/02/26 HTML / CSS
Skyscanner阿联酋:全球领先的旅游搜索平台
2017/11/25 全球购物
英国天然抗衰老护肤品品牌:Nakin Skin Care
2019/04/16 全球购物
娇韵诗俄罗斯官方网站:Clarins俄罗斯
2020/10/03 全球购物
Tessabit美国:集世界奢侈品和设计师品牌的意大利精品买手店
2020/06/29 全球购物
优秀毕业生自荐信范文
2014/01/01 职场文书
积极分子思想汇报
2014/01/04 职场文书
2014县委书记党的群众路线教育实践活动对照检查材料思想汇报
2014/09/22 职场文书
Python实现仓库管理系统
2022/05/30 Python