你的位置:首页 > Java教程

[Java教程]JavaScript中的作用域和闭包


JavaScript中的作用域和闭包

 

很多人都在说自己对闭包的理解,有的对我理解闭包很有帮助,有的我看了还是没能够理解,以下是我自己学到现在对作用域和闭包的理解,可能还比较浅显,希望给跟我一样在学习JavaScript的人们一点点帮助,也希望大家不吝指教!

 

先要了解全局变量和局部变量。

 

在最外层直接声明的变量,就叫全局变量。全局变量(或函数),在全局的任何地方都可以使用(作用范围是全局)。

在函数中声明的变量,叫做该函数的局部变量。局部变量(或函数),只在该函数内部起作用,外边访问不到。

 

1)函数内部可以直接读取全局变量

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

2)函数外部无法读取函数内的局部变量

1 function fn(){2   var a = 1;3 }4 alert(a); //报错 a is not defined

3)函数内部声明变量的时候,一定要加上var,否则实际上是声明了一个隐式全局变量

1 function fn(){2   a = 1;3 }4 fn();5 alert(a); // 1

4)

1 function fn1(){2   var a = 1;3   function fn2(){ //函数fn2就是闭包4     alert(a);5   }6 return fn2();7 }8 fn1(); // 1

函数fn2被嵌套在函数fn1内,这时fn1内部的所有局部变量,对fn2都是可见的。但是反过来fn2内的局部变量对fn1都是不可见的。既然fn2可以读取fn1中的局部变量,那么只要把fn2作为返回值,我们就可以在fn1外部读取它的内部变量了。

 

JavaScript中的闭包,就是定义在一个函数内部的子函数,可以使用其父函数的局部变量。

1 function fn(b){2 return function(){3   b++;4   alert(b);5 }6 }7 var a = fn(0);8 a(); // 1

闭包除了可以读取其父函数的局部变量,还会让这些变量的值始终保存在内存中。消耗内存,所以不使用的局部变量需要删除。记得老师还说过在IE中可能导致内存泄露。

 1   function fn1(){ 2  3     var a = 0; 4     b = function(){ 5       a++; 6     } 7     function fn2(){ 8       alert(a); 9     }10     return fn2;11   }12 13   var result = fn1();14   result(); // 015   b();16   result(); // 1

在这段代码中,result实际上就是闭包fn2函数。它一共运行了两次,第一次的值是0,第二次的值是1。这证明了,函数fn1中的局部变量a一直保存在内存中,并没有在fn1调用后被自动清除。

原因就在于fn1是fn2的父函数,而fn2被赋给了一个全局变量,这导致fn2始终在内存中,而fn2的存在依赖于fn1,因此fn1也始终在内存中,不会在调用结束后,被垃圾回收机制回收。

b = function(){
a++;
}

b是一个隐式全局变量,它的值是一个匿名函数,匿名函数本身也是一个闭包,所以可以在函数外边对函数内的局部变量进行操作。

 

作用域:

JS作用(读、写)域(空间、范围):可以理解为在什么样的范围内可以对数据进行读写。

alert(a);var a = 0; //undefined

alert(a);a = 0; //报错a is not defined

 

浏览器中的“JS解析器”:读到script开始执行代码之前至少分两步

1)“找一些东西”:var function 参数

比如找到了咱们声明的变量a之后不会去读,是先给他一个值未定义a = undefined;

所有的变量,在正式运行代码之前,都提前赋了一个值:未定义

fn1 = function fn1(){alert(2);}

所有的函数,在正式运行代码之前,都是整个函数块

这一步叫做:JS的预解析

预解析的过程中遇到重名的会只留一个(有值的),如果都是有值的就把之前的替换掉。

解读代码的时候读到表达式会修改预解析的值。函数的声明不去修改表达式的值。

2)逐行解读代码:(修改仓库里的数据)

表达式(可以改变值的):= + - * / % ++ -- ! 参数 ...... Number()

当读到alert(a);时,不会往下去读,会先去它的仓库里去找有没有a

 

<script></script>是一个域,一个页面中可以有多个

自上而下,处理完一个script再处理下一个script。上边的script会影响到下边的script

所以,当一个script标签,需要调用另外一个script内的东西,那这个script一定要写在另外一个script下边

if和for的{}不是作用域,相当于是通透的 里边的东西和定义到全局是一样的

 

作用域范围:

全局作用域:

1)最外层定义的全局变量、全局函数;

2)未定义直接赋值的变量即隐式全局变量;

3)所有window对象的属性。

以上都拥有全局作用域

局部作用域:function(函数作用域)

由里到外 (局部有能力改外面的值)

 

几段代码:

1)

1 var a = 1;2 function fn1(){3   alert(a);4   var a = 2;5 }6 fn1();   //undefined7 alert(a); //1

刚开始还是找var function 参数,function还是函数块;当逐行解读代码的时候,读到函数调用fn1(),函数是一个域,开启了一个新的作用域,又会发生两件事(预解析,逐行解读代码)“一个小仓库” 局部的a跟外边全局的a没有关系

2)

作用域链:就近原则,先在子级找,在子级找不到会返回到父级去找

1 var a = 1;2 function fn1(){3   alert(a);4   a = 2;5 }6 fn1();   //17 alert(a); //2

3)参数本质上就是一个局部变量

1 var a = 1;2 function fn1(a){3   alert(a);4   a = 2;5 }6 fn1();   //undefined7 alert(a); //1

4)参数也是可以改变值的,函数调用从参数开始读

1 var a = 1;2 function fn1(a){3   alert(a);4   a = 2;5 }6 fn1(a);  //17 alert(a); //1

5)局部是可以访问到外面的值的,所以可以改num的值。

1 var num = 0;2 function fn1(){3   num++;4 }5 fn1();6 alert(num); //1

从外面(全局)是找不到里面(函数/局部)的东西的,如果想找到至少有两种方法:

想要获取函数内的值:

1)用全局变量

1 var str = '';2 function fn1(){3   var a = 'abc';4   str = a;5 }6 fn1();7 alert(str); // abc

2)局部的函数调用

1 function fn1(){2   var a = 'abc';3   function fn2(a){4     alert(a);5   }6   fn2(a);7 }8 fn1();