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 的最后一 ...
随机推荐
- 【ZZ】Visual C++ 6.0 精简安装版(支持VA、ICC 等等安装)
(2012-04-22 08:10:10) 标签: it 分类: 软件_Software Visual C++ 6.0 精简安装版(支持VA.ICC 等等安装) 2012-04-16 21:07 想找 ...
- datatable的使用
学习可参考:http://www.guoxk.com/node/jquery-datatables http://yuemeiqing2008-163-com.iteye.com/blog/20069 ...
- commons io上传文件
习惯了是用框架后,上传功能MVC框架基本都提供了.如struts2,springmvc! 可是假设项目中没有使用框架.而是单纯的使用jsp或servlet作为action,这时我们就能够使用commo ...
- SQL还原数据库后,数据库显示受限制用户解决方法
数据库->属性->选项
- 安装 jdk-8u121( jdk1.8 ) Eclipse jee neon java EE 4.6 并配置 中国科学技术大学 http://mirrors.ustc.edu.cn/eclipse/ 仓库源
官网太慢用百度网盘,打不开刷新几次试试 http://pan.baidu.com/s/1qYTUrGK#list/path=%2F 首先下载安装 jdk-8u121-windows-x64.exe ...
- 淘宝客网站SEO及赚钱与揭密
- machine learning for hacker记录(2) 数据分析
本章主要讲了对数据的一些基本探索,常见的six numbers,方差,均值等 > data.file <- file.path('data', '01_heights_weights_ge ...
- mac10.10 打造Python多版本虚拟环境
一.简介 1.在操作系统mac10.10(yosemite)中搭建Python多版本虚拟环境: 2.多版本虚拟环境包含三个含义: 在一个操作系统中安装多个版本的Python,不同版本可以随意切换,例如 ...
- Centos7利用kvm搭建Windows虚拟机
这几天玩了一下kvm虚拟化,真的很有意思,我把这几天踩的坑,还有收获,都记录下来,作为以后的复习和检查. 首先说一下我的基本逻辑,我有一台win7的笔记本,我的底层虚拟化是使用VMWare构建的Cen ...
- 一篇文章搞明白CORS跨域
面试问到数据交互的时候,经常会问跨域如何处理.大部分人都会回答JSONP,然后面试官紧接着就会问:"JSONP缺点是什么啊?"这个时候坑就来了,如果面试者说它支持GET方式,然后面 ...