码迷,mamicode.com
首页 > 编程语言 > 详细

javascript中的变量、作用域和内存问题

时间:2015-01-31 12:00:31      阅读:335      评论:0      收藏:0      [点我收藏+]

标签:

 

 

 

1、变量

变量的值的类型:基本类型值和引用类型值两种。

基本类型:UndefinedNullBooleanStringNumber,这五类基本数据类型的值在内存中占有固定大小的空间,因此保存在栈内存(存放简单数据)中。

引用类型值:是指存放在堆内存中的对象,占用的大小不固定,变量中保存的只是指向对象的一个指针(内存中的一个存储位置,这个位置上存储的是具体的某个对象),指针即内存地址的大小是固定的,因此指针位置可以存储在栈内存中,但具体的对象存储在堆内存中。

基本类型值是“按值访问”,引用类型值是“按引用访问”。

 

2、复制变量值

复制基本类型值和引用类型值存在差异。

基本类型值:从一个变量向另一个变量复制基本类型值,会将该值复制一份到新变量的位置。因此,两个变量是互不相关的,操作其中一个不会影响另一个。

 

Var num=10;

Var num2=num;  

 

 

引用类型值:修改其中一个会影响另一个,两者指向的是同一个对象。

Var obj1=new Object();

Var obj2=obj1;

 

               栈内存

 

 

 

 

 

 

 

3、传递参数

ECMAScript中所有函数的参数都是按值传递,即把函数外的值复制给函数内的的参数。基本类型和引用类型的值传递与基本类型和引用类型变量的传递一样(如上)。变量访问时由按值访问和按引用两种,而参数传递只能是按值传递。

如 function setName(obj){

Obj.name=’hello’;   

Obj=new Object();

Obj.name=’world’;

}

Var c=new Object();

setName(c);

Alert(c.name); //hello

如果是按引用传递的话,obj重新指向一个对象后,会将c也指向新的obj,那么cname属性应该是world

 

4、垃圾收集

Javascript采用的是标记-清除策略

 

三种最基本的垃圾回收算法,

引用计数(Reference Counting)算法:这个可能是最早想到的方法。形象点说,引用计数可以这么理解,房子里放了很多白纸,这些纸就好比是内存。使用内存,就好比在这些纸上写字。内存可以随便使用,但是,有个条件,任何使用一张纸的人,必须在纸的一角写上计数1,如果2个人同时使用一张纸,那么计数就变成2,以此类推。当一个人使用完某张纸的时候,必须把角上的计数减1,这样,一旦当计数变为0,就满足了垃圾回收条件,等在一旁的机器人会立即把这张纸扔进垃圾箱。基于引用计数器的垃圾收集器运行较快,不会长时间中断程序执行,适宜地必须 实时运行的程序。但引用计数器增加了程序执行的开销;同时,还有个最大的问题,这个算法存在一个缺陷,就是一旦产生循环引用,内存就会被泄露。举个例子,我们new2个对象ab,这时,ab的计数都是1,然后,我们把a的一个属性指向bb的一个属性指向a,此时,由于引用的关系,ab的计数都变成了2,当程序运行结束时,退出作用域,程序自动把a的计数减1,由于最后a的计数仍然为1,因此,a不会被释放,同样,b最后的计数也为1b也不会被释放,内存就这么泄露了!

 

标记-清除(Mark-Sweep)算法:同样是房间和白纸的例子,这次规则有所修改。白纸仍然随便用,并且,一开始,不需要做什么记号,但是用到某个时候,机器人会突然命令所有人停下来,这时,需要每个人在自己仍然需要使用的白纸上做一个记号,大家都做完记号后,机器人会把那些没有记号的白纸全部扔进垃圾箱。正如其名称所暗示的那样,标记-清除算法的执行过程分为“标记”和“清除”两大阶段。这种分步执行的思路奠定了现代垃圾收集算法的思想基础。与引用计数算法不同的是,标记-清除算法不需要运行环境监测每一次内存分配和指针操作,而只要在“标记”阶段中跟踪每一个指针变量的指向――用类似思路实现的垃圾收集器也常被后人统称为跟踪收集器( Tracing Collector )。当然,标记-清楚算法的缺陷也很明显,首先是效率问题,为了标记,必须暂停程序,长时间进行等待,其次,标记清除算法会造成内存碎片,比如被标记清除的只是一些很小的内存块,而我们接下来要申请的都是一些大块的内存,那么刚才清除掉的内存,其实还是无法使用。解决方案,常见的有2种,一是清楚后对内存进行复制整理,就像磁盘整理程序那样,把所有还在使用的内存移到一起,把释放掉的内存移到一起

第二种方案是不移动内存,而是按大小分类,建立一系链表,把这些碎片按大小连接并管理起来,(4个字节的内存一个链表,8个字节的内存一个链表……)如果我们需要4个字节的内存,就从4个字节的链表里面去取,需要16个字节,就从16字节的链表里面去取,只有到了一定时候,比如程序空闲或者大块的内存空间不足,才会去整理合并这些碎片。

iejavascript的垃圾回收,采用的就是这种算法。

 

复制(copying)算法mark-sweep算法效率低下,由此,又产生了一种新的奇思妙想,我们再把规则换一下:还是房间和白纸的例子,这次我们把房间分成左右2部分,一开始,所有人都在左边,白纸仍然随便用,一定时候,机器人又会叫大家停下来,这次不做记号了,你只要带着你还需要的白纸转移到右边去就可以了(相当于把现有的程序复制一份,无法使用的部分自然不会被复制),那些没用的纸自然就剩了下来,然后机器人会把左边所有的垃圾打扫干净(相当于把原先使用的那一半内存直接清空),下次执行垃圾回收的时候采用同样的方式,只不过这次从右边向左边迁移。这种算法的效率奇高,可惜,对内存的消耗太大,尤其是在1960年,内存可比黄金贵多了,直接砍掉一半的内存,显然是无法接受的。

 

了解万垃圾回收算法,再来看看IE下为什么会产生内存泄露。

 

IE 6中,对于javascript object内部,javascript使用的是mark-and-sweep算法,这点前面也有提到,因此,纯粹的javascript对象的使用,不会造成内存泄露,但是对于javascript object与外部object(包括native objectvbscript object等等)的引用时,IE 6使用引用计数,这样一来,内存泄露就产生了。这点在犀牛书第八章函数部分有提到。

以下是常见的几种javascript内存泄露的情况:

 

一、循环引用:

 

  <html>

     <head>

         < script language ="JScript">

         var  myGlobalObject;

         function  SetupLeak()  // 产生循环引用,因此会造成内存泄露

        {

             //  First set up the script scope to element reference

            myGlobalObject  = document.getElementById("LeakedDiv");

             //  Next set up the element to script scope reference

            document.getElementById("LeakedDiv").expandoProperty  =  myGlobalObject;

        }

         

     </head>

     <body onload = "SetupLeak()">

         <div id ="LeakedDiv" ></div>

     </body>

</html>

我们可以看到,myGlobalObject指向了一个DOM对象,而这个DOM对象的一个属性又指向了myGlobalObject,循环引用出现,内存泄露,其原理如下:

 

解决方案很简单,在确保属性不再使用后,加入以下代码就可以了:

 

function  BreakLeak(){  // 解开循环引用,解决内存泄露问题

          document.getElementById( " LeakedDiv " ).expandoProperty  =  null ;

}

说起来容易,不过当我们程序非常复杂的时候,发现和修改就没有这么容易了。

 

 

javascript中的变量、作用域和内存问题

标签:

原文地址:http://www.cnblogs.com/lydialee/p/4263813.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!