JavaScript闭包浅谈

-------------------

作者:willingtolove;

本文链接:http://www.cnblogs.com/willingtolove/p/4745889.html

1. 变量的作用域:

  在javascript中,局部变量的作用域是由它定义的函数决定的,嵌套函数可以访问它的外部作用域的变量。

  EX1:

1 function hello() {
2     var name = "world";
3     function hi() {
4         alert(name);
5     }
6     hi();
7 }
8 hello();

  上述代码的运行结果:弹出框   “world”;

   因为name这个变量的作用域是1-7行(hello这个函数内);

  要想详细理解请查阅:作用域与作用域链;这个对理解闭包很有用!

2. js的垃圾回收机制

   js具有自动垃圾收集机制,开发人员不用再关心内存使用问题;这种垃圾收集机制原理很简单:找出不再继续使用的变量,然后释放其占用的内存。为此,垃圾收集器会按固定的时间间隔

(或按代码设定的)周期性的执行这一操作。

   函数中局部变量的生命周期:局部变量只在函数执行的过程中存在。这个过程中,会为变量在栈或堆上分配相应的空间,以便存储它们的值。当函数结束后,该变量就没用了,就会被垃圾

收集器所标记,等待回收。下次再执行此函数的时候,所有的变量又回到最初的状态,重新赋值使用.但是有时候,这个函数内部又嵌套了另一个函数,而这个内部函数使用了外部函数的变量,并且

内部函数在外面被调用.这时垃圾回收机制就会有问题.如果在外部函数结束后,又直接调用了其内部函数,那么内部函数就无法读取到他所需要的外部函数中变量的值了.所以js解释器在遇到函数定

义的时候,会自动把函数和它可能使用的变量(包括本函数内变量和父级和祖先级函数的变量)一起存储起来,相当于存储了一个环境,也就是构建一个闭包,这些变量(或者说是环境)将不会被内

存回收器所回收,只有当内部的函数不可能被调用以后(例如被删除了...),才会销毁这个闭包,而没有任何一个闭包引用的变量会被下一次内存回收启动时所回收.

3. 闭包的定义:

    闭包是涉及独立变量的的函数;换句话说,在闭包中定义的函数会记住它创建的环境;

    也有这样理解的:闭包就是能够读取其他函数内部变量的函数;由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成“定义在一个函数内部的函数”。

所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。

   官方”的解释是:闭包是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。

其实这句话通俗的来说就是:JavaScript中所有的function都是一个闭包。不过一般来说,嵌套的function所产生的闭包更为强大,也是大部分时候我们所谓的“闭包”。

    创建闭包的最常见的方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量;

    EX2:

1 function hello() {
2     var name = "world";
3     function hi() {
4         alert(name);
5     }
6     return hi;
7 }
8 var myHello = hello();
9 myHello();

    上面代码的运行结果与EX1中的效果一样;

     为什么呢?

     这段代码有两个特点:

    1、函数hi嵌套在函数hello内部;

    2、函数hello返回函数hi。  

     这样在执行完var myHello=hello()后,变量myHello实际上是指向了函数hi,再执行myHello()后就会弹出一个窗口显示变量name的值。这段代码其实就创建了一个闭包,为什么?

因为函数hello外的变量myHello引用了函数hello内的函数hi,就是说:当函数hello的内部函数hi被函数hello外的一个变量引用的时候,就创建了一个闭包。

     代码执行第8行时,name变量生命周期开始,但当代码执行到第9行时,name变量的生命周期并没有结束;

   通过上面的例子,可以这样理解闭包:当一个函数在它所创建的环境之外执行时,就是闭包。(函数myhello就是一个闭包)

   闭包是一个特殊的对象,它包括两部分:① 函数;②创建函数的环境;

     EX3:   闭包例子

  

 1 function play() {
 2     var num = 1;
 3     return function () {
 4         alert(num++)
 5     };
 6 }
 7 var myPlay = play();
 8 myPlay();// 结果:1
 9 myPlay();// 结果:2
10 myPlay = null;//num被回收!
11 myPlay();//错误: 缺少对象

    分析:内部函数在返回前,会将所有已访问过的外部函数中的变量(num)在内存中锁定,也就是说,这些变量将常驻 myPlay的内存中,不会被垃圾回收器回收;

num被锁定在myPlay的闭包里,那么每次执行 myPlay的时候,num都会自增一次;

    EX4:  闭包例子

 1 function Add(x) {
 2     return function (y) {
 3         return x + y;
 4     };
 5 }
 6
 7 var Add5 = Add(5);
 8 var Add10 = Add(10);
 9
10 console.log(Add5(2));
11 console.log(Add10(2));

    运行结果:7,12

   分析:

1 //Add5 就相当于:
2 function Add5(y) {
3     return 5 + y;
4 };
5 //Add10 就相当于:
6 function Add10(y) {
7     return 10 + y;
8 };

    Add5和Add10都是闭包,但是它们存储了不同的环境; Add5的环境中x是5,在Add10的环境中x是10;

4. 闭包的使用场景

  1)  匿名自执行函数:(html中元素事件初始化)

    EX5:

 1 <head>
 2     <title></title>
 3     <script type="text/javascript">
 4         window.onload = function() {
 5             for (var i = 1; i < 4; i++) {
 6                 var li = document.getElementById("a" + i);
 7                 li.onclick = (function(i) {//匿名自执行函数
 8                     return function() {
 9                         alert(i);
10                     }
11                 })(i);
12             }
13         }
14
15     </script>
16 </head>
17 <body>
18     <ul>
19         <li id="a1">a1</li>
20         <li id="a2">a2</li>
21         <li id="a3">a3</li>
22     </ul>
23 </body>

    效果:点击a1,弹出a1;点击a2,弹出a2;点击a3,弹出a3;

    2)    实现封装,从而实现了从函数外部读取函数内部的局部变量;

    EX6:

 1         var person = function () {
 2             var name = "world";//变量作用域为函数内部,外部无法访问
 3             return {
 4                 getName: function () {
 5                     return name;
 6                 },
 7                 setName: function (newName) {
 8                     name = newName;
 9                 }
10             }
11         }();
12         alert(person.name);//直接访问,结果为undefined
13         alert(person.getName());//结果:world
14         person.setName("hello");
15         alert(person.getName());//结果:hello

  3)   模拟面向对象中的对象;

      不同的对象拥有独立的成员及状态,互不干涉。虽然JavaScript中没有类这样的机制,但是通过使用闭包,
      我们可以模拟出这样的机制。

   EX7:

 1         function Person(){
 2             var name = "default";
 3
 4             return {
 5                 getName : function(){
 6                     return name;
 7                 },
 8                 setName : function(newName){
 9                     name = newName;
10                 }
11             }
12         };
13
14         var hello = Person();
15         print(hello .getName()); //结果:   default
16         hello .setName("hello");
17         print(hello .getName());  //结果:   hello
18
19         var world = Person();
20         print(world .getName());   //结果:   default
21         world .setName("world");
22         print(world .getName());    //结果:   world 

  

5. 常见错误

  EX8-1:

 1 var result = [];
 2 function play() {
 3     var i = 0;
 4     for (; i < 3; i = i + 1) {
 5         result[i] = function () {
 6             alert(i)
 7         }
 8     }
 9 };
10 play();
11 result[0](); //结果: 3
12 result[1](); //结果: 3
13 result[2](); //结果: 3

  EX6中,本希望play函数中的变量i被内部循环的函数使用,而实际上,只能获得该变量最后保留的值,也就是说.闭包中所记录的变量,只是对这个变量的一个引用,而非变量的值,当这个变量被改变了,

闭包里获取到的变量值,也会被改变.

解决的方法之一,是让内部函数在循环创建的时候立即执行,并且捕捉当前的索引值,然后记录在自己的一个本地变量里.然后利用返回函数的方法,重写内部函数,让下一次调用的时候,返回本地变量的值,改进后的代码:

   EX8-2:

 1 var result = [];
 2 function play() {
 3     var i = 0;
 4     for (; i < 3; i = i + 1) {
 5         result[i] = (function (j) {
 6             return function () {
 7                 alert(j);
 8             };
 9         })(i);
10     }
11 };
12 play();
13 result[0](); //结果: 0
14 result[1](); //结果: 1
15 result[2](); //结果: 2

性能考虑:

   EX9-1:

 1 function MyObject(name, message) {
 2   this.name = name.toString();
 3   this.message = message.toString();
 4   this.getName = function() {
 5     return this.name;
 6   };
 7
 8   this.getMessage = function() {
 9     return this.message;
10   };
11 }

  分析:每new一个MyObject,getName和getMessage就会copy一次,影响性能;

   利用闭包对EX9-1进行改进:

  EX9-2:

 1 function MyObject(name, message) {
 2     this.name = name.toString();
 3     this.message = message.toString();
 4 }
 5 (function() {
 6     this.getName = function() {
 7         return this.name;
 8     };
 9     this.getMessage = function() {
10         return this.message;
11     };
12 }).call(MyObject.prototype);

   分析:无论new多少个MyObject,getName和getMessage只copy一次;

总结:

  在动态执行环境中,数据实时地发生变化,为了保持这些非持久型变量的值,我们用闭包这种载体来存储这些动态数据。这就是闭包的作用。也就说遇到需要存储动态变化的数据或将被回收的数据

时,我们可以通过外面再包裹一层函数形成闭包来解决。

  当然,闭包会导致很多外部函数的调用对象不能释放,滥用闭包会使得内存泄露。

  ------

  此片随笔也是参考了各位前辈们的讲义,还有很多知识点等待学习研究,欢迎指正!

时间: 2024-10-06 21:25:21

JavaScript闭包浅谈的相关文章

JavaScript对象浅谈

JavaScript对象 对象 ECMA-262 把对象(object)定义为"属性的无序集合,每个属性存放一个原始值.对象或函数".严格来说,这意味着对象是无特定顺序的值的数组. 对象是属性和方法的无序集合 对象就是无序属性的集合 尽管 ECMAScript 如此定义对象,但它更通用的定义是基于代码的名词(人.地点或事物)的表示. 在JavaScript中,除了数字.字符串.布尔值.null.undefined这五种原始类型,之外的都是对象,也就是印证了"万物皆对象&quo

闭包浅谈

虽然可能在编码时会经常用到闭包,但对闭包的概念一直比较模糊. 简单说下自己对闭包的理解 闭包的实现方法  在我接触的脚本语言有 lua.js.python,感觉语法都差不多,主要特点就是 :无需编译.若类型.数据结构强大.灵活多变 这里随口说下脚本在开发中的优点: 无需编译,在软件更新时作用很大,由于appstore的审核比较严,如果运行中的游戏应用出现问题时需要更新时,这时候如果等appstore审核那估计要俩星期之后了游戏现在大部分的做法就是 游戏上层业务逻辑都用lua写,底层很少变动要求效

6. 闭包浅谈

1. 可以看成是for循环先执行,第一个function中 i 是10. // 这段代码输出的是10个10而不是期望的0到9,因为闭包内是对i的引用,然后函数执行时i已经变成了10 function f1() { for (var i = 0; i < 10; i++) { setTimeout(function() { console.log(i) }, 1000); } } f1(); 2. 第二个function中e 有 i的拷贝(1到10的拷贝) // 解决以上的问题可以采用自执行的匿名

javascript数组浅谈1

最近心血来潮要开始玩博客了,刚好也在看数组这块内容,第一篇就只好拿数组开刀了,自己总结的,有什么不对的地方还请批评指正,还有什么没写到的方面也可以提出来我进行完善,谢谢~~ 首先,大概说说数组的基本用法. 数组,即Array类型,是开发中最常用的类型之一,javascript中的数组和其他语言最大的区别就是每一项可以保存任何类型的数据,而且数组的大小是可以动态调整的,有点绕?看看代码吧 1.数组的创建: var arr=new Array(20); var arr1=["小伞",1,t

javascript数组浅谈2

上次说了数组元素的增删,的这次说说数组的一些操作方法 join()方法: 1 var arr=[1,2,3] 2 arr.join("_") //1_2_3 join方法会返回一个由数组中每个值的字符串形式拼接而成的一个以join方法参数为连接符的字符串,join方法参数如果为空则以逗号连接,和toString()方法得到的值相似. reverse()方法: 1 var arr=[2,1,3]; 2 arr.reverse(); //arr=[3,1,2] reverse()方法会使数

javascript数组浅谈3

前两节说了数组最基本的创建,队列方法,排序和一些操作方法,这节说说迭代和归并方法. every()方法 & some()方法 这两个方法会对数组中的每一项运行给定函数,然后返回一个布尔值,理解起来可以当成everyone和someone的区别来理解,看个例子就好了: var arr=[1,2,3,4,5]; var everyResult=arr.every(function(item,index,arr){ return (item>2); }) //false var someResul

Js之浅谈dom操作

JavaScript之浅谈dom操作 1.理解dom: DOM(Document Object Model ,文档对象模型)一种独立于语言,用于操作xml,html文档的应用编程接口. 怎么说,我从两个角度理解: 对于JavaScript,为了能够使JavaScript操作Html,JavaScript就有了一套自己的dom编程接口. 对于Html,dom使得html形成一棵dom树,类似于一颗家族树一样,一层接一层,子子孙孙. 所以说,有了DOM,在我看来就是相当于JavaScript拿到了钥

浅谈JavaScript闭包

一.背景知识 在介绍闭包之前,我觉得有必要先简单的介绍一些背景知识,如变量的作用域.嵌套函数.垃圾回收机制等概念. 1.作用域 作用域是程序运行时变量可被访问的范围,定义在函数内的的变量是局部变量,局部变量的作用域只能是函数内部范围内,它不能在函数外引用.定义在模块最外层的的变量是全局变量,它是全局范围内可见的,当然在函数里面也可以读取到全局变量的. var a = 123; //全局变量 function fun(){ var b = 456; //局部变量 } 2.嵌套函数 函数不仅可以定义

浅谈javascript单体【读javascript设计模式第五章节单体有感】

单体,整个运行环境就独有一份,最简单的一种单体就是一个把所有属性和方法都集中在一起的对象,区别于一般的字面量对象,一般字面量对象是对一个物体的描述,集合该物体所具有的一些属性和方法,而单体则包含更多的些逻辑在里面,单体的好处有,划分命名空间,如果用来作为网页包装器,可以使得页面所有变量都封装在一个对象里,大幅度减小网页里的全局变量, 代码如: common.js (function(wz){ $.extend({ init:function(){ var self = this; this.bi