全面理解JavaScript中的闭包
作者:LuckyJing 发布时间:2024-04-17 10:09:24
引子
闭包是有权访问另一个函数作用域中的变量的函数。
闭包是javascript中很难理解的部分,很多高级的应用都依靠闭包来实现的,我们先来看下面的一个例子:
function outer() {
var i = 100;
function inner() {
console.log(i);
}
}
上面代码,根据变量的作用域,函数outer中所有的局部变量,对函数inner都是可见的;函数inner中的局部变量,在函数inner外是不可见的,所以在函数inner外是无法读取函数inner的局部变量的。
既然函数inner可以读取函数outer的局部变量,那么只要将inner作为返会值,就可以直接在ouer外部读取inner的局部变量。
function outer() {
var i = 100;
function inner() {
console.log(i);
}
return inner;
}
var rs = outer();
rs();
这个函数有两个特点:
函数inner嵌套在函数ouer内部;
函数outer返回函数inner。
这样执行完var rs = outer()后,实际rs指向了函数inner。这段代码其实就是一个闭包。也就是说当函数outer内的函数inner被函数outer外的一个变量引用的时候,就创建了一个闭包。
作用域
简单的说,作用域就是变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期。在JavaScript中,变量的作用域有全局作用域和局部作用域两种。
全局作用域
var num1 = 1;
function fun1 (){
num2 = 2;
}
以上三个对象num1,num2和fun1均是全局作用域,这里要注意的是末定义直接赋值的变量自动声明为拥有全局作用域;
局部作用域
function wrap(){
var obj = "我被wrap包裹起来了,wrap外部无法直接访问到我";
function innerFun(){
//外部无法访问我
}
}
作用域链
Javascript中一切皆对象,这些对象有一个[[Scope]]属性,该属性包含了函数被创建的作用域中对象的集合,这个集合被称为函数的作用域链(Scope Chain),它决定了哪些数据能被函数访问。
function add(a,b){
return a+b;
}
当函数创建的时候,它的[[scope]]属性自动添加好全局作用域
var sum = add(3,4);
当函数调用的时候,会创建一个称为运行期上下文(execution context)的内部对象,z这个对象定义了函数执行时的环境。它也有自己的作用域链,用于标识符解析,而它的作用域链初始化为当前运行函数的[[Scope]]所包含的对象。
在函数执行过程中,每遇到一个变量,都会经历一次标识符解析过程以决定从哪里获取和存储数据。该过程从作用域链头部,也就是从活动对象开始搜索,查找同名的标识符,如果找到了就使用这个标识符对应的变量,如果没找到继续搜索作用域链中的下一个对象,如果搜索完所有对象(最后一个为全局对象)都未找到,则认为该标识符未定义。
闭包
闭包简单来说就是一个函数访问了它的外部变量。
var quo = function(status){
return {
getStatus: function(){
return status;
}
}
}
status保存在quo中,它返回了一个对象,这个对象里的方法getStatus引用了这个status变量,即getStatus函数访问它的外部变量status;
var newValue = quo('string');//返回了一个匿名对象,被newValue引用着
newValue.getStatus();//访问到了quo的内部变量status
假如并没有getStatus这个方法,那么quo('sting')结束后,status自动被回收,正是因为返回的匿名对象被一个全局对象引用,那么这个匿名对象又依赖于status,所以会阻止status的释放。
例子:
//错误方案
var test = function(nodes){
var i ;
for(i = 0;i<nodes.length;i++){
nodes[i].onclick = function(e){
alert(i);
}
}
}
匿名函数创建了一个闭包,那么其访问的i是外部test函数中的i,所以每一个节点实际上引用的是同一个i。
//改进方案
var test = function(nodes){
var i ;
for(i = 0;i<nodes.length;i++){
nodes[i].onclick = function(i){
return function(){
alert(i);
};
}(i);
}
}
每一个节点绑定了一个事件,这个事件接收一个参数,并且立即运行,传入i,因为是按值传递的,所以每一次循环都会为当前i产生一个新的备份。
闭包的作用
function outer() {
var i = 100;
function inner() {
console.log(i++);
}
return inner;
}
var rs = outer();
rs(); //100
rs(); //101
rs(); //102
上面的代码中,rs是闭包inner函数。rs共运行了三次,第一次100,第二次101,第三次102,这说明在函数outer中的局部变量i一直保存在内存中,并没有在调用自动清除。
闭包的作用就是在outer执行完毕并返回后,闭包使javascript的垃圾回收机制(grabage collection)不会回收outer所占的内存,因为outer的内部函数inner的执行要依赖outer中的变量。(另一种解释:outer是inner的父函数,inner被赋给了一个全局变量,导致inner会一直在内存中,而inner的存在依赖于outer,因些outer也始终于在内存中,不会在调用结束后被垃圾收集回收)。
闭包有权访问函数内部的所有变量。
当函数返回一个闭包时,这个函数的作用域将会一直在内存中保存到闭包不存在为止。
闭包与变量
由于作用域链的机制,闭包只能取得包含函数中任何变量的最后一个值。看下面例子:
function f() {
var rs = [];
for (var i=0; i <10; i++) {
rs[i] = function() {
return i;
};
}
return rs;
}
var fn = f();
for (var i = 0; i < fn.length; i++) {
console.log('函数fn[' + i + ']()返回值:' + fn[i]());
}
函数会返回一个数组,表面上看,似乎每个函数都应该返回自己的索引值,实际上,每个函数都返回10,这是因为第个函数的作用域链上都保存着函数f的活动对象,它们引用的都是同一变量i。当函数f返回后,变量i的值为10,此时每个函数都保存着变量i的同一个变量对象。我们可以通过创建另一个匿名函数来强制让闭包的行为符合预期。
function f() {
var rs = [];
for (var i=0; i <10; i++) {
rs[i] = function(num) {
return function() {
return num;
};
}(i);
}
return rs;
}
var fn = f();
for (var i = 0; i < fn.length; i++) {
console.log('函数fn[' + i + ']()返回值:' + fn[i]());
}
这个版本中,我们没有直接将闭包赋值给数组,而是定义了一个匿名函数,并将立即执行匿名函数的结果赋值给数组。这里匿名函数有一个参数num,在调用每个函数时,我们传入变量i,由于参数是按值传递的,所以就会将变量i复制给参数num。而在这个匿名函数内部,又创建了并返回了一个访问num的闭包,这样,rs数组中每个函数都有自己num变量的一个副本,因此就可以返回不同的数值了。
闭包中的this对象
var name = 'Jack';
var o = {
name : 'bingdian',
getName : function() {
return function() {
return this.name;
};
}
}
console.log(o.getName()()); //Jack
var name = 'Jack';
var o = {
name : 'bingdian',
getName : function() {
var self = this;
return function() {
return self.name;
};
}
}
console.log(o.getName()()); //bingdian
内存泄露
function assignHandler() {
var el = document.getElementById('demo');
el.onclick = function() {
console.log(el.id);
}
}
assignHandler();
以上代码创建了作为el元素事件处理程序的闭包,而这个闭包又创建了一个循环引用,只要匿名函数存在,el的引用数至少为1,因些它所占用的内存就永完不会被回收。
function assignHandler() {
var el = document.getElementById('demo');
var id = el.id;
el.onclick = function() {
console.log(id);
}
el = null;
}
assignHandler();
把变量el设置null能够解除DOM对象的引用,确保正常回收其占用内存。
模仿块级作用域
任何一对花括号({和})中的语句集都属于一个块,在这之中定义的所有变量在代码块外都是不可见的,我们称之为块级作用域。
(function(){
//块级作用域
})();
闭包的应用
保护函数内的变量安全。如前面的例子,函数outer中i只有函数inner才能访问,而无法通过其他途径访问到,因此保护了i的安全性。
在内存中维持一个变量。如前面的例子,由于闭包,函数outer中i的一直存在于内存中,因此每次执行rs(),都会给i加1。
猜你喜欢
- 一、使用jdbc连接数据库,插入数据库时,数据里的数据显示乱码,为 " ??? "两种解决方案:1、修改服务端的mysq
- 一、函数解释1.Softmax函数常用的用法是指定参数dim就可以:(1)dim=0:对每一列的所有元素进行softmax运算,并使得每一列
- 这篇文章主要介绍了windows环境中利用celery实现简单任务队列过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定
- 遇到一个问题,需要正则匹配远端FTP目录下的文件,如果使用ftp客户端可以通过命令行很容易的做到这一点,但是暂时没有一个工具支持这样的需求,
- 首先,我们要先看看微信远程控制电脑的原理是什么呢? 我们可以利用Python的标准库控制本机电脑,然后要实现远程的话,我们可以把电子邮件作为
- 首先你要确保你机器上面安装了python,其次,你还要确保你上面安装了Django。接下来,才能进入到搭建第一个Django应用程序很简单的
- 在服务器上生成动态内容是使用ASP最主要的原因之一,所以我们选择的第一个测试项目是确定把动态内容发送到应答流使用什么方法最好。基本的选择有两
- 万恶的源泉:Fireboo的疑问(当然 lambda 本身写的就有问题):>>> filter( lambda x: x
- 本文实例讲述了python实现在控制台输入密码不显示的方法。分享给大家供大家参考。具体实现方法如下:import console;names
- 之前写了一个matlab的,越用越觉得麻烦,如果不同数据集要改类别数目,而且运行速度慢。所以重新写了一个Python的,直接读取xml文件夹
- 功能是从客户端向服务发送一个字符串, 服务器收到后将字符串重新发送给客户端,同时,在连接建立之后,服务器可以向客户端发送任意多的字符串客户端
- 方法一(常规):代码:count = int(input('输入数据个数:\n'))a = 1while a <= c
- 这篇文章主要介绍了Python如何在DataFrame增加数值,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,
- 1、元旦之前受赵晨之邀作为讨论嘉宾参加了ACM组织的“人与信息社会巡讲”。2、去之前赵晨发给了我大致的讨论提纲。咣当了好几下~说实话,我是硬
- 前言上一篇文章Go 实现 WebSockets和什么是 WebSockets我们先介绍了什么是 WebSock
- 前言matplotlib 是Python最著名的绘图库,它提供了一整套和matlab相似的命令API,十分适合交互式地进行制图。本文将以例子
- js汉字简繁转换源代码:<html> <head> <title>汉字简繁转换工具_asp之家</
- SQL Server如何通过SQL语句直接操作另一个SQL SERVER的数据1、 现在执行SQL语句的数据库服务器开启Ad Hoc Dis
- 这一段CSS代码相当简单,目的就是想用CSS来控制某段文字的显示与隐藏。起初我采用了下面的代码,令人不可思议的是,它们在我的IE6.0上居然
- 一、准备工作首先准备两张表用于演示:CREATE TABLE `student_info` ( `id` int NOT NUL