标签:
//C语言 #include <stdio.h> void main() { int i=2; i--; if(i) { int j=3; } printf("%d/n",j); // j 未定义 }
var a = 0; function test() { var c = 1; alert(b);// 会创建b 这个全局变量 alert(a);// 可以访问a } alert(c);// undefined 不能访问c
var res = []; function test() { for(var i = 0; i < 3; i++) {// 我想点击 res[i] = function () { alert(i); } } } res[0]();// 会一直输出2 , 因为i 一直在内存中, 没被回收 res[1]();// 会一直输出2 , 因为i 一直在内存中, 没被回收 res[2]();// 会一直输出2 , 因为i 一直在内存中, 没被回收
function test(){ (function (){ for(var i=0;i<4;i++){ } })(window); // 这里可以往闭包里面传参 alert(i); // 未定义, 因为i已经被销毁了, 为啥, 因为函数作用域呗(不能访问里面), 这是个匿名函数 } test();
(function (){ //内容 })(window, window.document .....);
var res = []; function test() { for(var i = 0; i < 3; i++) {// 我想点击 (function (j) { res[j] = function () { alert(j); } })(i);// 函数结束, j 已经自动回收了, i 只是个参数 } } res[0]();// 0 res[1]();// 1 res[2]();// 2
一, 引用计数 reference counting: 这个可能是最早想到的方法。形象点说,引用计数可以这么理解,房子里放了很多白纸,这些纸就好比是内存。使用内存,就好比在这些纸上写字。内存可以随便使用,但是,有个条件,任何使用一张纸的人,必须在纸的一角写上计数1,如果2个人同时使用一张纸,那么计数就变成2,以此类推。当一个人使用完某张纸的时候,必须把角上的计数减1,这样,一旦当计数变为0,就满足了垃圾回收条件,等在一旁的机器人会立即把这张纸扔进垃圾箱。基于引用计数器的垃圾收集器运行较快,不会长时间中断程序执行,适宜地必须实时运行的程序。但引用计数器增加了程序执行的开销;同时,还有个最大的问题,这个算法存在一个缺陷,就是一旦产生循环引用,内存就会被泄露。举个例子,我们new了2个对象a和b,这时,a和b的计数都是1,然后,我们把a的一个属性指向b,b的一个属性指向a,此时,由于引用的关系,a和b的计数都变成了2,当程序运行结束时,退出作用域,程序自动把a的计数减1,由于最后a的计数仍然为1,因此,a不会被释放,同样,b最后的计数也为1,b也不会被释放,内存就这么泄露了!当JavaScript代码生成一个新的内存驻留项时(比如一个对象或函数),系统就会为这个项留出一块内存空间。因为这个对象可能会被传递给很多函数,并且会被指定给很多变量,所以很多代码都会指向这个对象的内存空间。JavaScript会跟踪这些指针,当最后一个指针废弃不用时,这个对象占用的内存会被释放。A ---------> B ------------> C例如对象A有一个属性指向B,而B也有一个属性指向C。即使当前作用域中只有对象A有效,但由于指针的关系所有3个对象都必须保留在内存中。当离开A的当前作用域时(例如代码执行到声明A的函数的末尾处),垃圾收集器就可以释放A占用的内存。此时,由于没有什么指向B,因此B可以释放,最后,C也可以释放。然而,当对象间的引用关系变得复杂时,处理起来也会更加困难。A ---------> B ------------> C^、_ _ _ _ _ _ _|这里,我们又为对象C添加了一个引用B的属性。在这种情况下,当A释放时,仍然有来自C的指针指向B。
function leak() {// 循环引用例子 var a = new Object(); var b = new Object(); a.attr1 = b;// a 包含 b 的指针 b.attr2 = a;// b 包含 a 的指针 }
优点: 实现简单缺点: 容易产生循环引用, 内存泄漏的情况
二, 标记清除 mark sweep: 同样是房间和白纸的例子,这次规则有所修改。白纸仍然随便用,并且,一开始,不需要做什么记号,但是用到某个时候,机器人会突然命令所有人停下来,这时,需要每个人在自己仍然需要使用的白纸上做一个记号,大家都做完记号后,机器人会把那些没有记号的白纸全部扔进垃圾箱。正如其名称所暗示的那样,标记-清除算法的执行过程分为“标记”和“清除”两大阶段。这种分步执行的思路奠定了现代垃圾收集算法的思想基础。与引用计数算法不同的是,标记-清除算法不需要运行环境监测每一次内存分配和指针操作,而只要在“标记”阶段中跟踪每一个指针变量的指向——用类似思路实现的垃圾收集器也常被后人统称为跟踪收集器( Tracing Collector )。当然,标记-清楚算法的缺陷也很明显,首先是效率问题,为了标记,必须暂停程序,长时间进行等待,其次,标记清除算法会造成内存碎片,比如被标记清除的只是一些很小的内存块,而我们接下来要申请的都是一些大块的内存,那么刚才清除掉的内存,其实还是无法使用。解决方案,常见的有2种,一是清楚后对内存进行复制整理,就像磁盘整理程序那样,把所有还在使用的内存移到一起,把释放掉的内存移到一起,如图:
但是,这样一来效率就更低了。第二种方案是不移动内存,而是按大小分类,建立一系链表,把这些碎片按大小连接并管理起来,(4个字节的内存一个链表,8个字节的内存一个链表……)如果我们需要4个字节的内存,就从4个字节的链表里面去取,需要16个字节,就从16字节的链表里面去取,只有到了一定时候,比如程序空闲或者大块的内存空间不足,才会去整理合并这些碎片。为什么重点谈mark-sweep算法呢,主要是ie对javascript的垃圾回收,采用的就是这种算法。
三, 复制copying:mark-sweep算法效率低下,由此,又产生了一种新的奇思妙想,我们再把规则换一下:还是房间和白纸的例子,这次我们把房间分成左右2部分,一开始,所有人都在左边,白纸仍然随便用,一定时候,机器人又会叫大家停下来,这次不做记号了,你只要带着你还需要的白纸转移到右边去就可以了(相当于把现有的程序复制一份,无法使用的部分自然不会被复制),那些没用的纸自然就剩了下来,然后机器人会把左边所有的垃圾打扫干净(相当于把原先使用的那一半内存直接清空),下次执行垃圾回收的时候采用同样的方式,只不过这次从右边向左边迁移。这种算法的效率奇高,可惜,对内存的消耗太大,尤其是在1960年,内存可比黄金贵多了,直接砍掉一半的内存,显然是无法接受的。优点: 算法高效缺点: 内存消耗太大
function outerFn() { var outerVar = {}; function innerFn() { console.log(‘hello‘); } outerVar.fn = innerFn;// 造成循环引用, 因为 innerFn 引用了 outerVar return innerFn;// 由于返回了内部函数innerFn,而 innerFn 是个闭包, 会读取整个outerFn的内容不释放 }
$(document).ready(function() { var button = document.getElementById(‘button-1‘); button.onclick = function() {// 内部函数可以读取外部函数的所有资源, 包括 button, 所以循环引用了 console.log(‘hello‘); }; });
function hello() { console.log(‘hello‘); } $(document).ready(function() { var button = document.getElementById(‘button-1‘); button.onclick = hello;// 这个就不是内部函数了, 也就不是闭包 });
$(document).ready(function() { var button = document.getElementById(‘button-1‘); button.onclick = function() {// 内部函数可以读取外部函数的所有资源, 包括 button, 所以循环引用了 console.log(‘hello‘); this.onclick = null;// 在不再使用的时候把它设为null, 如本例只需要点击一次 }; });
$(document).ready(function() { var $button = $(‘#button-1‘); $button.click(function(event) { event.preventDefault(); console.log(‘hello‘); }); });
标签:
原文地址:http://www.cnblogs.com/haili042/p/4940169.html