[JS基础回顾] 闭包 又双叒叕来~~~

语言: CN / TW / HK

highlight: arta theme: jzman


目录

  1. 闭包的解释
  2. 闭包暴露``函数作用域3种方式
  3. 关于闭包的内存泄露
  4. 循环和闭包

一 闭包的解释

闭包是基于词法作用域书写代码时所产生的自然结果,你甚至不需要为了利用它们而有意识地创建闭包

MDN的解释闭包是函数声明该函数的词法环境的组合。

Tips: 词法作用域词法环境 1,此时函数还没被执行,所以使用的是词法作用域即静态作用域.2, 此时函数被执行,此时词法作用域就会变成词法环境(包含静态作用域与动态作用域)

以上的解释 个人感觉还是不够清晰 我这样理解 1. 闭包就是突破了函数作用域 2. 闭包就是函数嵌套函数, 子函数可以访问父函数的变量(也就是所谓的自由变量), 所以,此变量不会被回收.

闭包暴露``函数作用域3种方式:

1) 通过外部函数的参数进行暴露

闭包内 调用外部函数 通过外部函数的参数 暴露 闭包内 自由变量.

js function fn() { var a = 2; function innerFn() { outerFn(a) //通过外部函数的参数进行暴露 } innerFn(); }; function outerFn(val) { console.log(val); // 2 } fn(); // 2

2) 通过外部作用域的变量进行暴露

其中val为全局变量

```js function fn() { var a = 1; function innerFn() { val = a; //通过外部作用域的变量进行暴露 } innerFn(); };

fn(); console.log(val); // 1

```

3) 通过return直接将整个函数进行暴露

```js function fn() { var a = 1; function innerFn() { console.log(a); } return innerFn; //通过return直接将整个函数进行暴露 };

let a = fn(); a(); // 1 ```

关于闭包的内存泄露

首先必须声明一点:使用闭包并不一定会造成内存泄露,只有使用闭包不当才可能会造成内存泄露.

为什么闭包可能会造成内存泄露呢?原因就是上面提到的,因为它一般会暴露自身的作用域给外部使用.如果使用不当,就可能导致该内存一直被占用,无法被JS的垃圾回收机制回收.就造成了内存泄露.

注意: 即使闭包里面什么都没有,闭包仍然会隐式地引用它所在作用域里的所用变量. 正因为这个隐藏的特点,闭包经常会发生不易发现的内存泄漏问题.

常见哪些情况使用闭包会造成内存泄露:

    1. 使用定时器未及时清除.因为计时器只有先停止才会被回收.所以决办法很简单,将定时器及时清除,并将造成内存的变量赋值为null(变成空指针)
    1. 相互循环引用.这是经常容易犯的错误,并且也不容易发现.
    1. 闭包引用到全局变量上.因为全局变量是只有当页面被关闭的时候才会被回收.

四 循环和闭包

1) 同步循环打印 正确的值

js for (var i=1; i<5; i++) { console.log( i ); } // 1 2 3 4

2) 同步中嵌套异步任务(中的宏任务)循环打印 错误的值

当执行 console 时, 循环已经完成, 同步任务执行完成后,执行宏任务,此时 i 已经是 5.所以打印5个5.

js for (var i=1; i<5; i++) { setTimeout( function timer() { console.log( i ); }, i*1000 ); } // 打印出 5 个 5

3) 创造5个独立的函数作用域,但是 i 也全都是对外部作用域的引用 错误的值

它的最终值仍然是5个5.为什么?我们来分析下,它用了一个匿名函数包裹了定时器,并立即执行.在进行for循环时,会创造5个独立的函数作用域(由匿名函数创建的,因为它是闭包函数).但是这5个独立的函数作用域里的i也全都是对外部作用域的引用.即它们访问的都是i的最终值5.这并不是我们想要的,我们要的是5个独立的作用域,并且每个作用域都保存一个"当时"i的值.

js for (var i=1; i<5; i++) { (function() { setTimeout( function timer() { console.log( i ); }, i*1000 ); })(); } // 打印出 5 个 5

4) 通过匿名函数创建独立的函数作用域,并且通过变量保存独立的 i 值

```js for (var i=1; i<5; i++) { (function () { var x=i; console.log(x1000); // 1000 2000 3000 4000 setTimeout( function timer() { console.log( x ); }, x1000 ); })(); }

// 1 2 3 4 ```

5) 通过匿名函数创建独立的函数作用域,并且通过参数保存独立的 i 值

```js for (var i=1; i<5; i++) { (function (x) { console.log(x1000); // 1000 2000 3000 4000 setTimeout( function timer() { console.log( x ); }, x1000 ); })(i); }

// 1 2 3 4 ```

参考

注意

  • 使用定时器未及时清除.因为计时器只有先停止才会被回收.所以决办法很简单,将定时器及时清除,并将造成内存的变量赋值为null(变成空指针)
  • 闭包引用到全局变量上.因为全局变量是只有当页面被关闭的时候才会被回收.
  • 闭包就是函数嵌套函数, 子函数可以访问父函数的变量(也就是所谓的自由变量), 所以,此变量不会被回收.
  • 另外,闭包被问到,一定要说到作用域 与 执行上下文, AO VO 对象.可参考我的另一文章, 【JS引擎】解析过程