堆栈
堆(heap)和栈(stack)
堆: 为程序员动态分配的内存,大小不定也不会自动释放,特点是先进先出;
栈: 为自动分配的空间,它由系统自动释放,特点是后进后出。
基本数据类型存放于栈内存中,Undefined Null String Number Boolean,它们是直接按值存放的,可以直接访问。
引用数据类型存放于堆内存中,变量只是保存的一个指针,该指针指向堆内存中的地址,当访问引用类型数据(Array、Object、Function等)时,先从栈中获得该对象的指针,再从堆中取出对象的数据。
值传递和地址传递
var a = 10;
var b = a;
b = 20;
console.log(a,b);
//以上的代码修改 b 的值并不会影响 a 的值
var a = [1,2,3,4];
var b = a;
var c = a[0];
console.log(b);
console.log(c);
b[0] = 9;
c = 10;
console.log(a);//[9,2,3,4]
//以上代码可以看出 当改变 b 中的数据时,a 中的数据也发生了变化;改变 c 的 数据时,a 不会受影响
//a 是数组,属引用类型,当将 a 赋值给 a 时,传递的是栈中的地址,而不是堆内存中的对象。
//而 c 只是从 a 堆内存中获取的一个数据值,保存于栈中。修改 c 时,是在栈中直接修改
深拷贝与浅拷贝
在定义引用数据类型时,变量存放的只是一个地址。当使用对象拷贝,传递的也只是一个地址。因此在访问拷贝对象属性时,会根据地址找到源对象指向的堆内存中。
浅拷贝
var obj1 = {
name : "zhar",
desc : ["北京"]
}
function copy(o1){
var newObj = {};
for(var key in o1){
newObj[key] = o1[key];
}
return newObj;
}
// var obj2 = obj1;
var obj2 = copy(obj1);
obj2.name = "tom";
obj2.desc.push("昌平");
console.log(obj1);//{ name: 'zhar', desc: [ '北京', '昌平' ] }
深拷贝
var obj1 = {
name : "zhar",
desc : ["北京"]
}
function copy(obj,target){
var newObj = target || {};
console.log(obj)
for(var key in obj){
//判断是不是引用数据类型,即是不是对象
if(typeof obj[key] === "object"){
newObj[key] = (obj[key].constructor===Array)?[]:{};
//第二次调用时,要传入target即newObj[key],newObj[key]要重复使用
copy(obj[key],newObj[key]);
}else{
newObj[key] = obj[key];
}
}
return newObj;
}
var obj2 = copy(obj1);
深拷贝就是为了解决浅拷贝时数据互相影响而存在的
垃圾回收
Javascript 具有自动垃圾回收机制,执行环境会管理代码执行过程中使用的内存。
使我们不必像 C 或 C++开发者那样,手动去管理内存的释放。
自动回收机制原理:
垃圾回收器按照固定的时间间隔找出不再继续使用的变量,然后释放其占用的内存。
两种策略:
- 标记清除(现在浏览器都在使用)
最常用的垃圾回收方式。当变量进入环境时,将变量标记为"进入环境";当变量离开环境时,将其标记为"离开环境"。
到2008年,各浏览器使用的清除策略都是标记清除,差别在于时间间隔不同 - 引用计数(已被弃用)
引用计数是跟踪每个值被引用的次数;当有一个变量被引用时,则这个值的引用次数加1,当取消一个引用时,次数减1。当引用值变为0 时,将由垃圾收集器回收
引用计数方式有严重的问题,就是,当变量相互引用时,如:
var obj1 = {}
var obj2 = {}
obj1.a = obj2;
obj2.b = obj1;
对于上面的代码,存在相互引用,其引用计数永远为2,就会导致对象永远不会被回收。
前端程序员最简单粗暴的释放内存的方式:
obj = null;