js-调用栈机制与ES6尾调用优化介绍

调用栈的英文名叫做Call Stack,大家或多或少是有听过的,但是对于js调用栈的工作方式以及如何在工作中利用这一特性,大部分人可能没有进行过更深入的研究,这块内容可以说对我们前端来说就是所谓的基础知识,咋一看好像用处并没有很大,但掌握好这个知识点,就可以让我们在以后可以走的更远,走的更快! 博客、前端积累文档、公众号、GitHub目录数据结构:栈调用栈是什么?用来做什么?调用栈的运行机制调用栈优化内存调用栈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}尾调用优化优化了什么? ...

May 21, 2019 · 1 min · jiezi

浅谈尾递归

要说尾递归先理解尾调用尾调用定义来自尾调用维基百科 在计算机学里,尾调用是指一个函数里的最后一个动作是返回一个函数的调用结果的情形,即最后一步新调用的返回值直接被当前函数的返回结果代码形式上表现为一个函数执行的最后是调用另一个函数//仅举例,没有使用特定语言的语法function f(x) { a(x); b(x); return g(x); //函数执行的最后调用另一个函数}核心理解就是看一个函数在调用另一个函数得时候,本身是否可以被“释放”判断:下列那种调用时尾调用// 情况一function f(x){ int a = g(x); return a;}// 情况二function f(x){ return 3+g(x);}// 情况三function f(x){ if (x > 0) { return g(x); } return r(x);}答案情况一是调用函数g(x)之后,还有别的操作,所以不属于尾调用,即使语义一样,因为要得到a得结果,需要等待g(x)函数,所以f(x)无法释放。情况二在调用后也有别的操作,所以不属于尾调用,同理f(x)也是无法释放,即使写在同一行。情况三中,不管x取什么值,最后一步操作都是函数调用,所以属于尾调用。尾调用有什么好处先看普通调用的过程//用如下三个函数举例function f(x){ res = g(x); return res+1;}function g(x){ res = r(x); return res + 1;}function r(x){ res = x + 1; return res + 1;}文字描述函数调用调用f(x),在内存形成一个调用记录,又称调用帧(call frame),保存调用位置和内部变量等信息。函数f(x)内调用函数g(x),那么在f(x)的调用帧上方会形成一个g(x)的调用帧函数g(x)内部还调用函数r(x),所以在g(x)的调用帧上方会形成一个r(x)的调用帧函数r(x)调用结束,将结果返回给g(x),同时函数r(x)结束并“消失”同理,g(x)调用结束并“消失”最后到f(x),结束并消失(图中没有体现)上述调用过程中,所有的调用帧会在一个调用栈(call stack)中上述过程维基百科中的描述 在程序运行时,计算机会为应用程序分配一定的内存空间;应用程序则会自行分配所获得的内存空间,其中一部分被用于记录程序中正在调用的各个函数的运行情况,这就是函数的调用栈。常规的函数调用总是会在调用栈最上层添加一个新的堆栈帧(stack frame,也翻译为“栈帧”或简称为“帧”),这个过程被称作“入栈”或“压栈”(意即把新的帧压在栈顶)。上述调用过程中有什么风险如下图,当函数的调用层数非常多时,调用栈会消耗不少内存,甚至会撑爆内存空间(栈溢出),造成程序严重卡顿或意外崩溃。尾调用解决上述风险//这是一个尾调用function f() { m = 1; n = 2; return g(m + n);}f();// 等同于function f() { return g(3);}f();// 等同于g(3);上述代码,我们可以看到,我们调用g之后,和f就没有任何关系了,函数f就结束了,所以执行到最后一步,完全可以删除 f() 的调用记录,只保留 g(3) 的调用记录,尾调用的意义如果所有函数都是尾调用,那么完全可以做到每次执行时,调用帧为一,这将大大节省内存什么是尾递归尾递归 = 尾调用 + 递归递归:函数调用自身,称为递归尾调用:函数最后是调用另一个函数所以,尾递归可以总结为:一个函数在其内部最后一步调用其自身#用python举例def tailrecsum(x, running_total=0): if x == 0: return running_total else: #tailrecsum函数得最后一步是调用另一个函数,其中这个“另一个函数”是其自身 return tailrecsum(x - 1, running_total + x) 参考尾调用尾调用优化Tail Calls, Default Arguments, and Excessive Recycling in ES-6什么是尾调用漫谈递归:从斐波那契开始了解尾递归 ...

February 14, 2019 · 1 min · jiezi