JS高级程序设计(3rd)中对闭包的定义就是一句话,首先闭包是一个函数,怎样的函数呢?有权访问另一个函数作用域中的变量 的函数。而创建闭包的常见方式就是在一个函数的内部创建另一个函数,就是嵌套函数。
 
 
闭包会涉及到的点主要有
①     作用域链(这个原理让我们明白内部嵌套的函数是能够访问外部父函数里定义的变量的,而对于嵌套函数来说引用了非自己作用域内定义的这些变量通常又被称为自由变量)
 
②     函数执行的机制(这个又涉及到执行环境execution context ,环境栈(注意环境栈的底部永远是全局上下文global context),活动对象,变量对象等)。
 
先说执行环境相关的:
当函数执行时,函数的环境会被推入到环境栈的顶部,理论上函数执行完毕后会将其环境弹出pop,将控制权返回给之前的执行环境,但是由于有了闭包,情况又会有所不同。
 
再来说两个相关概念:活动对象(activation object)和变量对象(variable object)
变量对象:每个执行环境都会有自己的变量对象,里面存储着在该执行环境中定义的变量和函数;
如果这个执行环境是函数Function Context,那么则将其活动对象作为变量对象,活动对象中还包含了arguments(形参类数组)、formal parameters(形参的值),活动对象还包含了Argument 对象,该对象具有callee,length等属性
 
理解了以上两点,现在可以来说一下函数执行的整体流程了:
1、执行代码,全局执行环境
     创建global . variable object
2、全局变量的赋值 or 调用函数
     调用函数时,会得到当前函数的活动对象activation object,该活动对象中包含了该函数内部变量的声明,函数的声明,形参
3、进入所调用的函数的上下文
     进行该函数所在作用域上的变量的赋值及各种运算(此时的作用域包括全局的variable object和当前函数执行环境的activation object)
4、分为三种情况
     a、函数正常return或结束,该函数执行环境被弹出,回到step2继续执行其他代码;
     b、若在函数中有内部函数的调用,执行step 3;
     c、若函数返回了另一个函数,且该函数有对自由变量的引用,则形成闭包。此时作用域链机制仍然有效,当前的执行环境Function Context不会被弹出环境栈,函数的活动对象也留在了内存中,不会在调用结束后被垃圾回收机制回收。回到step2继续执行其他代码;
5、所有代码执行完毕,程序关闭,释放内存
 
③     垃圾回收机制
一般来说,一个函数在执行开始的时候,会给其中定义的变量划分内存空间保存,以便后面的语句所用,等到函数执行完毕返回了,这些变量就被认为是无用的了,对应的内存空间也就被回收了。下次再执行此函数的时候,所有变量又回到了最初状态,重新赋值使用。
 
但是如果一个函数parent内部又嵌套了另一个函数child,而这个child函数又是可能在外部被调用到的,并且这个内部嵌套函数child又使用了外部函数parent中的某些变量,这个时候就形成了闭包,此时的内存回收机制就会与前面一般情况有所不同。
 
在外部函数parent执行返回后 又直接调用了内部嵌套函数,如果按一般回收情况这时候parent已经执行完毕被回收了,那么内部嵌套函数就没法读取已经被回收的变量,所以在遇到闭包时,JS解析器实际上会将内部嵌套函数本身和父级和祖先级的变量(自由变量)一起保存起来,保存在该闭包中。并且这些变量不会被内存回收器回收。只有当这些内部函数不可能被调用之后(比如被删除了或者没有了指针)才会销毁这个闭包,同时那些不再被该闭包引用的变量才会在下一次内存回收启动时被回收。
 
由于闭包会使得函数中的变量一直都被保存在内存中,使得内存消耗很大,影响网页性能。所以我们有必要在函数执行完毕后对其进行手动销毁。
 

下面我们来用例子说明闭包的一些特性,闭包常见的有两种形式--函数作为返回值,函数作为参数传递
 
看个例子:

 function test() {
var num = 1;
return function() {
num++;
console.log(num)
}
}
var anotherTest = test();
anotherTest();//
anotherTest();//
以上可以看出闭包让其引用的变量一直存在在内存中(注意虽然活动对象没有被销毁,其包含的变量函数依然存在在内存中,但是却不能直接调用哦)
 
再来个例子:

 var result = [];
function test() {
var num = 0;
for (; num < 3; num++) {
result[num] = function() {
console.log(num);
}
}
}
test();
result[0](); //
result[1](); //
result[2](); //

上面这段代码中,本意是想让test中的变量 i 被内部匿名函数循环使用并依次输出索引0 1 2,但结果却与预想不同。为什么呢?因为闭包中记录的自由变量只是对自由变量的一个引用,也就说只能取得该变量最后状态保留的那个值。本例中执行完for循环后 i 变量最后的值是3,所以所有引用 i 值的结果都将是3。
 
关于自由变量的取值来看两个例子:
 
 var test = 10;
function fun() {
var test = 100;
return function foo(num) {
if (num < test) {
console.log(num + '<' + test);
}
}
}
var f1 = fun();
f1(15);
//15<100
可以看到这个例子中test的取值是100,这个100来自于创建foo函数的作用域fun中,fun也是foo函数的父级函数;那么自由变量到底是来自创建它的作用域还是其父级作用域呢?在看一个例子:

 var test = 10;
parameter = function (num) {
if (num > test) {
console.log(num+'>'+test);
}
};
(function (fun) {
var test = 100;
fun(15);
})(parameter);
//15>10
上面这个例子中,test的取值为10,而这个10是来自全局作用域的。这证明了自由变量实际上是在创建这个函数的作用域中取值而不是其父级作用域中取值。 

初步学习JS中的闭包的更多相关文章

  1. 浅谈JS中的闭包

    浅谈JS中的闭包 在介绍闭包之前,我先介绍点JS的基础知识,下面的基础知识会充分的帮助你理解闭包.那么接下来先看下变量的作用域. 变量的作用域 变量共有两种,一种为全局变量,一种为局部变量.那么全局变 ...

  2. JS中的闭包(closure)

    JS中的闭包(closure) 闭包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现.下面就是我的学习笔记,对于Javascript初学者应该是很有用 ...

  3. 详解js中的闭包

    前言 在js中,闭包是一个很重要又相当不容易完全理解的要点,网上关于讲解闭包的文章非常多,但是并不是非常容易读懂,在这里以<javascript高级程序设计>里面的理论为基础.用拆分的方式 ...

  4. js中的闭包之我理解

    闭包是一个比较抽象的概念,尤其是对js新手来说.书上的解释实在是比较晦涩,对我来说也是一样. 但是他也是js能力提升中无法绕过的一环,几乎每次面试必问的问题,因为在回答的时候.你的答案的深度,对术语的 ...

  5. js中的“闭包”

    js中的“闭包” 姓名:闭包 官方概念:闭包是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分. ( ⊙o⊙ )!!!这个也太尼玛官方了撒,作为菜鸟的 ...

  6. Js中的闭包原理

    要了解清楚js中的闭包制机,那么得先了解全局执行环境.块级执行环境.函数执行环境.变量对象.环境栈.作用域链.摧毁执行环境. 全局执行环境 全局执行环境指的是最外层的执行环境.在web中全局执行环境被 ...

  7. js中的闭包理解一

    闭包是一个比较抽象的概念,尤其是对js新手来说.书上的解释实在是比较晦涩,对我来说也是一样. 但是他也是js能力提升中无法绕过的一环,几乎每次面试必问的问题,因为在回答的时候.你的答案的深度,对术语的 ...

  8. js中的闭包理解

    闭包是一个比较抽象的概念,尤其是对js新手来说.书上的解释实在是比较晦涩,对我来说也是一样. 但是他也是js能力提升中无法绕过的一环,几乎每次面试必问的问题,因为在回答的时候.你的答案的深度,对术语的 ...

  9. 初识js中的闭包

    今天看了关于js闭包方面的文章,还是有些云里雾里,对于一个菜鸟来说,学习闭包确实有一定的难度,不说别的,能够在网上找到一篇优秀的是那样的不易. 当然之所以闭包难理解,个人觉得是基础知识掌握的不牢,因为 ...

随机推荐

  1. 医院里的CR、DR、CT、磁共振、B超都是什么?

    转自 百度知道MR CT CR DR DSA X线 都事医学影像疾病诊断的一种. MRI 是磁共振影像检查,可以获得横断面,矢状面和冠状面的影像.空间分辩率好. CT 是一种X线诊断设备,是一种复杂的 ...

  2. C++解析XML字符串

    项目交互遇到了需要VC++中解析XML字符串,故花了点时间了解了下VC++中解析XML的诸多方法主要包括三种:msxml(微软提供).markup.TinyXml. 开始花了点时间使用msxml3,虽 ...

  3. [常用类]Math、Random、System、BigInteger、BigDecimal

    Math类中的成员全是静态成员,构造方法是 私有的,以避免被创建对象 常用方法: int abs() double ceil() //向上取整 double floor() //向下取整 int ma ...

  4. Version Controlling with Git in Visual Studio Code and Azure DevOps

    Overview Azure DevOps supports two types of version control, Git and Team Foundation Version Control ...

  5. JavaScript基础3——使用Button提交表单

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  6. ES6——generator-yield

    yield 既可传参,又可以返回 第一个next()传参无效,只用来启动 如果函数前漏掉 * 就是普通函数 如果有yield会报错, ReferenceError: yield is not defi ...

  7. Mac更改PHP默认目录的方法

    参考:http://www.cnblogs.com/muyunlee/p/6386095.html

  8. 前端开发HTML&css入门——常用的标签以及一个小练习

    meta标签 <!doctype html> <html> <head> <meta charset="utf-8" /> < ...

  9. EL表达式多条件判断

    ${bndExport.containerList[0].kind eq '01' || bndExport.containerList[0].kind eq '02'}

  10. centos 搭建svn服务器

    1 安装svnserve yum install subversion -y 2 创建仓库 mkdir /svn/rep1 -p mkdir /svn/rep2 -p svnadmin create ...