我相信学过Javascript这门语言的程序员应该都对Closure这个概念有所了解,然而网上以及各种Javascript书籍里面对Closure这个概念的定义有各种说法。我本人觉得很多地方对Closure这个概念的定义都是片面的,目前看到的比较全面准确的定义应该是Wikipedia上面的定义了,但是Wikipedia上面的定义不是很好理解。

我通过网上查阅了些资料后结合Wikipedia的定义,下面给出我自己对Closure这个概念的理解。

要想正确理解闭包必须要先对一些概念有所了解:

非本地变量(non-local variables):

又叫自由变量(free variables),是一个既不在本地作用域也不在全局作用域里的变量,通常存在于嵌套或匿名函数的上下文对象里面。

第一类函数(First-Class Functions):

在编程语言里,如果函数也能够像其他数据类型一样被操纵如在运行时被构造、被赋值给一个变量、能够被当做参数传递或者被其他函数返回,那么这种类型的函数即为第一类函数;通常闭包出现在支持这种类型函数的语言中。如:

var foo = function() {
alert("Hello World!");
};
var bar = function(arg) {
return arg;
};
bar(foo)();

内部函数(Inner Functions):

内部函数也叫做嵌套函数,是一种被定义在另外一个函数体(也叫外部函数)里面的函数。每一次外部函数被调用的时候,就会创建一个内部函数的实例。如:

function outer(info){
  //print is inner function
  function print(msg){
   return "Error:"+msg;  
}
return print(info);
}

内部函数有一个非常重要的特性就是它可以隐式的访问外部函数的作用域,这意味着内部函数能够访问外部函数的参数、本地变量等。

词法环境(Lexical environment):

词法环境是一种特殊的内部对象,在某一代码块里面的所有本地函数、函数参数及本地变量都是该内部对象的属性。在浏览器里最顶层代码块里面的词法环境对象是window,而window对象有一个隐含的属性[[scope]]保存上下文作用域,对window来说[[scope]]应该为null,而对最顶层代码块里面定义的函数来说,当函数被创建时的词法环境里面的[[scope]]属性就是window对象。

如下代码所示:

//这个位置的词法环境是window对象
//在代码执行前window={f:function...,a:undefined,g:undefined} 注:这里省略了其他不相关的属性
var a = 5;
function f(arg){//创建f函数时添加隐藏属性f.[[scope]]=window
  //调用函数时该函数的词法环境对象为{arg:...}
alert("f:"+arg);  
}
var g = function(arg){
  alert("g:"+arg);
}
f();//每次调用时创建自己的词法环境,并通过[[scope]]属性形成作用域链

什么是闭包:

如果不了解词法环境这个概念,就很难理解Wikipedia上面对Closure实现的定义:

Closures are typically implemented with a special data structure that contains a pointer to the function code, plus a representation of the function's lexical environment。

翻译出来后就是:闭包就是一个对象(一种特殊的数据结构),它包含一个指向构成该闭包的函数的指针以及在闭包创建时外部函数的词法环境的引用。同时在闭包里面访问的外部函数词法环境里面属性也叫做非本地变量。

什么时候会创建闭包:

当在一个内部函数被暴露到定义它的外部函数的外面时,也就是说如果一个内部函数能够从定义它的函数的外面被访问时,这个时候闭包就被创建了(这里说创建是因为闭包也是一个对象),同时该闭包函数的[[scope]]属性有对外部函数词法环境的引用,所以在外部函数调用结束后,闭包依然能够访问外部函数的词法环境对象。

闭包大部分的时候都是通过函数调用返回一个内部函数的形式创建,那么下面的代码有产生闭包吗?

function Person(name) {
this._name = name;
this.getName = function() {
return this._name;
};
} var p = new Person();

什么时候使用闭包:

Javascript里面的闭包可以做许多有用的事情,如配置回调函数或者模仿Java语言中的private data等等。

Callback function:

window.addEventListener("load", function() {
var showMessage = getClosure("some message<br />");
window.setInterval(showMessage, 1000);
}); function getClosure(message) {
function showMessage() {
document.getElementById("message").innerHTML += message;
}
return showMessage;
}

Private data:

function Person(name) {
var _name = name;
this.getName = function() {
return _name;
};
}

什么时候不使用闭包或闭包的误用:

对于初学者来说最经典的就是Loop循环:

window.addEventListener("load", function() {
for (var i = 1; i < 4; i++) {
var button = document.getElementById("button" + i); button.addEventListener("click", function() {
alert("Clicked button " + i);
});
}
});

上面代码的错误非常明显,因为在循环里面的事件绑定回调函数都引用了相同的外部函数词法环境,当循环结束后变量i的值为4,所以当事件触发时,弹出来的肯定都是Clicked button 4这条消息了。

使用闭包同样要注意内存泄露问题,在闭包里面一不小心就会形成循环引用的问题如:

function setHandler() {
var elem = document.getElementById('id')
elem.onclick = function() {
...
}
}

在上面的代码,elem通过onclick引用一个函数,同时在函数里面通过[[scope]]引用到外部函数的词法环境。

如果在构造器里面有方法但没用使用到private data,那么最好将其移至prototype里面去,因为每次实例化该构造器的一个对象都会在this对象中创建相应的函数对象,如:

function Person(name) {
var _name = name;
this.getName = function() {
return _name;
};
this.sayHello = function() {
alert("Hello!");
};
} //这里sayHello并有使用private data,为了避免每次new Person都创建sayHello函数,那么可以将其移至prototype里面 function Person(name) {
var _name = name;
this.getName = function() {
return _name;
};
} Person.prototype.sayHello = function() {
alert("Hello!");
};

注意:如果内部函数使用的是new Function()那么它的[[scope]]则不会引用外部函数的词法环境,而是window对象。

window.a = 1
function getFunc() {
var a = 2
var func = new Function('', 'alert(a)')
return func
} getFunc()() // 1, from window

Javascript中Closure及其相关概念的更多相关文章

  1. 在Javascript中闭包(Closure)

    在Javascript中闭包(Closure) 什么是闭包 “官方”的解释是:所谓“闭包”,指的是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分. ...

  2. JavaScript中的闭包(closure)

    闭包的特性 1.函数嵌套函数 2.函数内部可以引用外部的参数和变量 3.参数和变量不会被垃圾回收机制回收  闭包的缺点就是常驻内存,会增大内存使用量,使用不当很容易造成内存泄露,主要用于私有的方法和变 ...

  3. javascript中的闭包closure详解

    目录 简介 函数中的函数 Closure闭包 使用闭包实现private方法 闭包的Scope Chain 闭包常见的问题 闭包性能的问题 总结 简介 闭包closure是javascript中一个非 ...

  4. 理解JavaScript中的“this”

    对于javascript的初学者来说,一般对“this”关键字都感到非常迷惑.本文的目的旨在让你全面的了解“this”,理解在每一个情景下如何使用“this”,希望通过本文,可以帮助同学们不在害怕“t ...

  5. JavaScript闭包(Closure)

    JavaScript闭包(Closure) 本文收集了多本书里对JavaScript闭包(Closure)的解释,或许会对理解闭包有一定帮助. <你不知道的JavsScript> Java ...

  6. JavaScript中的匿名函数及函数的闭包

    1.匿名函数 函数是JavaScript中最灵活的一种对象,这里只是讲解其匿名函数的用途.匿名函数:就是没有函数名的函数. 1.1 函数的定义,首先简单介绍一下函数的定义,大致可分为三种方式 第一种: ...

  7. JavaScript中的闭包和匿名函数

    JavaScript中的匿名函数及函数的闭包   1.匿名函数 2.闭包 3.举例 4.注意 1.匿名函数 函数是JavaScript中最灵活的一种对象,这里只是讲解其匿名函数的用途.匿名函数:就是没 ...

  8. JavaScript中Eval()函数的作用

    这一周感觉没什么写的,不过在研究dwz源码的时候有一个eval()的方法不是很了解,分享出来一起学习 -->首先来个最简单的理解 eval可以将字符串生成语句执行,和SQL的exec()类似. ...

  9. javascript中的闭包解析

    学习javaScript已经有一段时间了,在这段时间里,已经感受到了JavaScript的种种魅力,这是一门神奇的语言,同时也是一门正在逐步完善的语言,相信在大家的逐步修改中,这门语言会逐步的完善下去 ...

随机推荐

  1. Python学习(004)-字典{}

    特点: 无序状态 键唯一   不可变类型:字符串.整型.元组 可变类型:列表.字典   字典创建 第一种: dic1={','sex':'man'} print(dic1['name']) ----- ...

  2. 获取当前进程目录 GetCurrentDirectory() 及 获取当前运行模块路径名GetModuleFileName()

    GetCurrentDirectory 获得的是当前进程的活动目录(资源管理器决定的),可以用SetCurrentDirectory 修改的. 转自 http://m.blog.csdn.net/bl ...

  3. Windows下使用Nexus搭建pypi私服

    Nexus之前一直作为maven的私服而被大家所熟知,但是其实nexus可以做很多种仓库的私服,官网的说明就揭示了一切,真是又方便又强大的开源工具. 首先下载安装nexus,地址: https://w ...

  4. Maven私服Nexus详解

    maven的仓库只有两大类:1.本地仓库 2.远程仓库,在远程仓库中又分成了3种:2.1 中央仓库 2.2 私服 2.3 其它公共库. 私服是一种特殊的远程仓库,它是架设在局域网内的仓库服务,私服代理 ...

  5. struts2 MVC模式

    在学习struts2之前首先了解一下MVC模式. MVC是一个设计模式,它强制性的使应用程序的输入.处理和输出分开.使用MVC应用程序被分成三个核心部件:模型.视图.控制器.它们各自处理自己的任务.分 ...

  6. 1.1 Linux中的进程 --fork、孤儿进程、僵尸进程、文件共享分析

    操作系统经典的三态如下: 1.就绪态 2.等待(阻塞) 3.运行态 其转换状态如下图所示: 操作系统内核中会维护多个队列,将不同状态的进程加入到不同的队列中,其中撤销是进程运行结束后,由内核收回. 以 ...

  7. 常用sql语法初级

    博主在工作中,常常需要使用sql语句来进行查询,总结发现,灵活使用这几个要点,就可以应付大部分简单情况. 一.连接:根据两个或多个表中的列之间的关系,从这些表中查询数据. JOIN或INNER JOI ...

  8. Codeforces Round #462 (Div. 2) B-A Prosperous Lot

    B. A Prosperous Lot time limit per test 1 second memory limit per test 256 megabytes input standard ...

  9. HDU 4662 MU Puzzle 数论或者水题

    题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=4662 题目是问目标串能否由MI得到,我们可以逆向思维,目标串能否反过来处理得到MI,所以,首先排除M ...

  10. oracle 11g 建库 建表 增 删 改 查 约束

    一.建库 1.(点击左上角带绿色+号的按钮) 2.(进入这个界面,passowrd为密码.填写完后点击下面一排的Test按钮进行测试,无异常就点击Connect) 二.建表 1-1. create t ...