闭包 | 青训营笔记

91 阅读6分钟

这是我参与「第四届青训营 」笔记创作活动的的第3天

在昨天讨论高阶函数的时候,发现很多地方都用了闭包,在使用的时候感觉对这块知识点有点不清楚,于是又整理了一下相关的知识点。

环境和作用域

想要谈及闭包,我们先要知道环境和作用域的概念。

什么是执行环境

执行环境是在javascript代码马上执行之前创建,是动态的,javascript中有三种执行环境。

第一种为:全局执行环境,这是最外围的执行环境,一旦代码被载入,引擎最先进入的就是这个环境。在浏览器中,全局环境就 是window对象,所以所有全局属性和函数都是作为window对象的属性和方法创建。全局执行环境直到应用程序退出时才会被销毁。每个函数都有自己的执行环境,当执行流进入一个函数时,函数的环境就会被推入到一个环境栈中,执行完后,栈将其弹出,把控制权交给之前的执行环境。

let a = 100;
//比如,我们想这样直接定义一个变量,那么这个变量全局生效,直到程序结束,这个变量才被销毁。

第二种为:函数执行环境,当执行流执行一个函数时,JavaScript会创建一个新的函数执行环境,函数执行环境中的代码执行完后,该环境被销毁,保存在其中的所有变量和函数定义也随之被销毁。总结:函数执行环境在代码执行之前创建,在调用完成之后释放

function show(){
    let name = 'student';
    function showGender(){
        let gender = 'nan';
    }
    console.log(name)
}

show();

在这个函数里面,gender的执行环境是showGender这个函数,在外面打印gender,会得到undefined的结果。同理,name这个变量可以在show这个函数中打印(包括showGender)。

值得注意的是:在多次调用show这个函数的时候,分配的变量的地址是不同的,在每次调用结束后,程序会自动的销毁这些函数内部的变量。

活动对象在最开始只有一个变量,就是 arguments 对象,这个对象在全局环境中是不存在,只有在函数中。

第三种为:Eval执行环境。

用eval执行的语句应该和普通语句没有区别,其作用域就是当前的作用域。

js堆栈内存释放

  • 堆内存:存储引用类型值,对象类型就是键值对,函数就是代码字符串。

  • 堆内存释放:将引用类型的空间地址变量赋值成 null,或没有变量占用堆内存了浏览器就会释放掉这个地址

  • 栈内存:提供代码执行的环境和存储基本类型值。

  • 栈内存释放:一般当函数执行完后函数的私有作用域就会被释放掉。

    但栈内存的释放也有特殊情况:① 函数执行完,但是函数的私有作用域内有内容被栈外的变量还在使用的,栈内存就不能释放里面的基本值也就不会被释放。② 全局下的栈内存只有页面被关闭的时候才会被释放

函数的生命周期

为了更好的理解函数中变量的创建和销毁的过程,我们举几个例子来解释。

示例1:

let n = 0;
function add(){
    console.log(++n);
}
add();//1
add();//2
add();//3

示例2:

function add(){
    let n=1;
    function sum(){
        console.log(++n);
    }
    sum();
}
add();//2
add();//2
add();//2

示例3:

function add(){
    let n=1;
    return function sum(){
        console.log(++n);
    }
}

let a=add();
a();//2
a();//3
let b=add();
b();//2
b();//3

我们先来分析一下上面的三个例子

示例2每一次调用这个add这个函数,都重新分配内存,每一次的调用都是一个单独的操作,所以输出全为2.

示例3相当于给a赋值了一个名为sum的函数,而这个sum函数依赖于n这个变量,所以在掉用a这个函数时,原来的n并没有被销毁。至于函数b,则是重新找了一块内存,里面放着sum这个函数和n这个变量,相当于新的一次变量。

那么为什么举示例1这样的一个例子呢,其实,示例1中的add函数,也是依赖于上面那个全局变量,所以不断累加。或许有人想问,如果没有任何函数调用n这个变量,n是不是会销毁?其实不是,上面我们讲过,全局变量在环境结束的时候才会删除,至于为什么不会像示例3一样,我猜想是以为有全局函数和控制台也使用了n。

理解了上面的例子,我们在将难度升级一下。

示例4:

function add(){
    let n=1;
    return function sum(){
        //console.log(++n);
        let m=1;
        function show(){
            console.log(++m);
        }
    }
}

let a=add();
a();//2
a();//2

示例4中函数a相当于sum函数,sum里面没有调用sum外面需要的值,每一次调用a都分配了新的空间存m和show函数,所以和示例2相似,只不过示例2清除的是add,而事例4清除的是sum.

示例5:

function add(){
    let n=1;
    return function sum(){
        //console.log(++n);
        let m=1;
        return function show(){
            console.log(++m);
        }
    }
}

let a=add();
a();//2
a();//3

这里m就是show函数所需要的值,因此不会重新创建.

示例6:

function add(){
    let n=1;
    return function sum(){
        console.log(++n);
        let m=1;
        return function show(){
            console.log(++m);
        }
    }
}

let a=add();
a();//2,2
a();//3,3

通过上面的示例,这个例子显得格外简单。

构造函数的作用域

function add() {
    let n = 1;
    this.sum = function () {
        console.log(++n);
    }
}

let a = new add();
a.sum();//2
a.sum();//3

上面的构造函数的本质其实是:

function add() {
    let n = 1;
    sum = function () {
        console.log(++n);
    }
    return {
        sum:sum
    }
}

let a = new add();
a.sum();//2
a.sum();//3

这样看,其实与上面的函数的生命周期是一样的道理。

块级作用域

letconst 有块级作用域,但是 var 没有。举几个例子。 示例2:

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

示例2:

for(let i=0;i<3;i++){
   console.log(i);
}
console.log(i);
//0 1 2 undefine

示例3:

for(var i=0;i<3;i++){
    setTimeout(()=>{
        console.log(i);
    },1000)
}
//3 3 3

示例4:

for(let i=0;i<3;i++){
    setTimeout(()=>{
        console.log(i);
    },1000)
}
//0 1 2

闭包

什么是闭包

在 <<JavaScript高级程序>> 中说,闭包是指有权访问另外一个函数作用域中的变量的函数

形成闭包的原因

内部的函数存在外部作用域的引用就会导致闭包

var a = 0 
function foo(){
    var b =14
    function fo(){
        console.log(a, b) 
    } 
    fo() 
} 
foo()

这里的子函数 fo 内存就存在外部作用域的引用 a, b,所以这就会产生闭包

闭包使用的场景

下面这篇文章讲述了闭包的七种使用场景,我就不再赘述。 juejin.cn/post/693746…

这里举一个实际开的例子吧

let arr = [1,23,4,5,6,7,8,9,21,10];

function between(a,b){
    return function(v){
        return v>= a&& v<=b;
    };
}

console.log(arr.filter(between(3,9)));

参考文章

juejin.cn/post/693746…

第九章 这次把JS闭包给你讲得明明白白_哔哩哔哩_bilibili

结语

文章如果有不正确的地方,欢迎指正,共同学习,共同进步。

若有侵权,请联系作者删除。