一、javascript中的作用域

①全局变量-函数体外部进行声明

②局部变量-函数体内部进行声明

1)函数级作用域

javascript语言中局部变量不同于C#、Java等高级语言,在这些高级语言内部,采用的块级作用域中会声明新的变量,这些变量不会影响到外部作用域。

而javascript则采用的是函数级作用域,也就是说js创建作用域的单位是函数。

例如:

在C#当中我们写如下代码:

  1. static void Main(string[] args)
  2. {
  3. for (var x = ; x < ; x++)
  4. {
  5. Console.WriteLine(x.ToString());
  6. }
  7. Console.WriteLine(x.ToString());
  8. }

上面代码会出现如下的编译错误:

The name 'x' does not exist in the current context

同样在javascript当中写如下代码:

  1. <script>
  2. function main() {
  3. for (var x = 1; x < 10; x++) {
  4. console.log(x.toString());
  5. }
  6. console.log(x.toString());
  7. }
  8. main();
  9. </script>

输出结果如下:

[Web浏览器] "1"

[Web浏览器] "2"

[Web浏览器] "3"

[Web浏览器] "4"

[Web浏览器] "5"

[Web浏览器] "6"

[Web浏览器] "7"

[Web浏览器] "8"

[Web浏览器] "9"

[Web浏览器] "10"

这就说明了,“块级作用域”和“函数级作用域”的区别,块级作用域当离开作用域后,外部就不能用了,就像上面的C#例子,"x"离开for循环后就不能用了,但是javascript中不一样,它还可以进行访问。

 2)变量提升

再看javascript中作用域的一个特性,例子如下:

  1. function func(){
  2. console.log(x);
  3. var x = 1;
  4. console.log(x);
  5. }
  6. func();

输出结果:

[Web浏览器] "undefined"

[Web浏览器] "1"

如上面的结果:有意思的是,为什么第一个输出是“undefined”呢?这就涉及到javascript中的“变量提升”,其实我感觉叫“声明提升”更好,因为这个机制就是把变量的声明提前到函数的前面。并不会把值也同样提升,也就是为什么第一次没有输出“1”的原因。

上面的代码就相当于:

  1. function func(){
  2. var x;
  3. console.log(x);
  4. var x = 1;
  5. console.log(x);
  6. }
  7. func();

3)函数内部用不用“var”对程序的影响

这是个值得注意的地方:

  1. var x = 1;
  2. function addVar(){
  3. var x = 2;
  4. console.log(x);
  5. }
  6. addVar();
  7. console.log(x);

输出:

[Web浏览器] "2"

[Web浏览器] "1"

当在函数内部去掉var之后,再执行:

  1. var x = 1;
  2. function delVar(){
  3. x = 2;
  4. console.log(x);
  5. }
  6. delVar();
  7. console.log(x);

[Web浏览器] "2"

[Web浏览器] "2"

上面的例子说明了,在函数内部如果在声明变量没有使用var,那么声明的变量就是全局变量。

二、javascript的作用域链

先看如下的代码:

  1. var name="Global";
  2. function t(){
  3.         var name="t_Local";
  4.         
  5.         function s1(){
  6.             var name="s1_Local";
  7.                 console.log(name);
  8.         }
  9.         function s2(){
  10.                 console.log(name);
  11.         }
  12.         s1();
  13.         s2();
  14. }
  15. t();

输出结果:

[Web浏览器] "s1_Local"

[Web浏览器] "t_Local"

那么就有几个问题:

1.为什么第二次输出的不是s1_Local?

2.为什么不是Global?

解决这个两个问题就在于作用域链…

下面就解析一下这个过程,

例如当上面程序创建完成的时候,会形成上图中的链状结构(假想的),所以每次调用s1()函数的时候,console.log(name);先会在他自己的作用域中寻找name这个变量,这里它找到name=“s1_Local”,所以就输出了s1_Local,而每次调用s2()的时候,它和s1()函数过程一样,只不过在自身的作用域没有找到,所以向上层查找,在t()函数中找到了,于是就输出了"t_Local"。

同样如果我们可以验证一下,如果把t中的name删除,可以看看输出是不是“Global”

结果如下:

[Web浏览器] "s1_Local"

[Web浏览器] "Global"

当然具体每一个作用域直接是如何连接的,请参考

http://www.cnblogs.com/lhb25/archive/2011/09/06/javascript-scope-chain.html

其中也说明了为什么JS当中应该尽量减少with关键字的使用。

三、闭包

了解了作用域和作用域链的概念之后,再去理解闭包就相对容易了。

1.闭包第一个作用

还是先看例子:

  1. function s1() {
  2. var x = 123;
  3. return s2();
  4. }
  5.  
  6. function s2() {
  7. return x;
  8. }
  9.  
  10. alert(s1());

这里我想弹出x的值,但是却发生了错误 "Uncaught ReferenceError: x is not defined",根据作用域链的知识,了解到因为s2中没有x的定义,并且向上找全局也没有x定义,所以就报错了。也就是说s1和s2不能够共享x的值。

那么问题来了,我想要访问s1当中的x,怎么弄?

修改代码如下:

  1. function s1() {
  2.     var x = 123;
  3.     return function(){
  4.         return x;
  5.     };
  6. }
  7.     
  8. var test = s1();
  9. console.log(test());

结果为:

[Web浏览器] "123"

解释:因为function本质也是数据,所以它和x的作用域相同,可以访问x。这样就相当于对外部开放了一个可以访问内部变量的接口。

还可以怎么玩,稍微修改一下代码

  1. var func;
  2. function f(){
  3. var x='123';
  4. func=function(){
  5.     return x;
  6.     };
  7. }
  8. f();
  9. alert(func());

定义一个全局的变量,然后在函数内部让其等于一个函数,这样就可以通过这个全局变量来进行访问x了。

综上:闭包是啥?闭包就相当于函数外部和内部的桥梁,通过闭包可以在外部访问函数内部的变量。这也是闭包的第一个作用。

2.闭包的第二个作用

先看代码:

  1. function f1(){
  2.     var n=1;
  3.     add=function(){
  4.         n+=1;
  5.     };
  6.     function f2(){
  7.         console.log(n);
  8.         return '输出完成';
  9.     }
  10.     return f2;
  11. }
  12. var res=f1();
  13. console.log(res());
  14. add();
  15. console.log(res());

输出结果:

[Web浏览器] "1"

[Web浏览器] "输出完成"

[Web浏览器] "2"

[Web浏览器] "输出完成"

问题为什么第二次输出的结果n变成了2,没有被清除?

我的理解:res是一个全局变量,一直驻留在内存当中,它就相当于f2函数,f2函数又要依赖于f1函数,所以f1也驻留在内存当中,保证不被GC所回收,每一次调用add函数的时候,就相当于把f1中的n重新赋值了,这样n的值就变化了。这也是闭包的第二个作用,可以让变量的值始终保存在内存当中

3.闭包的应用

①可以做访问控制(相当于C#当中的属性)

  1. //定义两个变量,用于存储取值和存值
  2. var get,set;
  3. //定义一个自调用函数,设定set和get方法
  4. (function(){
  5. //设定x的默认值
  6.     var x = 0;
  7.     set = function(n){
  8.         x = n;
  9.     }
  10.     get = function(){
  11.         return x;
  12.     }
  13. })();
  14.  
  15. console.log(get());
  16. set(5);
  17. console.log(get());

输出结果:

[Web浏览器] "0"

[Web浏览器] "5"

②可以用做迭代器

  1. //定义一个函数,里面使用了闭包
  2. function foo(myArray){
  3.     var i=0;
  4.     //闭包迭代器
  5.     next=function(){
  6.         //每次返回数组的当前值,下标+1
  7.         return myArray[i++];
  8.     }
  9. }
  10. //调用foo,参数为一个数组
  11. foo(['a','b','c','d']);
  12. //每次调用next都会打印数组的一个值
  13. console.log(next());
  14. console.log(next());
  15. console.log(next());
  16. console.log(next());

输出结果:

[Web浏览器] "a"

[Web浏览器] "b"

[Web浏览器] "c"

[Web浏览器] "d"

③闭包在循环中的使用

  1. 1
  2. function f(){
  3.     var a=[];
  4.     var i;
  5.     for(i=0;i<3;i++){
  6.         a[i]=function(){
  7.             return i;
  8.         };
  9.     }
  10.     return a;
  11. }
  12. var test=f();
  13. console.log(test[0]());
  14. console.log(test[1]());
  15. console.log(test[2]());

输出结果:

[Web浏览器] "3"

[Web浏览器] "3"

[Web浏览器] "3"

为什么结果不是0、1、2?

这里我们使用了闭包,每次循环都指向了同一个局部变量i,但是闭包不会记录每一次循环的值,只保存了变量的引用地址,所以当我们在调用test[0]()、test[1]()、test[2]()的时候都返回的是for循环最后的值,也就是3的时候跳出了循环。

例2:我想输出0,1,2怎么搞?

  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. }
  13. var test=f();
  14. console.log(test[0]());
  15. console.log(test[1]());
  16. console.log(test[2]());

结果:

[Web浏览器] "0"

[Web浏览器] "1"

[Web浏览器] "2"

关于这个为什么和上面不一样,我在知乎上找到了一篇文章,总结的非常好,

★★★★★引用自:http://www.zhihu.com/question/33468703

Javascript的作用域、作用域链以及闭包的更多相关文章

  1. 个人理解的javascript作用域链与闭包

    闭包引入的前提个人理解是为从外部读取局部变量,正常情况下,这是办不到的.简单的闭包举例如下: function f1(){ n=100; function f2(){ alert(n); } retu ...

  2. JavaScript高级内容:原型链、继承、执行上下文、作用域链、闭包

    了解这些问题,我先一步步来看,先从基础说起,然后引出这些概念. 本文只用实例验证结果,并做简要说明,给大家增加些印象,因为单独一项拿出来都需要大篇幅讲解. 1.值类型 & 引用类型 funct ...

  3. 【进阶2-2期】JavaScript深入之从作用域链理解闭包(转)

    这是我在公众号(高级前端进阶)看到的文章,现在做笔记   https://github.com/yygmind/blog/issues/18 红宝书(p178)上对于闭包的定义:闭包是指有权访问另外一 ...

  4. JavaScript高级内容笔记:原型链、继承、执行上下文、作用域链、闭包

    最近在系统的学习JS深层次内容,并稍微整理了一下,作为备忘和后期复习,这里分享给大家,希望对大家有所帮助.如有错误请留言指正,tks. 了解这些问题,我先一步步来看,先从稍微浅显内容说起,然后引出这些 ...

  5. 《浏览器工作原理与实践》<10>作用域链和闭包 :代码中出现相同的变量,JavaScript引擎是如何选择的?

    在上一篇文章中我们讲到了什么是作用域,以及 ES6 是如何通过变量环境和词法环境来同时支持变量提升和块级作用域,在最后我们也提到了如何通过词法环境和变量环境来查找变量,这其中就涉及到作用域链的概念. ...

  6. 图解Javascript——作用域、作用域链、闭包

    什么是作用域? 作用域是一种规则,在代码编译阶段就确定了,规定了变量与函数的可被访问的范围.全局变量拥有全局作用域,局部变量则拥有局部作用域. js是一种没有块级作用域的语言(包括if.for等语句的 ...

  7. javascript深入理解--作用域,作用域链,闭包的面试题解

    一.概要 作用域和作用域链是js中非常重要的特性,关系到理解整个js体系,闭包是对作用域的延伸,其他语言也有闭包的特性. 那什么是作用域?作用域指的是一个变量和函数的作用范围. 1.js中函数内声明的 ...

  8. javascript笔记:javascript的关键所在---作用域链

    javascript里的作用域是理解javascript语言的关键所在,正确使用作用域原理才能写出高效的javascript代码,很多javascript技巧也是围绕作用域进行的,今天我要总结一下关于 ...

  9. javascript的关键所在---作用域链

    javascript的关键所在---作用域链 javascript里的作用域是理解javascript语言的关键所在,正确使用作用域原理才能写出高效的javascript代码,很多javascript ...

随机推荐

  1. Kooboo中如何切换数据库(注意:如果切换数据库,需要Kooboo中没有一个website 否则会报错数据库中没有表之类的)

    Setup database provider 来自Kooboo document   Kooboo CMS can almost support all the types of database, ...

  2. 转载 8天掌握EF的Code First开发之Entity Framework介绍

    转载原地址:  http://www.cnblogs.com/farb/p/IntroductionToEF.html Entity Framework概要 Entity Framework是微软的O ...

  3. 1098. Insertion or Heap Sort (25)

    According to Wikipedia: Insertion sort iterates, consuming one input element each repetition, and gr ...

  4. 消息队列数量统计(MSMQ,Performance Counter)

    微软消息队列服务MSMQ (Microsoft Message Queue),工作在在线或者离线场景,并提供异步编程功能.互联网和企业开发很多场景应用,例如电商的订单处理流程,这是因为客户端不需要等待 ...

  5. Android的横竖屏切换

    android的横竖屏切换,也会发生不少问题. 1. 锁定屏幕方向,禁止切换: 在AndroidManifest.xml中的Activity参数中加上   android:screenOrientat ...

  6. iOS使用宏写单例

    本文只介绍ARC情况下的单例 过去一直背不下来单例如何写,就是知道这么回事,也知道通过宏来写单例,但是一直记不住,今天就来记录一下 - (void)viewDidLoad {     [super v ...

  7. 面试题总结之JAVA

    JAVA 1. what is thread safe? 线程安全就是说多线程访问同一代码,不会产生不确定的结果.编写线程安全的代码是低依靠线程同步.线程安全: 在多线程中使用时,不用自已做同步处理线 ...

  8. Linux中的终端、控制台、tty、pty等概念

    参考:http://news.newhua.com/news1/program_language/2010/623/10623141048745773199BCF0CFH6AKB9930IGCFKHB ...

  9. Languages

    Languages A language class exists inside the system/Core folder, this class have 2 methods: load - L ...

  10. javascript笔记02:严格模式的特定要求

    1.严格模式变量必须声明,不然会报错: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" &quo ...