JavaScript 中的闭包-程序员宅基地

技术标签: ViewUI  前端  javascript  

1. 简要介绍

闭包可谓是js中的一大特色了,即使你对闭包没概念,你可能已经在不知不觉中使用到了闭包。闭包是什么,闭包就是一个函数可以访问到另一个函数的变量。这就是闭包,解释起来就这么一句话,不明白?我们来看一个简单的例子:

function getName(){
    var name='wenzi';
    setTimeout(function(){
        console.log(name);
    }, 500);
}
getName();

这就其实已经是闭包了,setTimeout中的function是一个匿名函数,这个匿名函数里的name是geName()作用域中的变量,匿名函数里只有一个输出语句:console.log();

还有一个很经典的例子也可以帮助我们理解什么是闭包:

function create(){
    var i=0;
    // 返回一个函数,暂且称之为函数A
    return function(){
        i++;
        console.log(i);
    }
}
var c = create(); // c是一个函数
c(); // 函数执行
c(); // 再次执行
c(); // 第三次执行

在上面的例子中,create()返回的是一个函数,我们暂且称之为函数A吧。在函数A中,有两条语句,一条是变量i自增(i++),一条是输出语句(console.log)。第一次执行执行c()时会产生什么样的结果?嗯,输出自增后的变量i,也就是输出1;那么第二次执行c()呢,对,会输出2;第三次执行c()时会输出3,依次累加。这个create()函数依然满足了我们在刚开始时的定义,函数A使用到了另一个函数create()中的变量i。

可是为什么会产生这样的输出呢,为什么i就能一直自增呢,create函数已经执行完并返回结果了呀,可是为什么还能接着使用i呢,而且i还能自增。这里就涉及到了三个比较重要的概念,讲解完这三个概念,我们对闭包就可以有一个比较好的理解了。

2. 三个重要概念

2.1 执行环境与变量对象

执行环境是JavaScript中一个重要的概念,它决定了变量或函数是否有权访问其他的数据,决定了它们各自的行为。每个执行环境都有一个与之对应的变量对象,执行环境中定义的所有变量和函数都保存在这个对象中。虽然我们的代码无法访问这个对象,但是解析器在处理数据时会在后台使用它。

我们用一个比较简单的比喻来形容这两个概念。执行环境就是一个人,变量对象就是这个人的身份证号,每个人都有其对应的身份证号。他这个人决定了他身上的很多属性和方法(动作),而且这个人的属性和方法都在他的身份证号上,当这个人消亡的时候,身份证号也就随之就注销了。

全局执行环境是最外层的一个执行环境。在web浏览器中,全局执行环境被认为是window对象,因为所有的全局变量和全局函数都是作为window对象的属性和方法创建的。某个执行环境中的所有代码执行完毕后,该环境被销毁,保存在其中的所有变量和函数定义也随之销毁(全局执行环境直到应用程序退出——例如关闭网页或者浏览器——时才会被销毁),被垃圾回收机制回收。

每个函数都有自己的执行环境。当执行流进入一个函数时,函数的环境就会被推入到一个环境栈中。而在函数执行后,栈将其环境弹出,把控制权返回给之前的执行环境。

2.2 作用域链

作用域链是当代码在一个环境中执行时创建的,作用域链的用途就是要保证执行环境中能有效有序的访问所有变量和函数。作用域链的最前端始终都是当前执行的代码所在环境的变量对象,下一个变量对象是来自其父亲环境,再下一个变量对象是其父亲的父亲环境,直到全局执行环境。

标识符解析是沿着作用域链一级一级地搜索标识符的过程。搜索过程始终从作用域链的前端开始,然后逐级地向后回溯,直到找到标识符为止(如果找不到标识符,通常会导致错误发生)。其实,通俗的理解就是:在本作用域内找不到变量或者函数,则在其父亲的作用域内寻找,再找不到则到父亲的父亲作用域内寻找,直到在全局的作用域内寻找!

2.3 垃圾回收机制

在js中有两种垃圾收集的方式:标记清除和引用计数。

标记清除:垃圾收集器在运行时会给存储在内存中的所有变量都加上标记(具体的标记方式暂时就不清楚了),待变量已不被使用或者引用,去掉该标记或添加另一种标记。最后,垃圾收集器完成内存清除工作,销毁那些已无法访问到的这些变量并回收他们所占用的空间。

引用计数:一般来说,引用计数的含义是跟踪记录每个值被引用的次数。当声明一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数便是1,如果同一个值又被赋给另一个变量,则该值的引用次数加1,相反,如果包含对这个值引用的变量又取得了另一个值,则这个值的引用次数减1。当这个值的引用次数为0时,说明没有办法访问到它了,因而可以将其占用的内存空间回收。

除了一些极老版本的IE,目前市面上的JS引擎基本采用标记清除来除了垃圾回收。但是需要注意的是IE中的DOM由于机制问题,是采用了引用计数的方式,所以会有循环引用的问题,造成内存泄露

var ele = document.getElementById(“element”);
var obj = new Object();
ele.obj = obj; // DOM元素ele的obj引用obj变量
obj.ele = ele; // obj变量的ele引用了DOM元素ele

这样就造成了循环引用的问题,导致垃圾回收机制回收不了ele和obj。不过,可以在不使用ele和obj时,对这两个变量进行 null 赋值,然后垃圾回收机制就会回收它们了。

3. 理解闭包

在第2部分讲解了三个重要的概念,这三个概念有助于我们更好的理解闭包。

我们再次拿出上面的这个例子:

function create(){
    var i=0;
    // 返回一个函数,暂且称之为函数A
    return function(){
        i++;
        console.log(i);
    }
}
var c = create(); // c是一个函数,即函数A
c(); // 函数执行
c(); // 再次执行
c(); // 第三次执行

从上面的“每个函数都有自己的执行环境”可以知道:create()函数是一个执行环境,函数A也是一个执行环境,且函数A的执行环境在create()的里面。这样就形成了一个作用域链:window->create->A。当执行c()时,函数A就会首先在当前执行环境中寻找变量i,可是没有找到,那么只能顺着作用域链向后找;OK,在create()的执行环境中找到了,那么就可以使用了变量i了。

可是我们还有一个疑问,按照上面的说法,函数create()执行完毕后,这个函数与里面的变量和方法应该被销毁了呀,可是为什么函数c()多次执行时依然能够输出变量i呢。这就是闭包的独特之处

函数create()执行完毕后,虽然它的作用域链会被销毁,即不再存在window->create这个链式关系,但是函数A()[c()]的作用域链还依然引用着create()的变量对象,还存在着window->create->A的链式关系,导致垃圾回收机制不能回收create()的变量对象,create()的变量对象仍然停留在内存中,直到函数A()[c()]被销毁后,create()的变量对象才会被销毁。

因此,虽然create()已经执行完毕了,但是create()的变量对象并没有被回收,还停留在内存中,依然可以使用。

从上面的讲解中我们可以看到,闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存。当页面中存在过多的闭包,或者闭包的嵌套很多很深时,会导致内存占用过多。因此,在这里建议:慎用闭包

4. 闭包与变量

有很多新手为DOM元素绑定事件时,通常会这么写:

function bindClick(){
    var li = document.getElementsByTagName('li'); // 假设一共有5个li标签
    for(var i=0; i<li.length; i++){
        li[i].onClick = function(){
            console.log('click the '+i+' li tag');
        }
    }
}

他的本意是想为每个li标签绑定一个单独的事件,点击第几个li标签,就能输出几。可是,最后的结果却是,点击哪个li标签输出的都是5,这是为什么呢?

其实这位程序员写的bindClick()已经构成了一个闭包,下面的这个函数有他的作用域,而变量i本不属于这个函数的作用域,而是属于bindClick()中的:

// 匿名函数
function(){
    console.log('click the '+i+' li tag');
}

因此,这就构成了一个含有闭包的作用域链:window->bindClick->匿名函数。可是这跟输出的i有关系么?有。作用域链中的每个变量对象保存的是对变量和方法的引用,而不是保存这个变量的某一个值。当执行到匿名函数时,bindClick()其实已经执行完毕了,变量i的值就是5,此时每个匿名函数都引用着同一个变量i。

不过我们稍微修改一下,以满足我们的预期:

/* 
    // 错误,onclick绑定的是立即执行函数的返回值,
    // 而此立即执行并没有返回值,也就是onclick = undefined
    function bindClick(){
    var li = document.getElementsByTagName('li');
    for(var i=0; i<li.length; i++){
        li[i].onclick = (function(j){
            console.log('click the '+j+' li tag');
        })(i);
    }
} */
// 更正,onclick 绑定的是 function(){ console.log('click the '+j+' li tag'); }
for(var i=0; i<li.length; i++){
    li[i].onclick = (function(j){
        return function(){
            console.log('click the '+j+' li tag');
        }
    })(i);
}

在这里,我们使用立即执行的匿名函数来保证传入的值就是当前正在操作的变量i,而不是循环完成后的值。

5. 闭包的应用场景

(1)在内存中维持一个变量。比如前面讲的小例子,由于闭包,函数create()中的变量i会一直存在于内存中,因此每次执行c(),都会给变量i加1.

(2)保护函数内的变量安全。还是那个小例子,函数create()中的变量c只有内部的函数才能访问,而无法通过其他途径访问到,因此保护了变量c的安全。

(3)实现面向对象中的对象。javascript并没有提供类这样的机制,但是我们可以通过闭包来模拟出类的机制,不同的对象实例拥有独立的成员和状态。

这里我们看一个例子:

function Student(){
    var name = 'wenzi';

    return {
        setName : function(na){
            name = na;
        },

        getName : function(){
            return name;
        }
    }
}
var stu = new Student();
console.log(stu.name); // undefined
console.log(stu.getName()); // wenzi

这就是一个用闭包实现的简单的类,里面的name属性是私有的,外部无法进行访问,只能通过setName和getName进行访问。

当然,闭包还存在另外一种形式:

var a = (function(){
    var num = 0;
    return function(){
        return num++;
    }
})()
a(); // 0
a(); // 1
a(); // 2

本人个人博客:http://www.xiabingbao.com

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

智能推荐

hive使用适用场景_大数据入门:Hive应用场景-程序员宅基地

文章浏览阅读5.8k次。在大数据的发展当中,大数据技术生态的组件,也在不断地拓展开来,而其中的Hive组件,作为Hadoop的数据仓库工具,可以实现对Hadoop集群当中的大规模数据进行相应的数据处理。今天我们的大数据入门分享,就主要来讲讲,Hive应用场景。关于Hive,首先需要明确的一点就是,Hive并非数据库,Hive所提供的数据存储、查询和分析功能,本质上来说,并非传统数据库所提供的存储、查询、分析功能。Hive..._hive应用场景

zblog采集-织梦全自动采集插件-织梦免费采集插件_zblog 网页采集插件-程序员宅基地

文章浏览阅读496次。Zblog是由Zblog开发团队开发的一款小巧而强大的基于Asp和PHP平台的开源程序,但是插件市场上的Zblog采集插件,没有一款能打的,要么就是没有SEO文章内容处理,要么就是功能单一。很少有适合SEO站长的Zblog采集。人们都知道Zblog采集接口都是对Zblog采集不熟悉的人做的,很多人采取模拟登陆的方法进行发布文章,也有很多人直接操作数据库发布文章,然而这些都或多或少的产生各种问题,发布速度慢、文章内容未经严格过滤,导致安全性问题、不能发Tag、不能自动创建分类等。但是使用Zblog采._zblog 网页采集插件

Flink学习四:提交Flink运行job_flink定时运行job-程序员宅基地

文章浏览阅读2.4k次,点赞2次,收藏2次。restUI页面提交1.1 添加上传jar包1.2 提交任务job1.3 查看提交的任务2. 命令行提交./flink-1.9.3/bin/flink run -c com.qu.wc.StreamWordCount -p 2 FlinkTutorial-1.0-SNAPSHOT.jar3. 命令行查看正在运行的job./flink-1.9.3/bin/flink list4. 命令行查看所有job./flink-1.9.3/bin/flink list --all._flink定时运行job

STM32-LED闪烁项目总结_嵌入式stm32闪烁led实验总结-程序员宅基地

文章浏览阅读1k次,点赞2次,收藏6次。这个项目是基于STM32的LED闪烁项目,主要目的是让学习者熟悉STM32的基本操作和编程方法。在这个项目中,我们将使用STM32作为控制器,通过对GPIO口的控制实现LED灯的闪烁。这个STM32 LED闪烁的项目是一个非常简单的入门项目,但它可以帮助学习者熟悉STM32的编程方法和GPIO口的使用。在这个项目中,我们通过对GPIO口的控制实现了LED灯的闪烁。LED闪烁是STM32入门课程的基础操作之一,它旨在教学生如何使用STM32开发板控制LED灯的闪烁。_嵌入式stm32闪烁led实验总结

Debezium安装部署和将服务托管到systemctl-程序员宅基地

文章浏览阅读63次。本文介绍了安装和部署Debezium的详细步骤,并演示了如何将Debezium服务托管到systemctl以进行方便的管理。本文将详细介绍如何安装和部署Debezium,并将其服务托管到systemctl。解压缩后,将得到一个名为"debezium"的目录,其中包含Debezium的二进制文件和其他必要的资源。注意替换"ExecStart"中的"/path/to/debezium"为实际的Debezium目录路径。接下来,需要下载Debezium的压缩包,并将其解压到所需的目录。

Android 控制屏幕唤醒常亮或熄灭_android实现拿起手机亮屏-程序员宅基地

文章浏览阅读4.4k次。需求:在诗词曲文项目中,诗词整篇朗读的时候,文章没有读完会因为屏幕熄灭停止朗读。要求:在文章没有朗读完毕之前屏幕常亮,读完以后屏幕常亮关闭;1.权限配置:设置电源管理的权限。

随便推点

目标检测简介-程序员宅基地

文章浏览阅读2.3k次。目标检测简介、评估标准、经典算法_目标检测

记SQL server安装后无法连接127.0.0.1解决方法_sqlserver 127 0 01 无法连接-程序员宅基地

文章浏览阅读6.3k次,点赞4次,收藏9次。实训时需要安装SQL server2008 R所以我上网上找了一个.exe 的安装包链接:https://pan.baidu.com/s/1_FkhB8XJy3Js_rFADhdtmA提取码:ztki注:解压后1.04G安装时Microsoft需下载.NET,更新安装后会自动安装如下:点击第一个傻瓜式安装,唯一注意的是在修改路径的时候如下不可修改:到安装实例的时候就可以修改啦数据..._sqlserver 127 0 01 无法连接

js 获取对象的所有key值,用来遍历_js 遍历对象的key-程序员宅基地

文章浏览阅读7.4k次。1. Object.keys(item); 获取到了key之后就可以遍历的时候直接使用这个进行遍历所有的key跟valuevar infoItem={ name:'xiaowu', age:'18',}//的出来的keys就是[name,age]var keys=Object.keys(infoItem);2. 通常用于以下实力中 <div *ngFor="let item of keys"> <div>{{item}}.._js 遍历对象的key

粒子群算法(PSO)求解路径规划_粒子群算法路径规划-程序员宅基地

文章浏览阅读2.2w次,点赞51次,收藏310次。粒子群算法求解路径规划路径规划问题描述    给定环境信息,如果该环境内有障碍物,寻求起始点到目标点的最短路径, 并且路径不能与障碍物相交,如图 1.1.1 所示。1.2 粒子群算法求解1.2.1 求解思路    粒子群优化算法(PSO),粒子群中的每一个粒子都代表一个问题的可能解, 通过粒子个体的简单行为,群体内的信息交互实现问题求解的智能性。    在路径规划中,我们将每一条路径规划为一个粒子,每个粒子群群有 n 个粒 子,即有 n 条路径,同时,每个粒子又有 m 个染色体,即中间过渡点的_粒子群算法路径规划

量化评价:稳健的业绩评价指标_rar 海龟-程序员宅基地

文章浏览阅读353次。所谓稳健的评估指标,是指在评估的过程中数据的轻微变化并不会显著的影响一个统计指标。而不稳健的评估指标则相反,在对交易系统进行回测时,参数值的轻微变化会带来不稳健指标的大幅变化。对于不稳健的评估指标,任何对数据有影响的因素都会对测试结果产生过大的影响,这很容易导致数据过拟合。_rar 海龟

IAP在ARM Cortex-M3微控制器实现原理_value line devices connectivity line devices-程序员宅基地

文章浏览阅读607次,点赞2次,收藏7次。–基于STM32F103ZET6的UART通讯实现一、什么是IAP,为什么要IAPIAP即为In Application Programming(在应用中编程),一般情况下,以STM32F10x系列芯片为主控制器的设备在出厂时就已经使用J-Link仿真器将应用代码烧录了,如果在设备使用过程中需要进行应用代码的更换、升级等操作的话,则可能需要将设备返回原厂并拆解出来再使用J-Link重新烧录代码,这就增加了很多不必要的麻烦。站在用户的角度来说,就是能让用户自己来更换设备里边的代码程序而厂家这边只需要提供给_value line devices connectivity line devices