js 调用栈机制与ES6尾调用优化介绍
调用栈的英文名叫做Call Stack,大家或多或少是有听过的,但是对于js调用栈的工作方式以及如何在工作中利用这一特性,大部分人可能没有进行过更深入的研究,这块内容可以说对我们前端来说就是所谓的基础知识,咋一看好像用处并没有很大,但掌握好这个知识点,就可以让我们在以后可以走的更远,走的更快!
目录
- 数据结构:栈
- 调用栈是什么?用来做什么?
- 调用栈的运行机制
- 调用栈优化内存
- 调用栈debug大法
数据结构:栈
栈是一种遵从后进先出(LIFO
)原则的有序集合,新元素都靠近栈顶,旧元素都接近栈底。
生活中的栗子,帮助一下理解:
餐厅里面堆放的盘子(栈),一开始放的都在下面(先进),后面放的都在上面(后进),洗盘子的时候先从上面开始洗(先出)。
调用栈是什么?用来做什么?
- 调用栈是一种栈结构的数据,它是由调用侦组成的。
- 调用栈记录了函数的执行顺序和函数内部变量等信息。
调用栈的运行机制
机制:
程序运行到一个函数,它就会将其添加到调用栈中,当从这个函数返回的时候,就会将这个函数从调用栈中删掉。
看一下例子帮助理解:
// 调用栈中的执行步骤用数字表示
printSquare(5); // 1 添加
function printSquare(x) {
var s = multiply(x, x); // 2 添加 => 3 运行完成,内部没有再调用其他函数,删掉
console.log(s); // 4 添加 => 5 删掉
// 运行完成 删掉printSquare
}
function multiply(x, y) {
return x * y;
}
调用栈中的执行步骤如下(删除multiply的步骤被省略了):
调用侦:
每个进入到调用栈中的函数,都会分配到一个单独的栈空间,称为“调用侦”。
在调用栈中每个“调用侦”都对应一个函数,最上方的调用帧称为“当前帧”,调用栈是由所有的调用侦形成的。
找到一张图片,调用侦:
调用栈优化内存
调用栈的内存消耗:
如上图,函数的变量等信息会被调用侦保存起来,所以调用侦中的变量不会被垃圾收集器回收。
当函数嵌套的层级比较深了,调用栈中的调用侦比较多的时候,这些信息对内存消耗是非常大的。
针对这种情况除了我们要尽量避免函数层级嵌套的比较深之外,ES6提供了“尾调用优化”来解决调用侦过多,引起的内存消耗过大的问题。
何谓尾调用:
尾调用指的是:函数的最后一步是调用另一个函数。
function f(x){
return g(x); // 最后一步调用另一个函数并且使用return
}
function f(x){
g(x); // 没有return 不算尾调用 因为不知道后面还有没有操作
// return undefined; // 隐式的return
}
尾调用优化优化了什么?
尾调用用来删除外层无用的调用侦,只保留内层函数的调用侦,来节省浏览器的内存。
下面这个例子调用栈中的调用侦一直只有一项,如果不使用尾调用的话会出现三个调用侦:
a() // 1 添加a到调用栈
function a(){
return b(); // 在调用栈中删除a 添加b
}
function b(){
return c() // 删除b 添加c
}
防止爆栈:
浏览器对调用栈都有大小限制,在ES6之前递归比较深的话,很容易出现“爆栈”问题(stack overflow)。
现在可以使用“尾调用优化”来写一个“尾递归”,只保存一个调用侦,来防止爆栈问题。
注意:
- 只有不再用到外层函数的内部变量,内层函数的调用帧才会取代外层函数的调用帧。
如果要使用外层函数的变量,可以通过参数的形式传到内层函数中
function a(){
var aa = 1;
let b = val => aa + val // 使用了外层函数的参数aa
return b(2) // 无法进行尾调用优化
}
- 尾调用优化只在严格模式下开启,非严格模式是无效的。
- 如果环境不支持“尾调用优化”,代码还可以正常运行,是无害的!
更多:
关于尾递归以及更多尾调用优化的内容,推荐查阅ES6入门-阮一峰
调用栈debug大法
查看调用栈有什么用
查看函数的调用顺序是否跟预期一致,比如不同判断调用不同函数。
快速定位问题/修改三方库的代码。
当接手一个历史项目,或者引用第三方库出现问题的时候,可以先查看对应API的调用栈,找到其中涉及的关键函数,针对性的修复它。
通过查看调用栈的形式,帮助我快速定位问题,修改三方库的源码。
如何查看调用栈
- 只查看调用栈:
console.trace
a()
function a() {
b();
}
function b() {
c()
}
function c() {
let aa = 1;
console.trace()
}
如图所示,点击右侧还能查看代码位置:
bugger
打断点形式,这也是我最喜欢的调试方式:
积跬步以至千里
平时需要有意识的去做这种小的优化(我现在就是),尽量写最佳实践的代码。
项目小的时候可能没什么影响,当一个项目体量大的时候,尤其是一些小方法拼接嵌套成一个大的API输出时,这时调用栈中对内存的消耗将是巨大的!这种优化也是不可小觑的,积跬步以至千里,诸君共勉!
结语
本文主要讲了这几个方面的内容:
- 理解调用栈的运行机制,对代码背后的一些执行机制也可以更加了解,帮助我们在百尺竿头更进一步。
- 我们应该在日常的code中,有意识的使用ES6的“尾调用优化”,来减少调用栈的长度,节省客户端内存。
- 利用调用栈,对第三方库或者不熟悉的项目,可以更快速的定位问题,提高我们debug速度。
最后:之前写过一篇关于垃圾回收机制与内存泄露的文章,感兴趣的同学可以扩展一下。
如果这篇文章帮助到了你,欢迎点赞和关注,你的支持是对我最大的鼓励!
以上2019/5/19
参考资料:
JavaScript 如何工作:对引擎、运行时、调用堆栈的概述
js 调用栈机制与ES6尾调用优化介绍的更多相关文章
- JS魔法堂:ES6新特性——GeneratorFunction介绍
一.前言 第一次看koajs的示例时,发现该语句 function *(next){...............} ,这是啥啊?于是搜索一下,原来这是就是ES6的新特性Generator ...
- JS的递归与TCO尾调用优化
转自:https://segmentfault.com/a/1190000004018047 这两天搜了下JS递归的相关文章, 觉得这篇文章很不错, 就顺手翻译了下,也算给自己做个笔记,题目是我自己加 ...
- JavaScript中的尾调用优化
文章来源自:http://www.zhufengpeixun.com/qianduanjishuziliao/javaScriptzhuanti/2017-08-08/768.html JavaScr ...
- JavaScript 中的尾调用
尾调用(Tail Call) 尾调用是函数式编程里比较重要的一个概念,它的意思是在函数的执行过程中,如果最后一个动作是一个函数的调用,即这个调用的返回值被当前函数直接返回,则称为尾调用,如下所示: f ...
- javascript专题系列--尾调用和尾递归
最近在看<冴羽的博客>,讲真,确实受益匪浅,已经看了javascript 深入系列和专题系列的大部分文章,可是现在才想起来做笔记.所以虽然很多以前面试被问得一脸懵逼的问题都被“一语惊醒梦中 ...
- Lua 正确的尾调用(proper tail call)
Lua支持“尾调用消除(tail-call elimination)”.尾调用(tail call):当一个函数调用是另一个函数的最后一个动作时,该调用才算是一条“尾调用”.例如,下面的代码就是一条“ ...
- 前端项目中常用es6知识总结 -- 箭头函数及this指向、尾调用优化
项目开发中一些常用的es6知识,主要是为以后分享小程序开发.node+koa项目开发以及vueSSR(vue服务端渲染)做个前置铺垫. 项目开发常用es6介绍 1.块级作用域 let const 2. ...
- ES6躬行记(15)——箭头函数和尾调用优化
一.箭头函数 箭头函数(Arrow Function)是ES6提供的一个很实用的新功能,与普通函数相比,不但在语法上更为简洁,而且在使用时也有更多注意点,下面列出了其中的三点: (1)由于不能作为构造 ...
- ES6学习笔记 -- 尾调用优化
什么是尾调用? 尾调用(Tail Call)是函数式编程的一个重要概念,就是指某个函数的最后一步是调用另一个函数. function f(x) { return g(x) } 如上,函数 f 的最后一 ...
随机推荐
- bvlc_reference_caffenet.caffemodel
#uncoding:utf-8 # set up Python environment: numpy for numerical routines, and matplotlib for plotti ...
- Struts2拦截器 解决登录问题
一.了解Struts2 拦截器[Interceptor] 拦截器的工作原理如图 拦截器是由每一个action请求(request)都包装在一系列的拦截器的内部,通过redirectAction再一次 ...
- 【BZOJ4950】lydsy七月月赛 C 二分图最大匹配
[BZOJ4950]lydsy七月月赛 C 题面 题解:比较直接的想法就是:每行,每列的最大值都留下,剩下的格子都变成1.但是如果一个格子既是行的最大值又是列的最大值,那么我们只需要把它留下即可.这就 ...
- GitLab Pages expect to run on their own virtual host
GitLab Pages administration | GitLab https://docs.gitlab.com/ce/administration/pages/
- java 图形化界面 布局管理器
package Layout; import java.awt.*; import javax.swing.*; public class MyBorderLayout extends JFrame{ ...
- CoreGraphics(转)
2.CoreGraphics 上面我们讲过,UIBezierPath是CoreGraphics的封装,使用它可以完成大部分的绘图操作,不过更底层的CoreGraphics更加强大. CoreGraph ...
- leetcode 748. Shortest Completing Word
Find the minimum length word from a given dictionary words, which has all the letters from the strin ...
- 【React系列】Props 验证
Props 验证使用 propTypes,它可以保证我们的应用组件被正确使用,React.PropTypes 提供很多验证器 (validator) 来验证传入数据是否有效.当向 props 传入无效 ...
- hdu1078 FatMouse and Cheese —— 记忆化搜索
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1078 代码1: #include<stdio.h>//hdu 1078 记忆化搜索 #in ...
- 使用eclipse的SVN连接码云
码云配置: 码云的项目上,启用SVN访问 eclipse的配置,不配置这个会报错