在学习闭包之前,要先了解Javascript中作用域(scope)的相关概念:

一,变量作用域(Gloabl & Local)

1.全局变量能在任何地方被访问;

 1 var a = 8;
 2 function a(){
 3     alert(a);
 4 }
 5 a(); //8
 6 //假如改写下面一种函数
 7 function a(){
 8     alert(a);
 9     var a = 1;
10     alert(a);
11 }
12 a(); //undifined, 1

后一种情况,在a()的作用域中,变量a被重写(rewrite),故第一个alert的时候提示a未定义.

2.定义在函数内的变量不能被函数外的程序访问到

1 function a(){
2     var a = 1;
3 }
4 alert(a);//undefined

3.定义在函数块(if or for loop...)中的变量对外部(第一层函数体)是可见的

 1 function f1(){
 2     var a = 1;
 3     for(var i=1;i<10; i++)
 4         a++;
 5     function f2(){
 6                 if(true)
 7             var b = 8;
 8     }
 9         alert(i);//10,i在f1()范围内都是可见的
10         f2();
11     alert(b);//undefined,b的可见性止于f2();
12 }
13 f1();
14 alert(i);//undifined,i的可见性止于f1();

4.若函数f1()中嵌套另一个函数fn(),则f2()能访问的变量将是它自己内部定义的加上父级函数(这里是:f2())的变量.

 1 var a = 1;
 2 function f1(){
 3     var b =2;
 4     alert(a); //1
 5     function f2(){
 6         var c =3;
 7         alert(b); //2,alert(a);//1
 8         function f3(){
 9             var d =4;
10             alert(c); //3,alert(b);//2,alert(a);//1
11             ...
12                 function fn(){
13
14                 }
15         }
16     }
17 }

当然,如果你喜欢.可以一直嵌套下去,这个规则是一直适合的.

二,函数作用域(Lexical Scope)

在函数被定义的时候(不是执行的时候),函数的"环境"就被创建.

1 function f1(){ var a =1; f2();}
2 function f2(){ return a; }
3 //error: a is not defined .当f2被定义(不是执行)的时候,在他自己的作用域和Gloabl scope中并未发现a.
4 f1();

当一个函数被定义的时候,他就"记住"了他所处的环境--作用域链;就像上面的程序,f1(),f2()都是定义的全局函数,因此他们的作用域都是Gloabl Scope.因此当f2();执行的时候自然不能访问f1()内部的变量.这时两者的作用域分别为:

1.f1();全局变量+自己内部变量;

2.f2();只能访问全局范围内的变量.

同样地:做如下的修改

1 function f1(){ var a =1; return f2();}
2 function f2(){ return a; }
3 //error: a is not defined.
4 f1();

同上:此时的f1,f2所处的环境都是Window,属于全局函数,f2自然也不能访问到同级别的f1中的变量.在f2定义的时候,并没有一个全局变量叫'a'的....

做如下改动:

 1 var = 5;
 2 f1(); //5
 3 a = 55; //缺省的全局变量
 4 f1(); //55
 5 delete a;//true
 6 f1(); // a is not defined
 7 delete f2();//true
 8 f1(); //f2 is not defined
 9 //重新定义下f2;
10 var f2 = function() { return a*2; }
11 var a = 5;
12 f1(); //10

当声明一个全局变量a后,f2()就可以正常工作了,因为f2记住了它所处的"环境"--Global Scope,并且它能访问到一切声明在全局的变量.就像这里的a.

故可得出以下结论:

当函数被定义的时候,只是记住了当前自己所处的"环境"--作用域.当函数执行的时候,按照自己能访问的域寻找相应的变量或则函数.注意,这里只能是自己有权访问的范围!(作用域链)

三.闭包--打破上面的作用域链限制
图(1)

我们把Gloabl Scope想成是整个宇宙,当然他包括一切事物.在这里,它包含变量(a),还有函数(F).

图(2)

在全局范围下,每个函数有自己的私有空间,他们能用这个空间存储一些变量,甚至是函数.

图(3)

如上图所示:有一个全局环境G,一个全局变量a,全局函数F,以及F中定义的变量b,还有F中私有的方法N和N自己的变量c.则他们的访问规则如下:

最里层的函数能访问到外层变量,反之则不行.

有趣的时,当引入这样一个函数N后,这样的规则将会被打破!---闭包

图(4)

我们来分析一下,在这里F,N处在同样的外部环境,大家都是全局函数,他们都能记住自己被定义时的环境.

另外的:N还能访问到F-space.故也能访问到变量b,这非常的有趣,因为a和N的位置一样.但是只有N能访问到b,而a却不能!---N打破了传统的作用域链.

闭包是怎么形成的?

1.可以在图(3)所示的情况中,定义N是忽略掉关键字var.(定义成了全局的函数).这样N具有双重身份,既是全局函数,但又能访问到F的空间!

1 function f1(){
2     f2 = function() {
3         //
4     }
5 }

2.通过F把N传递(return)到全局空间.

1 function f1(){
2     //
3     return function f2(){
4         //
5     }
6 }

实例1:

1 function f(){
2   var b = "b";
3   return function(){
4     return b;
5   }
6 }

声明了一个全局函数f(),有一个局部变量b;返回值是一个函数(匿名);把这个返回函数想象成上图中的N.它能访问到特有的环境--"f()的空间"+"全局空间";故它能访问到b;因为f是一个全局函数.你可以这样使用他:

1 var n = f();
2 n();

神奇的事就这么发生了,这里的新函数n()能访问到f的私有空间!

实例2:

1 var n;
2 function f(){
3   var b = "b";
4   n = function(){
5     return b;
6   }
7 }
8 n(); //"b"

So:当我们调用f();的时候会发生什么情况?

一个新的函数n被定义在f里面,不幸的是.定义的时候忘记了加关键字var.导致n变成了全局函数.在定义的时间内,n()都是在f()内部的,所以n()尽管作为全局函数,但也能访问到f的私有变量或则函数.

实例3:

THEN: What's Closure?

从我们上面的这么多讨论可以得到一个比较通俗的理解:闭包就是一种方法:想办法(return/gloabl function)让外界访问到父辈的私有变量.

1 function f(arg) {
2   var n = function(){
3     return arg;
4   };
5   arg++;
6   return n;
7 }
8 var m = f(23);
9 m(); //24

让外界实时感知f()内部私有变量的变化

实例4.循环结构中的闭包

循环结构中的闭包是很容易出现BUG的,如果不注意使用:特别是对概念理解不强的情况下.如下:

 1 function f() {
 2   var a = [];
 3   var i;
 4   for(i = 0; i < 3; i++) {
 5     a[i] = function(){
 6       return i;
 7     }
 8   }
 9   return a;
10 }

在f()中我们通过for()循环每次产生一个新的函数(闭包),然后用数组a记录下函数.最后返回a.试想一下a中的内容会是什么?接下来就是见证奇迹的时候....

1 var a = f();
2 a[0]; //3
3 a[1]; //3
4 a[2[; //3

哇!怎么会这样???你还可以通过这里了解更多
原理:我们通过循环创建了三个闭包,但是所有的闭包都是指向变量i的.由于前面说过,函数定义的时候只是记住了当前所处的环境,并没有记住自己范围内所有变量的值(这些值是可以随时增加/修改/删除的).他们做的都是同一件事---指向i.故在循环结束以后,i都变成了3.这就是产生以上结果的最终原因.

如何改进:

就上面产生的原因来讲,我们只要让三个闭包拥有不同的指向,这个问题就能解决.

 1 function f() {
 2   var a = [];
 3   var i;
 4   for(i = 0; i < 3; i++) {
 5     a[i] = (function(x){
 6       return function(){
 7         return x;
 8       }
 9     })(i);
10   }
11   return a;
12 }

是的,在这里通过self-invoking的方式,在每个闭包刚定义的时候就已经执行了---等等....这个执行的意思是:此时的x并不是一个指向了,而是一个真正的值!

PS:self-invoking大概是如下意思:

1 (function() { //... })();
2 //第一个()里面是函数主体.第二个括号的意思是执行...第二个()中的参数就是传递给这个函数的参数

改进后的执行结果将是:

1 var a = f();
2 a[0]; //0
3 a[1]; //1
4 a[2]; //2

还有一中改进方法:

另外声明一个构造函数:

 1 function makeClosure(x) {   //构造函数
 2     return function(){
 3       return x;
 4     }
 5 }
 6
 7 function f() {
 8   var a = [];
 9   var i;
10   for(i = 0; i < 3; i++) {
11     a[i] = makeClosure(i);
12   }
13   return a;
14 }

实例5.Getter/Setter

想象一个这样一个场景:你拥有一组比较特殊的变量,你不想他们暴露,并且也不希望其他的任何部分代码改变他们的值.方法是:

在这个函数里面提供额外的两个函数---一个用来获取他们的值(gettter),另一个用来设置他们的值(setter).当然,设置值的函数还应该包含一些必要的验证措施.

把函数getter和setter都放在含有特殊变量的函数里面,即他们共享相同的环境--作用域.

 1 var getter, setter;
 2 (function() {
 3   var secret = 0;
 4   getter = function(){
 5     return secret;
 6   };
 7   setter = function(v){
 8     secret = v;
 9   };
10 })()

在这个匿名函数里面,定义了两个全局的函数getter和setter,secret是一个无法被外界直接访问的私有变量.将会有如下的工作过程:

1 getter(); //0
2 setter(88);
3 getter(); //88

实例6:迭代器:展示用一个闭包来完成迭代器的功能

试想一下你有一个非常复杂的数据结构,你想遍历里面的每一项内容,我们实现一个next()方法去遍历他.

下面这个函数接受一个数组输入,然后在函数内部定义一个私有的指针i,i总是指向数组中的下一个元素.

1 function setup(x) {
2   var i = 0;
3   return function(){
4     return x[i++];
5   };
6 }

我们这样调用setup()的时候将会是这样的结果:

1 var next = setup(['a','b','c']);
2 next(); //"a"
3 next(); //"b"
4 next(); //"c"
5 next(); //undefined

注意,这里有一个问题:setup()函数中的私有变量i是一直存在内存中的!因为闭包会一直依赖它所处的环境,故父函数的变量会一直存在在内存中,滥用闭包将有可能引起内存泄漏!所以在退出闭包前,需要清空不必要的局部变量.

转载于:https://www.cnblogs.com/Poised-flw/archive/2013/03/28/2999674.html

Javascript 笔记(2)----闭包相关推荐

  1. [Effective JavaScript 笔记]第27条:使用闭包而不是字符串来封装代码

    函数是一种将代码作为数据结构存储的便利方式,代码之后可以被执行.这使得富有表现力的高阶函数抽象如map和forEach成为可能.它也是js异步I/O方法的核心.与此同时,也可以将代码表示为字符串的形式 ...

  2. JavaScript笔记 | 作用域和闭包 |《你不知道的JavaScript(上卷)》第一部分

    JavaScript | 作用域和闭包 | 读书笔记 读书笔记(自用) 来自<你不知道的JavaScript(上卷)>第一部分 作用域和闭包 1 作用域是什么 1.1编译的3个步骤 (1) ...

  3. 【javascript笔记】关于javascript中的闭包

    最开始看<javascript高级程序设计>的时候就看到了javascript中的闭包,在第七章第二节....好大概知道了,过了段时间,好了又忘了... 我们来看这本书里面关于闭包是怎么描 ...

  4. 【进阶2-3期】JavaScript深入之闭包面试题解

    (关注福利,关注本公众号回复[资料]领取优质前端视频,包括Vue.React.Node源码和实战.面试指导) 本周正式开始前端进阶的第二期,本周的主题是作用域闭包,今天是第8天. 本计划一共28期,每 ...

  5. JavaScript笔记(狂神说)

    JavaScript笔记(狂神说) 本文章根据b站狂神说javascript视频教程整理 视频链接:https://www.bilibili.com/video/BV1JJ41177di?from=s ...

  6. sizzle.js学习笔记利用闭包模拟实现数据结构:字典(Map)

    sizzle.js学习笔记利用闭包模拟实现数据结构:字典(Map) 这几天学习和查看了jQuery和Property这两个很流行的前端库的御用选择器组件Sizzle.js的源代码,收获还是相对多的!之 ...

  7. javascript笔记:深入分析javascript里对象的创建(上)续篇

    今天回来一看我的博客居然有这么多人推荐真是开心极了,看来大家对我的研究有了认可,写博客的动力越来越大了,而且我发现写javascript在博客园里比较受欢迎,写java的受众似乎少多了,可能博客园里j ...

  8. 让你分分钟学会Javascript中的闭包

    Javascript中的闭包 前面的话: 闭包,是 javascript 中重要的一个概念,对于初学者来讲,闭包是一个特别抽象的概念,特别是ECMA规范给的定义,如果没有实战经验,你很难从定义去理解它 ...

  9. 深入理解javascript原型和闭包(16)——完结

    之前一共用15篇文章,把javascript的原型和闭包. 首先,javascript本来就"不容易学".不是说它有多难,而是学习它的人,往往都是在学会了其他语言之后,又学java ...

  10. 深入理解javascript原型和闭包(17)——补this

    本文对<深入理解javascript原型和闭包(10)--this>一篇进行补充,原文链接:http://www.cnblogs.com/wangfupeng1988/p/3988422. ...

最新文章

  1. Connot resolve Symbol '.......'
  2. Django入门(七)   django的缓存
  3. 多线程开发之---线程等待
  4. LeetCode 295. 数据流的中位数 Hard难度
  5. JavaScript从入门到精通之入门篇(二)函数和数组
  6. redis过期监听性能_基于Redis的延迟处理
  7. ASP.NET MVC5+EF6+EasyUI 后台管理系统(40)-精准在线人数统计实现-【过滤器+Cache】...
  8. centos6.0安装中文输入法
  9. 使用阿里云Mysql集群做读写分离_以及使用阿里云服务器自己搭建MyCat集群_费用核算---Linux工作笔记046
  10. 【十一】Jmeter 函数助手实战:__time 时间戳函数
  11. bzoj 2434 [Noi2011]阿狸的打字机(AC自动机+fail树+dfs序+树状数组)
  12. Linux初学者需了解的知识
  13. 软件测试项目案例.pdf,最经典软件测试案例.pdf
  14. 【人工智能】动物、植物、车型、菜品、LOGO识别示例代码
  15. 显卡RTX2080 + CUDA10 + win10 + tensorflow配置安装探坑记
  16. 关于华为AR/HUAWEI AR Engine
  17. Java VisualVM安装Visual GC插件
  18. ZROI – 19普及组 – Day2 – T4 – 与非门树
  19. 关于25Qxx踩坑总结(无法写入)
  20. 最新Python 实现自动登录抖音(京东),实现滑块自动滑过

热门文章

  1. VDI环境的性能利器——固态存储
  2. Site-Site Ipsec ×××配置和验证
  3. vs2008 jQuery 智能提示失败可能是Jquery版本问题
  4. FPGA笔试数电部分(一)
  5. 非IT专业大学生对erp的思考
  6. 空类型(void *)的简单理解
  7. a标签提交form表单_Web前端开发基础知识,HTML中表单元素的理解
  8. 七夕祝福网页制作_啥?七夕过了你还不知道自己为啥单身??
  9. mysql默认dba_DBA 基本常识 - 安装完 MySQL 后必须调整的 10 项配置 - iTeknical
  10. 【渝粤教育】国家开放大学2018年春季 0550-21T素描(一) 参考试题