JavaScript中的尾调用优化
文章来源自:http://www.zhufengpeixun.com/qianduanjishuziliao/javaScriptzhuanti/2017-08-08/768.html
JavaScript 中的尾调用优化(tail call optimization)
我在学习尾调用优化的过程中,有两个误解:
第一个是,我们一谈优化,经常说时间的优化。但是尾调用优化却主要是指空间的优化。
第二个是,既然尾调用优化是在 es6 中支持的,那么可能又要学新的语法了。然而,尾调用优化并不需要新的语法,而只是是在解释器(如V8)中做的改进。尾调用是一直存在的,但是尾调用优化是在支持 es6 的解释器里添加的。
澄清了这两个问题之后,我们先来看看尾调用是什么。
尾调用
尾调用,从定义上来看很简单,简而言之是函数里的最后一个动作是函数调用。
从上面了解到,尾调用优化是对空间的优化,是对解释器的改进,怎么改进的呢,看一个例子:
1.function c() {
2. throw new Error()
3. return 'return from c'
4.}
5.function b() {
6. const b = 'b'
7. return c()
8.}
9.function a() {
10. const a = 'a'
11. return b(a)
12.}
13.a()
我们有意在函数 c 中抛出一个错误。在控制台会输出类似下面的结果:
https://flyyang.github.io/images/chrome-stack-error.png
从图中可以看到一个类似于栈的结构, a 调用 b, b 调用 c,c 抛出错误。这样的一个结构我们称之为调用堆栈(call stack)。
还不够直观,对吗?我们以图的形式演示一下执行的过程。
由于 JavaScript 是一个单线程(除了webworker, child process 这种场景)的程序,它只有一个调用堆栈。以上面的代码为例,在执行a调用前,call stack 的情况是:
1.+--------------------+
2.| |
3.| |
4.+ +
5.| |
6.| |
7.+ +
8.| |
9.| |
10.+ +
11.| |
12.| |
13.+--------------------+
14.| a b c |
15.| main | -> stack frame
16.+--------------------+
我们将全局环境用一个 main 来表示,main 在执行前只知道 a, b, c 三个函数,存在于 call stack 中。
call stack 由 stack frame 组成。stack frame 存一些参数 本地变量,返回地址等。
执行 a(), a 入栈。在 a 的stack frame 中,执行初始化然后在其末尾调用函数 b。
文章来源自:http://www.zhufengpeixun.com/qianduanjishuziliao/javaScriptzhuanti/2017-08-08/768.html
这时候就有两个策略,一层一层的stack frame 网上堆。如下:
1.+--------------------+
2.| |
3.| |
4.+--------------------+
5.| |
6.| c |
7.+--------------------+
8.| |
9.| b |
10.+--------------------+
11.| |
12.| a |
13.+--------------------+
14.| a b c |
15.| main |
16.+--------------------+
但是由于是尾调用,a 的返回仅仅依赖 b 的调用。所以 a 的 stack frame 是没有必要保存的。那么尾调用优化后的结果是:
1.+--------------------+
2.| |
3.| |
4.+--------------------+
5.| |
6.| |
7.+--------------------+
8.| |
9.| |
10.+--------------------+
11.| |
12.| c |
13.+--------------------+
14.| a b c |
15.| main |
16.+--------------------+
空间复杂度从 O(n) 降到了 O(1)。
严格模式
尾递归优化只在严格模式下生效。也可以在 es6 module 中使用,因为 es6 module 默认是遵循严格模式的。
从https://kangax.github.io/compat-table/es6/
可以看到, 目前主流浏览器只有 safari 支持尾递归调用。根据我们上面的介绍。
测试一下两种不同的模式:
https://flyyang.github.io/images/safari-loose-error.jpg
https://flyyang.github.io/images/safari-strict-error.jpg
可以看到尾递归优化使得栈调用从O(n)变成了O(1)。
判断以及递归
如何判断是不是尾调用,有一套详细的判定规则。本文不再详述,可以参考下面的链接。
我们知道递归对堆栈要求特别高,调用层次过多的话,会导致栈溢出错误。所以尾调用优化对尾递归意义非常。
文章来源自:http://www.zhufengpeixun.com/qianduanjishuziliao/javaScriptzhuanti/2017-08-08/768.html
JavaScript中的尾调用优化的更多相关文章
- JavaScript 中的尾调用
尾调用(Tail Call) 尾调用是函数式编程里比较重要的一个概念,它的意思是在函数的执行过程中,如果最后一个动作是一个函数的调用,即这个调用的返回值被当前函数直接返回,则称为尾调用,如下所示: f ...
- 前端项目中常用es6知识总结 -- 箭头函数及this指向、尾调用优化
项目开发中一些常用的es6知识,主要是为以后分享小程序开发.node+koa项目开发以及vueSSR(vue服务端渲染)做个前置铺垫. 项目开发常用es6介绍 1.块级作用域 let const 2. ...
- ES6躬行记(15)——箭头函数和尾调用优化
一.箭头函数 箭头函数(Arrow Function)是ES6提供的一个很实用的新功能,与普通函数相比,不但在语法上更为简洁,而且在使用时也有更多注意点,下面列出了其中的三点: (1)由于不能作为构造 ...
- js 调用栈机制与ES6尾调用优化介绍
调用栈的英文名叫做Call Stack,大家或多或少是有听过的,但是对于js调用栈的工作方式以及如何在工作中利用这一特性,大部分人可能没有进行过更深入的研究,这块内容可以说对我们前端来说就是所谓的基础 ...
- javascript专题系列--尾调用和尾递归
最近在看<冴羽的博客>,讲真,确实受益匪浅,已经看了javascript 深入系列和专题系列的大部分文章,可是现在才想起来做笔记.所以虽然很多以前面试被问得一脸懵逼的问题都被“一语惊醒梦中 ...
- ES6学习笔记 -- 尾调用优化
什么是尾调用? 尾调用(Tail Call)是函数式编程的一个重要概念,就是指某个函数的最后一步是调用另一个函数. function f(x) { return g(x) } 如上,函数 f 的最后一 ...
- PHP、Lua中的 尾调用
在程序设计中,递归(Recursion)是一个很常见的概念,合理使用递归,可以提升代码的可读性,但同时也可能会带来一些问题. 下面以阶乘(Factorial)为例来说明一下递归的用法,实现语言是PHP ...
- JavaScript中函数的调用
JavaScript中函数的调用 制作人:全心全意 在JavaScript中,函数定义后并不会自动执行,要执行一个函数需要在特定的位置调用该函数,调用函数需要创建调用语句,调用语句包含函数名称和参数. ...
- iOS 的尾调用优化原理
背景: 今天聊代码规范的问题的时候说了一下尾调用的问题. 一:概念: 什么是尾调用? 尾调用(Tail Call):某个函数的最后一步仅仅只是调用了一个函数(可以是自身,可以是另一个函数). 注意 “ ...
随机推荐
- JAVA基础——方法笔记
java方法_学习笔记 由于我本人对java方法已经比较熟悉了,java方法的定义和使用也比较简单,这里只列举了基于我自身容易搞错的地方,希望对大家的学习有帮助!! 方法的参数可以是基本数据类型,如 ...
- Bootstrap提示信息(标签、徽章、巨幕和页头)
前面的话 在Bootstrap中,有一些组件用于提示信息,如 标签.徽章.巨幕和页头.本文将详细介绍Bootstrap提示信息 标签 在一些Web页面中常常会添加一个标签用来告诉用户一些额外的信息,比 ...
- 消耗CPU的程序
昨天领导交代客户需要一个可以测试CPU性能的脚本,问题简化下就是说要做一个可以手动设置对CPU产生消耗的程序.心想哪有这种脚本,或许性能测试工具还差不多.琢磨了下,或许用死循环可以达到差不多的效果,但 ...
- 取得system32文件夹下面文件的写入权限
取得system32文件夹下面文件的写入权限 TAKEOWN /F %SystemRoot%\system32\riched32.dll ICACLS %SystemRoot%\system32\ri ...
- springmvc(一) springmvc框架原理分析和简单入门程序
springmvc这个框架真的非常简单,感觉比struts2还更简单,好好沉淀下来学习~ --WH 一.什么是springmvc? 我们知道三层架构的思想,并且如果你知道ssh的话,就会更加透彻的理解 ...
- php之试触法----error--关键字的误用
实际开发中,在不同网页的输出中,常常有许多公共的代码或者变量需要使用,于是定义了以下类来缩减代码量 如下代码所示: <?php class universalClass { function w ...
- 标准IO: 文件的打开与关闭函数 fopen & fclose
(1) 流(stream)和文件(file) 流和文件 在Turbo C2.0中是有区别的, Turbo C2.0 为编程者和被访问的设备之间提供了一层抽象的东西, 称之为"流&quo ...
- 【Vue】详解Vue生命周期
Vue实例的生命周期全过程(图) (这里的红边圆角矩形内的都是对应的Vue实例的钩子函数) 在beforeCreate和created钩子函数间的生命周期 在beforeCreate和created之 ...
- 关于github 0.6.2版本的使用方法
貌似做为一名前端开发人员,没听过使用过github,node,vue就像落伍一样,本人也是在前端自摸自爬的路上越走越远了,经常在群里听大神们讨论vue,github,node,好生羡慕,没人教,没人带 ...
- TP3.2写提交的验证码验证
把今天掌握的东西整理一下,要不然,我就忘干净了: 今天在做一个企业网站的时候,有一个在线留言的功能,最后提交的时候需要输入验证码.如图下: 当然,特连接的并不是我的后台 好了,开始了,首先我需要把验证 ...