首页 > 技术文章 > JavaScript难点系列(一):内存空间

Q-Zhan 2017-11-13 11:51 原文

深入了解js这门语言后,才发现它有着诸多众所周知的难点(例如:闭包、原型链、内存空间等)。有的是因为js的设计缺陷导致的,而有的则是js的优点。不管如何,总需要去学会它们,在学习过程中我觉得只看别人的文章并不能做到深刻理解,所以我决定写这一系列的文章来记录我所学习到的知识点,也方便自己以后回顾,如有写错的地方欢迎指正。
废话不多说,马上进入正题!

一、基本数据类型和引用类型

S中有五种基本数据类型:Undefined、Null、Boolean、Number和String。这些基本数据类型是存储在栈内存空间内的,我们可以直接操作栈内存中的值,称之为按值访问。这个栈内存类似于数据结构中的栈,也是遵循后进先出的原则。
除了基本数据类型,JS还有引用类型。引用类型的值是保存在内存中的对象,JS不能直接操作对象的内存空间,也就是说我们在操作对象时操作的是对象的引用而不是实际的对象。为此,引用类型的值是按引用访问的。
两者之间的存储方式不同导致了复制时会产生不同的影响。
这里写图片描述

如上图,把num1的值赋值给num2后,num1和num2的值是相互独立互不影响的。而当从一个变量向另一个变量复制引用类型的值时,复制的值实际上是一个指针。复制结束后两个变量的指针将引用存储在堆内存中的同一个对象。因此改变其中一个变量会影响到另一个变量。

这里写图片描述

二、内存空间管理

JS具有自动垃圾收集机制,垃圾收集器会按照一定的时间间隔周期性地执行回收内存的操作。

而JS的内存生命周期分为三个周期:

1.分配需要的内存
2.使用分配到的内存进行操作
3.使用完毕后被回收

用一个变量num的创建销毁来举例子

var num = 20; // 给变量num分配内存空间
console.log(num + 1); // 使用num变量
num = null; // 使用后释放内存空间

JS最常用的垃圾收集方式是标记清除(mark-and-sweep)。当局部变量的环境执行完毕后,局部变量就离开了其执行环境,这时它就会被自动地打上标记,等到下个垃圾收集操作时间到来时垃圾收集器就会回收变量占用的内存空间。对于全局变量,我们也可以像上例所示把null赋值给num,这样num就被手动地打上标记而被垃圾收集器回收。

三、内存泄露

所谓内存泄露就是指一些错误的使用导致其占用的内存空间即使不再使用也无法被正常回收。常见的内存泄露有四种:
1.多余的全局变量

function func() {
    num = 1;
    console.log(num);
}
func();
//如果没有手动释放,num在函数调用完后仍然存在,导致了内存泄露。
// num = null;

2.遗忘的定时器

function func() {
    var timer = setInterval(function(){
        console.log("123");
    },1000);
}
func();
func = null;
// 即使释放了func的内存空间,定时器也会继续执行。必须手动释放定时器
// clearInterval(timer);

3.没有清理的DOM引用

var elements = {
    button: document.getElementById("button")
};
document.body.removeChild(document.getElementById("button"));
//尽管我们移除了button元素,但elements对象里还保存着#button的引用。所以button元素所占用的内存空间不会被垃圾收集器回收。

4.闭包

var leaks = function(){  
    var leak = 'xxxxxx'; // 被闭包所引用,不会被回收
    return function(){
        console.log(leak);
    }
};
leaks();

推荐阅读