闭包知多少

233 阅读4分钟

闭包懂?

之前自认为熟悉了闭包,结果发现自己也是半桶水,一顿找资料,综合之前的各种积累,今天就来认真梳理一下闭包的知识啦~

首先得懂作用域

当某个函数被调用的时候,会创建一个执行环境及相应的作用域链。然后,使用arguments和其他命名参数的值来初始化函数的活动对象。在作用域链中,外部函数的活动对象位于第二位,再外部位于第三位,直至作用域链终点的全局执行环境。

作用域链就是为了保证对执行环境能访问到的数据的有序访问。比如要找变量a,从最前端找,直至全局执行环境。

那什么是闭包?

闭包就是一个有权访问另一个函数作用域的函数,常见方法就是在一个函数里创建另一个函数。

for example:

function fun1() {
    var i = 4;
    function fun2() {
        console.log(i);
    }
    return fun2;
}
​
var temp = fun1();
temp();//生成闭包,结果为4

上面就是一个简单的闭包的栗子啦~

深入一步?

关于setTimeout与闭包的坑

for (var i = 1; i <= 5; i++) {
    setTimeout( function timer(){
        console.log(i);
    },i*1000);
}

以上这段代码的结果会是以每秒一次的频率输出五次6。

为什么显示都是6呢?

闭包对函数类型的值进行传递时,保留对它被声明位置所处的作用的引用

setTimeout函数里的闭包的执行,需要1秒,所以for循环在setTimeout函数将timer放入执行栈(这里说法可能不正规,意思就是timer函数执行之前)前,已经执行完5次循环了。而timer里的i保存的是外层for循环i的引用,所以只能得到最终for循环执行完的结果,i=5

如果我们想要得到每隔一秒输出递增的数列呢?

且看下面代码:

for (var i=1; i<=5; i++) {
    (function(i) {
        setTimeout( function timer() {
            console.log(i);
        }, i*1000 );
    })(j)
}

立即执行函数表达式创造了新的函数作用域将timer函数包裹了起来,并用j捕获了每次循环时的i

再深入一步?

闭包与模块

使用闭包可以很好的将模块的公有属性和方法暴露出来:

var moudel1 = (function () {
    var _count = 0;
    var m1 = function () {
        _count++;
    };
    var m2 = function () {
        _count--;
    }
    var m3 = function () {
        console.log(_count);
    }
    return {
        m1 : m1,
        m2 : m2,
        m3 : m3
    }
​
})();
moudel1.m1();
moudel1.m3();//1
moudel1.m2();
moudel1.m3();//0

万物总不是十全十美的

闭包的用处很多,但是它的缺点就是可能会导致内存泄露;

js是有自己自动垃圾回收机制的,而闭包

阻止了垃圾回收机制对其进行回收,无法释放内存

因此当我们不需要使用变量对象的时候,要记得手动将其释放哦~

function fun1() {
    var i = 4;
    function fun2() {
        console.log(i);
    }
    return fun2;
}
​
var temp = fun1();
temp();//生成闭包
temp = null;//释放对象

彩蛋

js内存的堆与栈

这个问题我一直想了解,刚好看到一篇博文,里面有一篇文章,里面有一段:

文章链接:juejin.cn/post/684490…

而JavaScript 是高级语言,底层依旧依靠 C/C++ 来编译实现,其变量划分为基本数据类型和引用类型。

基本数据类型包括:undefined、null、boolean、number、string;

这些类型在内存中分别占有固定大小的空间,他们的值保存在栈空间,通过按值访问、拷贝和比较。

引用类型包括:object、array、function、error、date;这些类型的值大小不固定,栈内存中存放地址指向堆内存中的对象,是按引用访问的,说白了就跟 C 语言的指针一样的道理。

对于引用类型变量,栈内存中存放的知识该对象的访问地址,在堆内存中为该值分配空间,由于这种值的大小不固定,因此不能把他们保存到栈内存中;但内存地址大小是固定的,因此可以将堆内存地址保存到栈内存中。这样,当查询引用类型的变量时,就先从栈中读取堆内存地址,然后再根据该地址取出对应的值。

很显而易见的一点就是,JavaScript 中所有引用类型创建实例时,都是显式或隐式地 new 出对应类型的实例,实际上就是对应 C 语言的 malloc 分配内存函数。

Last

欢迎大家关注公粽号:CSandCatti

日常推送英语精读,算法题,前端知识~