闭包closure

标签: JavaScript

严格来说,闭包需要满足三个条件:【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

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

知乎精选:

从闭包说起的面试题

版权声明:本文为zyz00000000原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/zyz00000000/article/details/106643925

智能推荐

springMVC拦截器

一、     SpringMVC拦截器实现原理 用户请求到DispatherServlet中,DispatherServlet调用HandlerMapping查找Handler,HandlerMapping返回一个拦截器链(HandlerExecutionChain),springmvc中的拦截器是通过HandlerMapping发起的。 &nbs...

Unity Json反序列化

Json反序列化 结果:...

[机器学习-回归算法]Sklearn之线性回归实战

Sklearn之线性回归实战 一,前言 二,热身例子 三,贸易公司的简单例子 四,Sklearn 官网里的一个例子 参考资料 一,前言 一元线性回归的理论片请看我这个链接 二,热身例子 预测直线 y=1x1+2x2+3y = 1x_1 + 2x_2 +3y=1x1​+2x2​+3 导入LinearRegression 从Sklearn.liear_model 包里 拟合数据也可以说是训练 检验正确...

Android 开发者,你真的懂 Context 吗?

Android Context 详解 前言 一、Context是什么 二、Context结构 1、ContextImpl类介绍 2、ContextWrapper类介绍 3、ContextThemeWrapper 三、Context的数量 四、Context注意事项 五、如何正确回复以上面试题? 前言 Context 相信所有的 Android 开发人员基本上每天都在接触,因为它太常见了。但是这并不...

SpringMVC ----Json的简单交互处理

SpringMVC--Json Json的介绍 什么是JSON? JSON 和 JavaScript 对象互转 Controller返回JSON数据 Jackson 乱码 乱码的解决方法一 代码优化 乱码统一解决方法 返回json字符串统一解决 测试多个对象的集合输出 输出时间对象 抽取为工具类 FastJson fastjson 三个主要的类: JSONObject JSONArray JSON...

猜你喜欢

微信小程序自定义组件简单实现

本文将教你如何实现一个自定义的toast提示框,实现后的基本效果图如下: 小程序中一个自定义组件由 json wxml wxss js 4个文件组成的。下面我们一步一步地来创建文件及完成其中的配置: step1:创建自定义组件 首先创建一个components文件夹,用于放置所有自定义的组件,创建之后的目录结构为 其中的toastedit是我们本次...

PyTorch学习(四)--用PyTorch实现线性回归

教程视频:https://www.bilibili.com/video/BV1tE411s7QT 废话不多说,代码如下: 结果: 0 56.52023696899414 1 25.170454025268555 2 11.214292526245117 3 5.001270771026611 4 2.2352840900421143 5 1.0038176774978638 6 0.4554775...

1、Qt 的窗口组件和窗口类型

1、窗口组件 图形用户界面由不同的窗口和窗口组件组成 组件的类型 — 容器类(父组件):用于包含其它的界面组件 — 功能类(子组件):用于实现特定的交互功能 Qt 中没有父组件的顶级组件叫做窗口 QWidget QWidget 继承于 QObject 和 QPaintDevice — QObject 是所有支持 Qt 对象模型的基类 — QPaint...

从APP跳转到微信指定联系人聊天页面功能的实现与采坑之旅

从APP跳转到微信指定联系人聊天页面功能的实现与采坑之旅 起因 实现逻辑 效果图 实现过程 跳转微信按钮点击事件 无障碍监听主要方法 一些必要的参数 监听主要方法 遇到的坑 1. 搜索内容无法赋值给搜索框 2. 如何停止监听? 3. 没查询到结果如何停止监听? 4. 如果在微信其他页面怎么办? 5. 页面改变UI加载太慢 6. 聊天界面和主页面是同一个活动 7. 搜索不到结果时,发现他在搜索结果页...

6.ROI与泛洪填充

学习视频可参见python+opencv3.3视频教学 基础入门 ROI与泛洪填充 1.ROI ROI(region of interest),感兴趣区域 对lena图进行脸部的获取,代码如下 注意转化转化后的灰度图是单通道的,所以要转成三通道进行合并 结果如下 2.泛洪填充 简而言之,就是把你想要填充的区域填充成你想要的颜色 Iimage:输入图像,可以是一通道或者是三通道。 seedPoint...