我相信学过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. 一个用vue-cli vue-router2.1 vue 2.1 vuex2.1 echarts统计 express 的 时间轴 记录每天活动

    界面还挺好看的... 可以记录每天的点点滴滴... 1.使用 express 作为服务器 2.fs 模块 fs.writeFileSync 随机写入模拟数据 3.vuex 包括 states 存储数据 ...

  2. ios 从网络上获取图片

    -(UIImage *) getImageFromURL:(NSString *)fileURL { NSLog(@"执行图片下载函数"); UIImage * result; N ...

  3. Python seek和tell

    f = open("胡辣汤", mode="r+", encoding="utf-8") f.seek(0,2) # 移动到末尾 conte ...

  4. git添加本地项目到git

    1.切换到项目所在文件夹下:git int 2.git add -A 3.git commit -m '11' 4.git remote add origin https://github.com/g ...

  5. webpack 添加 jquery 插件

    webpack.base.config.js 加入以下配置: , plugins: [ new webpack.ProvidePlugin({ jQuery: 'jquery', $: 'jquery ...

  6. 几个你所不知道的技巧助你写出更优雅的vue.js代码

    1. watch 与 computed 的巧妙结合 如上图,一个简单的列表页面. 你可能会这么做: created(){ this.fetchData() }, watch: { keyword(){ ...

  7. 玩转TypeScript(1) --定义简单的类

    相对于JavaScript来说,TypeScript增强了强制类型,同时添加了一系列的面向对象的特性,包含:静态类型(Static typing).类(Classes).接口(Interfaces). ...

  8. 20155316 2016-2017-2 《Java程序设计》第5周学习总结

    教材学习内容总结 这周总结 try catch语法 异常继承结构 throw finally AutoCloseable接口 Collection Map Lambda表达式 上周总结 三个关键 类与 ...

  9. .NET 中小心嵌套等待的 Task,它可能会耗尽你线程池的现有资源,出现类似死锁的情况

    一个简单的 Task 不会消耗多少时间,但如果你不合适地将 Task 转为同步等待,那么也可能很快耗尽线程池的所有资源,出现类似死锁的情况. 本文将以一个最简单的例子说明如何出现以及避免这样的问题. ...

  10. 使用npm init快速创建web 应用

    一般来说我们会有npm init -y 快速生成package.json 文件, 但是npm init 可以使用脚手架工具,生成项目,比较方便 参考 npm init 帮助命令 npm init [- ...