Javascript中Closure及其相关概念
我相信学过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及其相关概念的更多相关文章
- 在Javascript中闭包(Closure)
在Javascript中闭包(Closure) 什么是闭包 “官方”的解释是:所谓“闭包”,指的是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分. ...
- JavaScript中的闭包(closure)
闭包的特性 1.函数嵌套函数 2.函数内部可以引用外部的参数和变量 3.参数和变量不会被垃圾回收机制回收 闭包的缺点就是常驻内存,会增大内存使用量,使用不当很容易造成内存泄露,主要用于私有的方法和变 ...
- javascript中的闭包closure详解
目录 简介 函数中的函数 Closure闭包 使用闭包实现private方法 闭包的Scope Chain 闭包常见的问题 闭包性能的问题 总结 简介 闭包closure是javascript中一个非 ...
- 理解JavaScript中的“this”
对于javascript的初学者来说,一般对“this”关键字都感到非常迷惑.本文的目的旨在让你全面的了解“this”,理解在每一个情景下如何使用“this”,希望通过本文,可以帮助同学们不在害怕“t ...
- JavaScript闭包(Closure)
JavaScript闭包(Closure) 本文收集了多本书里对JavaScript闭包(Closure)的解释,或许会对理解闭包有一定帮助. <你不知道的JavsScript> Java ...
- JavaScript中的匿名函数及函数的闭包
1.匿名函数 函数是JavaScript中最灵活的一种对象,这里只是讲解其匿名函数的用途.匿名函数:就是没有函数名的函数. 1.1 函数的定义,首先简单介绍一下函数的定义,大致可分为三种方式 第一种: ...
- JavaScript中的闭包和匿名函数
JavaScript中的匿名函数及函数的闭包 1.匿名函数 2.闭包 3.举例 4.注意 1.匿名函数 函数是JavaScript中最灵活的一种对象,这里只是讲解其匿名函数的用途.匿名函数:就是没 ...
- JavaScript中Eval()函数的作用
这一周感觉没什么写的,不过在研究dwz源码的时候有一个eval()的方法不是很了解,分享出来一起学习 -->首先来个最简单的理解 eval可以将字符串生成语句执行,和SQL的exec()类似. ...
- javascript中的闭包解析
学习javaScript已经有一段时间了,在这段时间里,已经感受到了JavaScript的种种魅力,这是一门神奇的语言,同时也是一门正在逐步完善的语言,相信在大家的逐步修改中,这门语言会逐步的完善下去 ...
随机推荐
- WebGL编程指南案例解析之平移和旋转的矩阵实现
手写各种矩阵: //矩阵 var vShader = ` attribute vec4 a_Position; uniform mat4 u_xformMatrix; void main(){ gl_ ...
- Stack Overflow 2016年度 20个最佳Python问题(一)
Stack Overflow 2016年度 20个最佳Python问题(一) https://zhuanlan.zhihu.com/p/25020763
- OK335xS I2C device registe hacking
/*************************************************************************** * OK335xS I2C device re ...
- Navicat #1045 - Access denied for user 'root'@'localhost' (using password: NO)
Navicat #1045 - Access denied for user 'root'@'localhost' (using password: YES) 出现上述问题,原因在于本机还开了APMS ...
- ConfigUtil读取配置文件
package utils; import java.util.ResourceBundle; public class ConfigUtil { private static ResourceBun ...
- asp.net && javascript MD5加密
/* * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message * Digest Algorithm, as d ...
- GOOGLE高级搜索的秘籍
一.摘要 本文内容来源自互联网,全面的介绍Google搜索的各种功能和技巧. 二.GOOGLE简介 Google(http://www.google.com/)是一个搜索引擎,由两个斯坦福大学博士生L ...
- JVM 详解
概念 数据类型 Java 虚拟机中,数据类型可以分为两类:基本类型和引用类型.基本类型的变量保存原始值,即:他代表的值就是数值本身:而引用类型的变量保存引用值.“引用值”代表了某个对象的引用,而不是对 ...
- Linux部署禅道Steps&Q&A
1.查看Linux的位数: getconf LONG_BIT 结果:32/64 2. 禅道开源版安装包下载 Linux 64位 下载站点1: http://sourceforge.net/projec ...
- FastAdmin 开发第三天:认识目录
以下为标准 FastAdmin 安装后的目录 我们在运行命令时都是在这个目录. 我们所有的命令都在这个目录下面运行. 比如:安装前端组件,bower install 安装php 组件 composer ...