关于ES6尾调用优化

时间: 2020-09-07阅读: 126标签: 优化

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性能来说是一个胜利。


站长推荐

1.云服务推荐: 国内主流云服务商,各类云产品的最新活动,优惠券领取。地址:阿里云腾讯云华为云

2.广告联盟: 整理了目前主流的广告联盟平台,如果你有流量,可以作为参考选择适合你的平台点击进入

链接: http://www.fly63.com/article/detial/9649

关闭

网站打开速度优化_如何提高网页访问速度技巧方法总结

网站的加载速度不仅影响着用户体验,也会影响搜索引擎的排名,在百度推出“闪电算法”以来,将网站首屏打开速度被列入优化排名行列,作为前端开发的我们需要如果来优化网站的打开速度呢?下面就整理挖掘出很多细节上可以提升性能的东西分享给大家

JS 代码脏乱差?你需要知道这些优化技巧

JavaScript 是万众瞩目的力量。它是世界上最流行的编程语言。它容易理解,有丰富的学习资源,对初学者非常友好。JavaScript 有着庞大的资源库,对小公司和大企业都颇具吸引力。庞大的 JS 工具和库生态系统为开发者的生产力带来了福音

Webpack 打包后代码执行时机分析与优化

代码执行时机将决定着是否能够正常执行,当依赖文件没加载完成就开始执行、使用对应模块,那么将会导致执行异常。这在“ 存在资源加载失败时,加载重试影响原来文件的执行顺序 ”的场景下尤为常见。

客户端内H5页面的首屏性能优化

性能优化是前端开发中不可避免的一个话题。本文将记录一次客户端内H5页面首屏性能优化的项目。信息流App是当下最流行的产品之一,如今日头条等。我所在的团队也是在做这样一款信息流App

如何对 React 函数式组件进行优化?

本文只介绍函数式组件特有的性能优化方式,类组件和函数式组件都有的不介绍,比如 key 的使用。另外本文不详细的介绍 API 的使用,后面也许会写,其实想用好 hooks 还是蛮难的。

React 中 的 9 种优化技术

谷歌的数据表明,一个有 10 条数据 0.4 秒可以加载完的页面,在变成 30 条数据加载时间为 0.9 秒后,流量和广告收入减少了 20%。当谷歌地图的首页文件大小从 100kb 减少到 70~80kb 时,流量在第一周涨了 10%,接下来的三周涨了 25%

网络串流播放_HTML5如何优化视频文件以便在网络上更快地串流播放

无论你正在将 GIF 动图转换为 MP4 视频,还是手头已经有一大堆 MP4 视频,你都可以优化文件结构,以使得这些视频更快地加载和播放。通过重组 atoms 将 moov 放到文件开头,浏览器可以避免发送额外的 HTTP range request 请求来搜寻和定位 moovatom

优化网页加载速度的方法总汇

优化方案的核心就3点:减少请求数、减少资源大小、找最快的服务器。如果你是一个网站的产品经理,快去找你们的开发确认是否有做过类似的优化吧。网页的加载流程。优化图片资源的格式和大小、开启网络压缩、使用浏览器缓存、减少重定向请求

移动 H5 首屏加速、优化方案

随着移动设备性能不断增强,web 页面的性能体验逐渐变得可以接受,又因为 web 开发模式的诸多好处(跨平台,动态更新,减体积,无限扩展)

提升网站页面速度的14条最佳实践

《高性能网站建设指南》一书共列举了14条提升网站速度的方法。减少http请求;使用内容发布网络;添加Expires头;压缩组件;将样式表放在顶部

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!