欢迎光临散文网 会员登陆 & 注册

JavaScript基础 理解闭包

2021-01-14 14:05 作者:刂C刂C刂  | 我要投稿

定义: 如果一个作用域可以访问另外一个函数的局部变量,此时就形成了闭包。而被访问变量的这个函数就是闭包。


理解闭包前,先看看自己是否理解以下几点概念:

1.首先明确: 闭包是函数!!!

2.js的两种数据类型:简单数据类型(number,布尔,null,string ,undefined);  复杂数据类型(对象,数组,函数)。


问:这两种数据类型如何区分出来的?

答: 其根本原因是在内存中的存储方式不同!


简单数据类型直接存储在中,比如: var i = 10; 10这个值存放在栈中,代码运行时直接就可以拿到。

复杂数据类型的值存储在中,栈中存放的是指向堆中数据的地址。

比如: var a = { n: 100};  代码运行时,在栈中实际存放的是 a = AABBBFFF123 的一串指向堆的地址,而真正的值 n: 100 ,是存放在堆中的。调用 对象a 时,实质上是依靠栈中存储的这段地址,去引用堆中的值。

值传递

3.js代码的垃圾回收及内存管理:

  1. 局部作用函数的局部变量,只在局部作用域可以访问。

  2. 全局作用域的变量,无论全局还是局部,都可以访问。

  3. 当期嵌套的作用域链的其他函数的变量和参数。(这就是闭包的实现原理)

    以上3种情况下,其存储变量和参数的内存空间都不回收。

举个例子: 

function fn() {

     var i = 10;

}

fn(); // 函数执行完毕,局部变量 i 占用的内存空间被回收

console.log(i);  // undefined


理解以上,下面开始正式理解闭包。看下面这段代码:

function a() {

    var i = 10;

    function b() {

          return i ;

    }

    return b();

}

var f = a();

console.log( f() ); // 输出1

先看结果: 上面的代码,我明明是在全局作用域下,却获取到了 局部作用域:函数a()  中定义的变量 i。为什么?

再来回头阅读一遍刚刚提到的js垃圾回收: 当期嵌套的作用域链的其他函数的变量和参数,不回收它的内存空间!!!


为了帮助理解:下面我们分析整段代码在内存中运行的过程:

代码执行过程

第一步: 变量提升,预定义函数。

第二步: f = a(); 

—>  a() 是函数,是复杂数据类型。

—>  所以我们实质上给f 赋的值是一串指向堆中的地址。

假设:函数a堆地址为: AABBCC133。

       函数b堆地址为:FFFFBBBXX。


第三步:console.log(f());

—> f(); 

—> 由于 f 等价于 执行地址为:AABBCC133 的a函数。

—> 所以 f() 等价于 a()();

—> a() 的返回值,return的是 b(),而实质上是return 了一串地址为:FFFFBBBXX 的堆地址。

—> 所以f() 实际上就是:立即执行堆地址为

FFFFBBBXX 的函数。


注意!!!!以下就是闭包的核心了!!!!!

看完了全局作用域中的执行,我们再看局部作用域:a() 

—> var i = 10;

—> 定义了一个堆地址为:FFFFBBBXX 的函数b。

—> return b();

—> 函数a执行结束。

—> 根据js的垃圾回收机制,一个局部作用域执行结束,就改释放它所占用的内存。

但是!!!由于 函数b()预定义时,使用了函数a() 定义的变量 i 。所以此时 函数a 虽然已经执行完成,return了,但是其占用的内存并没有被释放。

—> 接着我们在全局作用域下执行b函数。

—> b函数执行完成,b占用的内存被释放。在此时,a函数占用的内存才被释放。


应该非常详细了,如果还是不理解的话,可能需要仔细回顾js基础: 

1. 堆、栈的相关概念;

2. js的数据类型;

3. js的垃圾回收机制;

照着这个顺序来学习回顾,然后思考为什么需要闭包,闭包在内存中是如何实现的。

JavaScript基础 理解闭包的评论 (共 条)

分享到微博请遵守国家法律