闭包closure以及闭包在实际开发中的使用

一、闭包closure

严格来说,闭包需要满足三个条件:【1】访问所在作用域;【2】函数嵌套;【3】在所在作用域外被调用
有些人觉得只满足条件1就可以,所以IIFE是闭包;有些人觉得满足条件1和2才可以,所以被嵌套的函数才是闭包;有些人觉得3个条件都满足才可以,所以在作用域以外的地方被调用的函数才是闭包

1、函数作用域链的副作用,引起闭包,闭包所保存的是整个变量对象

        function foo() {
            var arr = [];
            for (var i = 0; i < 5; i++) {
                arr[i] = function () {
                    console.log(i);
                }
            }
            return arr;
        }
        var arrFunc = foo();
        for (var j = 0; j < arrFunc.length; j++) {
            arrFunc[j]();//55555
        }

 打印结果:55555

分析:数组arr中的每一项都指向一个新的函数,每个函数的作用域链都指向foo函数,所以当arr数组循环打印的时候,其实arr每项的function中没有i值,需要往作用域链上找——>foo函数中的i值

根据上面图解,我们让arr每项函数,的上级函数作用域 指向不同的且独立的函数,这样解决闭包中共享i值的问题

        function foo(){
            var arr = [];
            for(var i = 0; i < 5; i++){
                arr[i] = function(j){
                    return function(){
                        console.log(j)
                    }
                }(i)
            }
            return arr;
        }
        var arr = foo();
        for(var j = 0; j < arr.length; j ++){
            arr[j]();
        }

打印结果:01234

 分析:每一次匿名的立即执行函数 执行的时候,都生成新的作用域链,新的执行环境;将i值立即传入给匿名函数,使得arr数组每项函数在作用域链上查找i值的时候,可以找到对应的i值

 立即执行函数也可以包裹在arr外:达到同样的目的

        function foo(){
            var arr = [];
            for(var i = 0; i < 5; i++){
                (function(j){
                    arr[i] = function(){
                        console.log(j)
                    }
                })(i)
            }
            return arr;
        }

2、立即执行函数——闭包

匿名函数的this指向window

        /**
        IIFE立即执行函数,闭包
        */
        var a = 1;
        function foo(){
            var a = 2;
            function bar(){
                var a = 3;
                (function(){
                    console.log(this);//window
                    console.log(a);//3
                })()
            }
            bar();
        }
        foo();

 打印结果:this是window;a是3;

当执行到立即执行函数内部的时候:可以看到闭包a 是 3。

3、setTimeout的函数this是window

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

打印结果:55555

分析:

setTimeout执行的环境是window,形成了闭包才能获取到foo函数中的i值

事件队列,setTimeout将函数添加到事件队列中,在合适的时间执行。

解决办法:

同样可以用立即执行函数,来给每个setTimeout的函数体的作用域链添加新的一级

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

打印结果:01234

 看看下面的例子:同样根据函数特性,每次执行,生成不同的作用域环境

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

打印结果:01234

4、非闭包,而是函数作用域

下面两个实例打印结果都是 55555

函数的作用域是window,而在打印值的时候,window对象中的i值已经是5了

        var arr = [];
        for (var i = 0; i < 5; i++) {
            arr[i] = function () {
                console.log(i);
            }
        }
        for (var j = 0; j < arr.length; j++) {
            arr[j]();//55555
        }
        for (var i = 0; i < 5; i++) {
            setTimeout(() => {
                console.log(i)
            }, 1000 * i)
        }

5、并非return才形成闭包,而是因为函数作用域链中,某个函数的引用被暴露到外面,使得可以获取到对应的函数作用域链。

比如:baz函数被引用到与foo函数同等位置的fn变量,也是闭包。

        var fn;
        function foo() {
            var a = 2;
            function baz() {
                console.log(a);
            }
            fn = baz;
        }
        foo();
        fn(); // 2

由此要对比是:函数中的原始值和引用值被引用到外部是不受影响的。 

二、实际开发中使用闭包

1、setTimeout、setInterval

当定时器中函数使用函数作用域链的变量时,其实就是闭包。

        function foo(){
            var a = 1,
                b = 2,
                c = 3;
            var obj = {
                name: "obj"
            }
            setTimeout(function(){
                console.log(a,b,c,obj.name);
            },200)
        }
        foo();

2、回调函数

        function foo() {
            var a = 1,
                b = 2,
                c = 3;
            var obj = {
                name: "obj"
            }

            function callback() {
                console.log(a, b, c, obj.name);
            }
            getData(callback);
        }
        foo();
        //作用域链 cb --> ajax的执行函数 --> getData
        function getData(cb) {
            var url = "https://www.easy-mock.com/mock/5b4c4032189fc57b63eb8410/example/getMovie";
            $.get(url, function (res) {
                if (res) {
                    console.log(res);
                    if(cb && typeof cb === "function"){
                        cb(); //回调函数执行位置
                    }
                    
                }
            }, "json")
        };

作用域链 callback --> foo

作用域链 cb --> ajax的执行函数 --> getData

将某个作用域链的函数传给另一个作用域链,且执行。也是闭包

3、jquery自定义组件

立即执行函数内,定义的对象,变量,构造函数,构造函数的原型,方法等,都可以通过一个闭包放在jquery对象上,供外部使用。

        (function () {
            function Dialog() {

            }
            Dialog.prototype.init = function () {

            }
            var methods = {
                get: function () {

                },
                set: function () {

                }
            }
            $.fn.pluginDialog = function (param, data) {
                if (param && typeof param === "object") {
                    var dialog = new Dialog(param);
                    $(this).data("dialog", dialog);
                } else if (methods[param]) {
                    methods[param].call(this, data)
                }
            }
        })(jQuery)

三、知乎精选:

从闭包说起的面试题

猜你喜欢

转载自blog.csdn.net/zyz00000000/article/details/106643925