JavaScript核心知识之闭包

传说中面试必考的闭包概念,我来啦~

一、闭包到底是个啥?

1、闭包,closure,是指函数本身和该函数声明时所处的环境状态的组合。

要理解这个加粗的字“该函数声明时”,我个人jio得闭包概念讲的最明白的是《JavaScript权威指南》最新版原书第七版,让我来引用一下。

JavaScript使用词法作用域。

这意味着函数执行时使用的是定义函数时生效的变量作用域,而不是调用函数时生效的变量作用域,为了实现词法作用域,JavaScript函数对象的内部状态不仅要包括函数代码,还要包括对函数定义所在作用域的引用。

这种函数对象与作用域(即一组变量绑定)组合起来解析函数变量的机制,在计算机科学文献中被称作闭包(closure)。

就是说函数能够“记忆”其定义时所处的环境。即使函数不在其定义的环境中被调用,也能访问定义时所处环境的变量。

2、来举个面试题的栗子理解一下

作用域应用的特殊情况,有两种表现:

函数作为参数被传递

函数作为返回值被返回

(1)函数作为参数被传递

        // 函数作为返回值
        function create() {
     
            const a = 100;
            return function () {
     
                console.log(a);
            }
        }

        const fn = create();
        const a = 200;
        fn();// 100

这里输出的就是100,而不是200

(2)函数作为返回值被返回

// 函数作为参数
function print(fn) {
     
    let b = 100;
    fn();
}
let b = 200;
function fn() {
     
    console.log(b);
}
print(fn);// 200

这里输出的是200,而不是100

所以,一定要理解刚才第一点说的,变量到底取什么值,要去函数定义的地方,向上级作用域查找,不是在执行的地方查找!!!

3、来观察一下刚才的闭包

我们在刚才这个小栗子里打个debugger看下

        // 函数作为返回值
        function create() {
     
            debugger
            const a = 100;
            return function () {
     
                console.log(a);
            }
        }

        const fn = create();
        const a = 200;
        fn();// 100

我们在浏览器里会发现跑着跑着就出现了闭包变量

JavaScript核心知识之闭包_第1张图片

你看它都有闭包变量了,它还不是闭包?那必须就是闭包。

4、其实所有JavaScript函数都是闭包

《JavaScript权威指南》

严格来说,所有JavaScript函数都是闭包。但由于多数函数调用与函数定义都在同一个作用域内,所以闭包的存在无关紧要。闭包真正值得关注的时候,是定义函数与调用函数的作用域不同的时候。最常见的一个情形就是一个函数返回了在它内部定义的嵌套函数。

5、闭包的其他理解

不会还有人不理解闭包吧~

闭包并不是JavaScript里面才有的概念,有很多定义,我都打出来看看

有一种定义是,函数嵌套函数时,内层函数引用了外层函数作用域下的变量,并且内层函数在全局环境下可访问,进而形成的。

红宝书里说的是

闭包指的是那些引用了另一个函数作用域中变量的函数,通常是在嵌套函数中实现的。

如果还不理解的话,可以再去MDN逛一下,反正我是已经懂了

MDN的闭包

二、闭包到底有啥用?

闭包是很有用的一个东东~

因为它允许我们将数据与操作该数据的函数关联起来。

1、记忆性

当闭包产生时,函数所处环境的状态会始终保持在内存中,不会在外层函数调用后被自动清除。这就是闭包的记忆性。

比如写一个体温判断的功能

        function createCheckTemp(standardTemp) {
     
            function checkTemp(n) {
     
                if(n <= standardTemp) {
     
                    console.log('体温正常哒');
                } else{
     
                    console.log('发烧了,快隔离!');
                }
            }
            return checkTemp;
        }

        // 这里会记住这个standardTemp是37.3,后续调用都以这个为标准温度对比
        let checkTemp_func = createCheckTemp(37.3);
        checkTemp_func(38.1);// 发烧了,快隔离!
        checkTemp_func(36.5);// 体温正常哒

2、模拟私有变量

就是说可以隐藏数据

比如写一个计数器

        const number = {
     }
        number.counter = 0;
        function add() {
     
            return number.counter++;
        }
        console.log(add());// 0
        console.log(add());// 1
        console.log(add());// 2

但是这样的话,number.counter就在全局作用域,就不私有了

但是用闭包的话

        let add2 = (function() {
     
            let counter = 0;
            return function() {
     
                return counter++;
            }
        })();
        console.log(add2());// 0
        console.log(add2());// 1
        console.log(add2());// 2

这样纸的话,就可以保证counter是私有的了

三、闭包的坏处

闭包不可以滥用,可能会有内存泄露的问题

因为一般函数执行后,上下文就被销毁了,但是闭包会保留它们包含函数的作用域,所以比其他函数更占内存。

不合理的引用有可能会造成内存泄露

四、面试题

1、问,这个会输出什么?

        var fn = null;
        const foo = () => {
     
            var a = 2;
            function innerFoo() {
     
                console.log(c);
                console.log(a);
            }
            fn = innerFoo;
        }
        const bar = () => {
     
            var c = 100;
            fn();
        }
        foo();
        bar();

哈哈,答案是会报错啦,Uncaught ReferenceError: c is not defined

因为这个c根本就不在innerFoo()函数的作用域上

那比如这样改一下,这样输出啥呢?

        var fn = null;
        const foo = (c) => {
     
            var a = 2;
            function innerFoo() {
     
                console.log(c);// undefined
                console.log(a);// 2
            }
            fn = innerFoo;
        }
        const bar = () => {
     
            var c = 100;
            fn(c);
        }
        foo();
        bar();

这样输出的c是undefined,a没啥,是2

这里说明箭头函数的形参是没有传给innerFoo里的c变量的

但是

        var fn = null;
        const foo = (c) => {
     
            var a = 2;
            function innerFoo() {
     
                console.log(arguments[0]);// 100
                console.log(c);// undefined
                console.log(a);// 2
            }
            fn = innerFoo;
        }
        const bar = () => {
     
            var c = 100;
            fn(c);
        }
        foo();
        bar();

如果在innerFoo函数里打印arguments[0],又能拿到100的值

如果真的要输出c的值,这样传一下就好了

        var fn = null;
        const foo = () => {
     
            var a = 2;
            function innerFoo(c) {
     
                console.log(c);// 100
                console.log(a);// 2
            }
            fn = innerFoo;
        }
        const bar = () => {
     
            var c = 100;
            fn(c);
        }
        foo();
        bar();

你可能感兴趣的:(JavaScript,javascript,闭包,函数闭包)