持续输出js第4篇-(闭包作用及栈堆内存释放)

750 阅读7分钟

栈堆内存释放

我们就拿上一篇文章的最后那一题来说

例题1:

var n=10;
   function fn() {
      var n=20;
      function f() {
         n++;
         console.log(n);
    }
    f() ;
    return f;
 }
var x= fn();
x();
x();
console.log(n);

堆销毁

上面图中有2个堆5个栈,而且这两个堆都不能销毁,因为一个被fn占用,一个被f和x占用

如果要让它们销毁,很简单,让fn=null,f=null,x=null。

栈销毁

思考:全局作用域什么时候销毁?

页面在浏览器里面刷新(形成新的全局作用域)/关闭

一个函数执行的时候也形成了栈内存,它什么时候销毁??

而f和另外两个x的私有作用域(栈内存),一般情况下只要它们(函数)中代码一执行完就会销毁,因为它们的存在就是提供代码执行环境,代码执行完,就没它们什么事了,浏览器自动销毁它们

但是有一种情况,销毁不了

比如fn形成的栈内存销毁不了,因为aaafff222是在它里面开辟的堆,这个堆没有销毁,所以这个栈也不能销毁!!

详细解说:

例题2:

分析:

var f=fn(2) ;//=>先把FN执行(传递实参2),把FN执行的返回结果(RETURN后面
的值)赋值给F
f() ;//=>把返回的结果执行
fn(2) () ;//=>和上面两步骤类似,都是先把FN执行,把FN执行的返回结果再次执行

临时不销毁

从上面我们看到了一个新的情况,那就是,临时不销毁,也就是当我们执行到

在这里虽然fn执行的返回结果没有赋给f,没有被外部变量占用,但是它的结果(小函数)还是要执行,所以要开辟堆,暂时占用fn私有作用域,等小函数执行完了就可以一块销毁(堆和栈)。

return ## 对return右边的东西是不做提升的,相当于值,我们不对值提升,只对变量提升

一定记住一个东西就是,你跑上级作用域找了变量,修改了之后,上级作用域那个变量也要修改!

假如一个a函数执行返回一个b函数,那如果这个函数执行100次,也就会返回100次b函数,而这100个b函数都不一样,每次都返回一个全新的b函数(地址不同)

两种闭包函数

[闭包]
=>函数执形成-一个私有的作用域,保护里面的私有变量不受外界的干扰,
这种保护机制称之,为“闭包”

=>市面上的开发者认为的闭包是:形成-一个不销毁的私有作用域(私有栈
内存)才是闭包

柯理化函数

把返回的函数赋给f

惰性函数

同理,把返回的函数赋给utils

他们的原理都是形成一个不销毁的私有作用域(私有栈内存)

闭包作用和项目实战应用(面试必问)

其实项目中要尽量少使用闭包

//==>真实项目中为了保证Js的性能(堆栈内存的性能优化),应该尽可能的减
少闭包的使用(不销毁的堆栈内存是耗性能的)

//1.闭包具有保护作用:保护私有变量不受外界的干扰
//2.闭包具有保存作用:形成不销毁的栈内存,把一些值保存下来,方便后面的
调取使用

应用无非分为下面两大类

闭包作用之保护

在真实项目中,尤其是团队协作开发的时候,应当尽可能的减少全局变量的 使用,以防止相互之前的冲突(“全局变量污染”),那么此时我们完全可以把自己这一部分内容封装到一个闭包中, 让全局变量转换为私有变量

假如你已经写完了才发现,也没关系,你可以自定义一个函数,把所有代码包进去,闭包,就像下面这样。

不仅如此,我们封装类库插件的时候, 也会把自己的程序都存放到闭包中 保护起来,防止和用户的程序冲突,但是我们又需要暴露一些方法给客户使 用,这样我们如何处理呢?

1.JQ这种方式:把需要暴露的方法拋到全局,通过window.XX

  1. Zepto这种方式: 基于RETURN把需要共外面使用的方法暴露出去,通过return:{XX:}

闭包作用之保存

传统的es5规范当中,只有全局作用域和函数执行形成的私有作用域,判断和循环不会产生作用域

同步编程,事情只能一件一件做

异步编程,这件事没有彻底完成,不再等待,继续执行下面的任务

所有的事件绑定都是异步编程 (同步编程: 一件事 一件事的做,当前这件事没完成,下一个任务不能处理/异步编程:当前这件事件没有彻底完成,不在等待,继续执行下面的任务),绑定事件后,不需要等待执行,继续执行下一个循环任务

举个例子:(tablist长度默认是3)(下图末尾少了个扩折号)

当我们发生点击事件执行方法的时候,循环早已结束(让全局的i等于循环最后的结果3)

那怎么办呢,这样点击事件就无法正确完成。

解决方法1:自定义属性

在这里我们新加了一个myIndex属性(自定义)

该方法涉及this,后几篇补充

解决方法2:闭包

把函数用另一个函数包起来,这样里面的变量的上级作用域就指的的包它的函数,不是window。(仅限本例)

总结:形成了**不销毁(保存)**的3个私有作用域

循环三次,形成三个不销毁的私有作用域(自执行函数执行),而每一个不销毁的栈内存中都存储了一个私有变量I,

而这个值分别是每一次执行传递进来的全局I的值(也就是:第一个不销毁的作用域存储的是0,第二个是1,第三个是2);

当点击的时候,执行返回的小函数,遇到变量I,向它自己的上级作用域查找,找到的I值分别是: 0/1/2, 达到了我们想要的效果

但是这么写非常消耗性能,开发中一般不这样做

另一种写法: 解析在图里!

解决方法3:基于ES6解决(用let)

基于Es6中的LET来创建变量,是存在块级作用域的(类似于私有作用域)

作用域: (栈内存)

1.全局作用域

2.私有作用域(函数执行)

3.块级作用域(一般用大括号包起来的都是快级作用域,前提是ES6语法规范)

4.注意var obj={};//对象的大括号不是块级作用域

ES6中的块级作用域

  • 循环体
  • 判断体
  • 普通括号型

举个例子(普通括号型)

{
   let a=12;
   console.log(a) ;//=>12
}
console.log(a) ;//=>Uncaught ReferenceError: a is not defined

为什么报错呢?因为a是上面的块级作用域私有的,我们用不了它

再来个例子(循环体)

形成了五个块级作用域(每次循环形成一个),每个块级作用域中都有一个私有变量i,这5个i互不干扰!变量值就是每一次循环I的值

看看下面这个例子,ES6也翻车了

为啥报错呢,ES6虽然没有变量提升,but代码执行前有词法解析,在执行前就知道这个块里有哪些变量,它知道有a,但是你就是从上到下执行,规则如此还是会报错滴~~

补充一个小知识点:

itar:回车速成下面的结构666

结束啦,明天继续