垃圾收集机制
参考javascript高级程序设计和博客谈谈JS垃圾回收机制
JavaScript引擎中有一个后台进程称为垃圾回收器,它监视所有对象,并删除那些不可访问的对象。
设置为null解除引用:
var user = { a: 1 };
user = null; // 这样就切断变量和对象的联系,可以被释放
-
垃圾:
一般来说没有被引用的对象就是垃圾,就是要被清除。如果几个对象引用形成一个环,互相引用,但根访问不到它们,这几个对象也是垃圾,要被清除。
垃圾回收方法: -
标记清除:
JavaScript中最重用的垃圾收集方式是标记清除。
当运行一个函数的时候,就是当变量进入环境时,就将这个变量标记为“进入环境”。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为只要执行流进入相应的环境,就可能会用到它们。而当变量离开环境时,则将其标记为“离开环境”。首先,垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记。(即认为所有对象都是要清除的)
然后,它会去掉环境中的变量(比如函数内的局部变量)以及被环境中的变量引用的变量的标记。剩下的就是没有在使用的对象,可以被垃圾回收了。
在此之后,再被加上标记的变量将被视为准备删除的变量。
最后,垃圾收集器完成内存清除工作,销毁那些带标记的值并回收它们所占用的内存空间。 -
引用计数
记录每个对象被引用的次数,每次新建对象、赋值引用和删除引用的同时更新计数器,如果计数器值为0则直接回收内存。
优点:可即刻回收垃圾
缺点:计数器的增减处理繁重;循环引用无法回收
var fn1 = {
name: 'Dell'
};// 标记为1
var fn2 = fn1; // 标记为2
fn1 = {}; // 标记为2-1=1
fn2 = {}; // 标记为1-1=0,fn1可以被回收了
极端的情况:
循环引用
function fn(){
var a = {};
var b = {};
a.sub = b; // a指向的对象标记为2
b.sub = a; // b指向的对象标记为2
}
fn();
// 执行fn()之后,a、b指向的对象标记减1,并不为0,无法回收
假如这个函数被重复多次调用,就会导致大量内存得不到回收。为此放弃了引用计数方式,转而采用标记清除来实现其垃圾收集机制。可是,引用计数导致的麻烦依然存在。
IE 中有一部分对象并不是原生JavaScript对象。例如,其 BOM和DOM中的对象就是使用 C++以 COM(Component Object Model,组件对象模型)对象的形式实现的,而 COM对象的垃圾 收集机制采用的就是引用计数策略。
var element = document.getElementById("some_element");
var myObject = new Object();
// 建立了循环引用
myObject.element = element;
element.someObject = myObject;
由于存在这个循环引用,即使将DOM元素(element)从页面中移除,由于myObject.element指向它,它不会被回收。
为了避免类似这样的循环引用问题,最好是在不使用它们的时候手工断开原生JavaScript对象与DOM元素之间的连接:
myObject.element = null;
element.someObject = null;
为了解决上述问题,IE9把BOM和DOM对象都转换成了真正的 JavaScript对象。
闭包
闭包是指有权访问另一个函数作用域中的变量的函数。常见的创建闭包方式,就是在一个函数内部创建另一个函数。
// 闭包
function add(y,z){
var x = 1; // 局部变量
var add2 = function(){
console.log(x+y+z);
}
return add2();
}
// 核心是add2()函数不接受参数,它使用的值是从执行环境中获取的
add(2,3); // 6
优点:有利于封装,可以访问局部变量。
缺点:容易产生内存泄漏。
关键词
function Preson(){
this.name = "Nicholas";
this.age = 29;
this.job = "Software Engineer";
}
var person1 = new Person();
1.instanceof
console.log(person1 instanceof Person);// true
console.log(person1 instanceof Object);// true
// 所有对象本质上都是继承于Object
2.delete
delete person1.job;
用于删除对象的属性,但是对方法不起作用;不能删除原型链中的属性和变量;不能删除变量
3.call、apply
每个函数都包含两个非继承而来的方法:apply()和call()。这两个方法的用途都是在特定的作用域中调用函数,实际上等于设置函数体内this对象的值。
apply()方法接收两个参数:在其中运行函数的作用域,参数数组。
call()方法与apply()方法的作用相同,区别仅在于接收参数的方式不同,在使用call()方法时,传递给函数的参数必须逐个列举出来。
function add(x,y){
console.log(x+y);
}
function sub(x,y){
console.log(x-y);
}
sub.apply(add,[2,1]); // 1 add调用sub
sub.call(add,2,1); // 1 add调用sub
4.arguments
arguments是一个类数组对象,包含着传入函数中的所有参数,主要用途是保存函数参数。在函数代码中,使用特殊对象arguments,无需明确指出参数名就能访问它们。
function arg(x,y,z){
console.log(arguments[0],arguments[1],arguments[2]);
console.log(arguments.length);
}
arg(1,2,3);
// 1 2 3
// 3
arguments.length:arguments的一个属性,指向传递给当前函数的参数数量。
argumentns.callee:arguments的一个属性,是一个指向当前执行的函数的指针。