资讯专栏INFORMATION COLUMN

内存管理与垃圾回收

rockswang / 3434人阅读

摘要:的内存管理是那些被称作垃圾回收语言当中的一员。垃圾回收与内存泄漏垃圾回收,简称。反例循环多次触发,效率太低在旧的浏览器上会导致内存泄漏正解绑定事件的元素是不能在时被清理的,应该在之前取消事件绑定。

首先我们需要理解,内存是什么。简单来讲,内存存储了计算机运行过程的需要的全部数据,也就是计算机正在使用的全部数据。我们需要合理的使用内存,防止内存被大量无用数据占用,同时也要防止访问和修改与当前程序无关的内存区域。内存主要包括以下几个部分: 内核数据区域,栈区,共享库映像,堆区,可读写区域,只读区域。学习javascript,我们不需要理解内存和cache,内存和I/O之间具体工作原理,但我们需要了解掌握如何合理的使用内存,合理的分配释放内存。

javascript的内存管理

Javascript 是那些被称作垃圾回收语言当中的一员。垃圾回收语言通过周期性地检查那些之前被分配出去的内存是否可以从应用的其他部分访问来帮助开发者管理内存。换句话说,当计算机发现有的内存已经不能被访问到了,就会把它们标记为垃圾。开发者只需要知道一块已分配的内存是否会在将来被使用,而不可访问的内存可以通过算法确定并标记以便返还给操作系统。

引用传递和值传递

js中的变量除了6个基本类型以外,其余的都是对象。也就说基本类型在赋值是传递的是值,也就是原来数据的一份拷贝。基本类型包括number、string、boolean、symbol、null、undefined.
用2个例子来理解一下:

值传递
var a = 10;  //基本类型
var b = a;   //a把10拷贝一份,把这个拷贝给b
a = 20;  //修改了a,不影响a的拷贝
console.log(a);  //20
console.log(b);  //10
引用传递
var a = {num: 20};  //不是基本类型
var b = a;   //这里没有任何拷贝工作,b指向和a完全一致的同一块内存
b.num = 15;  //由于b和a指向同一块内存,所以b.num修改了等同于a.num修改了
console.log(a.num); //15
console.log(b.num); //15

//进一步理解
b = {age: 10};  //等号右边定义了一个新的对象,产生的新的内存分配,此时b指向了这块新的内存,a还是指向原来那块内存
console.log(a);  //{num: 15}
console.log(b);  //{age: 10}

值得强调的是:在函数参数传递的时候和返回值时一样遵守这个传递规则,这是构成闭包的基础条件

简单的函数传递参数
//一个反例
var num1 = 10;
var num2 = 20;
function swap(a, b){
    var temp = a;
    a = b;
    b = temp;
}
swap(num1, num2);
console.log(num1);  //10
console.log(num2);  //20

以上代码不能如愿的把2个传入变量的值交换,因为基本类型在参数传递时也是值传递,及a,b是num1,num2的拷贝,不是num1和num2本身。当然实现交换的方法很多,在不引入第三个变量情况下,不用多带带写一个函数。

//实现交换a,b
//方法1:
var temp = a;
a = b;
b =temp;

//方法2:
a = a + b;
b = a - b;
a = a - b;

//方法3:
a = [b, b = a][0];

//方法4(仅适用于整数交换):
a = a ^ b; // ^表示异或运算
b = a ^ b; 
a = a ^ b; 

//方法5:
[a, b] = [b, a]; //解构赋值
闭包的原理
var inc = function(){
    var x = 0;
    return function(){  //返回一个非基本类型
        console.log(x++);
    };
};
inc1 = inc();  //inc1是闭包内匿名函数的引用,由于该引用存在,匿名函数引用计数不为0,所以inc作用域对应的内存不能释放,闭包形成
inc1();  //0
inc1();  //1
浅拷贝与深拷贝

当对象的属性是对象的时候,简单地赋值导致改属性传递的是另一个对象属性的引用,这样的拷贝是浅拷贝,存在安全风险。我们应该递归的拷贝对象属性的每个对象,形成深拷贝。方法如下:

//浅拷贝与深拷贝
var o = {
    name: "Lily",
    age: 10,
    addr:{
        city: "Shenzheng",
        province: "Guangdong"
    },
    schools: ["primaryS", "middleS", "heightS"]
};
var newOne = copy(o);
console.log(o);   //Object {name: "Lily", age: 10, addr: Object}
console.log(newOne);   //Object {name: "Lily", age: 10, addr: Object}

newOne.name = "Bob";
console.log(newOne.name);  //"Bob"
console.log(o.name);   //"Lily"

newOne.addr.city = "Foshan";
console.log(newOne.addr.city);   //"Foshan"
console.log(o.addr.city);   //"Foshan"

function copy(obj){
    var obj = obj || {};
    var newObj = {};
    for(prop in obj){
        if(!obj.hasOwnProperty(prop)) continue;
        newObj[prop] = obj[prop];  //当obj[prop]不是基本类型的时候,这里传的时引用
    }
    return newObj;
}

var newOne = deepCopy(o);
console.log(o);   //Object {name: "Lily", age: 10, addr: Object}
console.log(newOne);   //Object {name: "Lily", age: 10, addr: Object}

newOne.name = "Bob";
console.log(newOne.name);  //"Bob"
console.log(o.name);   //"Lily"

newOne.addr.city = "Foshan";
console.log(newOne.addr.city);   //"Foshan"
console.log(o.addr.city);   //"Shenzheng"

newOne.schools[0] = "primatrySchool";
console.log(newOne.schools[0]);   //"primatrySchool"
console.log(o.schools[0]);   //"primatryS"

function deepCopy(obj){
    var obj = obj || {};
    var newObj = {};
    deeply(obj, newObj);
    
    function deeply(oldOne, newOne){
        for(var prop in oldOne){
            if(!oldOne.hasOwnProperty(prop)) continue;
            if(typeof oldOne[prop] === "object" && oldOne[prop] !== null){
                newOne[prop] = oldOne[prop].constructor === Array ? [] : {};
                deeply(oldOne[prop], newOne[prop]);
            }
            else
                newOne[prop] = oldOne[prop];
        }
    }
    return newObj;
}
变量定义和内存释放

不同的变量定义方式,会导致变量不能被删除,内存无法释放。

// 定义三个全局变量
var global_var = 1;
global_novar = 2; // 反面教材
(function () {
    global_fromfunc = 3; // 反面教材
}());

// 试图删除
delete global_var; // false
delete global_novar; // true
delete global_fromfunc; // true

// 测试该删除
typeof global_var; // "number"
typeof global_novar; // "undefined"
typeof global_fromfunc; // "undefined"

很明显,通过var定义的变量无法被释放。

垃圾回收与内存泄漏

垃圾回收(Garbage Collection),简称GC。简单来讲,GC就是把内存中不需要的数据释放了,这样这部分内存就可以存放其他东西了。在javascript中,如果一个对象不再被引用,那么这个对象就会被GC回收。具体回收策略包括以下3种:

标记回收

当从window节点遍历DOM树不能遍历到某个对象,那么这个对象就会被标记为没用的对象。由于回收机制是周期性执行的,这样,当下一个回收周期到来时,这个对象对应的内存就会被释放。

引用计数

当系统中定义了一个对象后,对于这一块内存,javascript会记录有多少个引用指向个部分内存,如果这个数为零,则这部分内存会在下一个回收周期被释放。

手动释放

就好比上一个例子中,利用delete关键字删除变量或属性,达到释放内存的目的。分一下几种情况:

//释放一个对象
obj = null;

//释放是个对象属性
delete obj.propertyName;
delete globalVariable;  //没有用var声明的变量是window的属性,用delete释放。

//释放数组
array.length = 0;

//释放数组元素
array.splice(2,2);  //删除并释放第三个元素起的2个元素

不过需要注意的是, 这几个GC策略是同时作用的:

var o1 = {};  //开辟一块内存放置对象,并用o1指向它
var o2 = o1;  //o2指向与o1同一个内存区域
console.log(o1);  //{}
console.log(o2);  //{}
o2 = null;  //标记o2为没用的对象
console.log(o2);  //null
console.log(o1);  //{}  由于还有o1指向这个内存区域,引用计数不为零,所以内存并没有被释放
o1 = null;  //引用计数为0, 内存释放

如果你访问了已经被回收了的内存,会发生不可预计的严重后果。比如一段内存被释放了,可能里面的值就不是原来的值了,你还要拿来用那不是自己找错误吗?更严重的就是你修改了其他程序的数据!!!我们将这样的变量叫做野指针(wild pointer)。为了避免这样的也只能出现,也为了节省计算机资源,我们需要防止内存泄露(memory leak)。

内存泄漏也称作存储渗漏,用动态存储分配函数动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元,直到程序结束。简单来说就是该内存空间使用完毕之后未回收。

内存泄露是每个开发者最终都不得不面对的问题。即便使用自动内存管理的语言,你还是会碰到一些内存泄漏的情况。内存泄露会导致一系列问题,比如:运行缓慢,崩溃,高延迟,甚至一些与其他应用相关的问题。

可能导致内存泄漏的操作

清除所以子元素用innerHTML=""替代removeChild(),因为在sIEve中监测的结果是用removeChild无法有效地释放dom节点。

//反例
var parent = document.getElementById("parent");
var first = parent.firstChild();
while(first){  //循环多次触发reflow,效率太低
    parent.removeChild(first);  //在旧的浏览器上会导致内存泄漏
    first = parent.firstChild();
}

//正解
document.getElementById("parent").innerHTML = “”;

绑定事件的元素是不能在remove时被清理的,应该在remove之前取消事件绑定。不过更好的办法是用事件委托的方式绑定事件。

var ele = document.getElementById("eleID");
ele.onclick = function fun(){
    //Do stuff here
}
//...
ele.onclick = null;  //删除元素前取消所有事件,jQuery中也是在删除节点前利用removeEventListen去除了对应事件
ele.parentNode.removeChild(ele);

意外的全局变量,会使得实际函数结束就应该释放的内存保留下来,造成资源浪费,包括以下两种情况:

在严格模式下编写代码可以避免这个问题

//情况一: 函数中没有用var声明的变量
function fun1(){
    name = "Mary";   //全局变量
}
fun1();

//情况二: 构造函数没用new关键字调用
function Person(name){
    this.name = name;
}
Person("Mary");   //函数内定义全局变量

定时器中的变量定义,会在每次执行函数的时候重复定义变量,产生严重的内存泄漏。

//反例
setInterval(function(){
    var ele = document.getElementById("eleID");  //改代码毎100毫秒会重复定义该引用
    //Do stuff
}, 100);

//正解
setInterval(function(){
    var ele = document.getElementById("eleID");  //改代码毎100毫秒会重复定义该引用
    //Do stuff
    ele = null;
}, 100);

如果闭包的作用域链中保存着一个DOM对象或者ActiveX对象,那么就意味着该元素将无法被销毁:

//反例
//不妨认为这里的上下文是window
function init(){
    var el = document.getElementById("MyElement"); //这是一个DOM元素的引用,非基本类型
    el.onclick = function(){  //el.onclick是function匿名函数的引用
         alert(el.innerHTML);  //funciton中访问了这个作用域以外的DOM元素引用el,导致el不能被释放
    }
 }
 init();

 //正解
 function init(){
    var el = document.getElementById("MyElement"); //这是一个DOM元素的引用,是非基本类型
    var text = el.innerHTML; //字符串,是基本类型,解决alert(el.innerHTML)不能正常工作问题
    el.onclick = function(){  //el.onclick是function匿名函数的引用
         alert(text);  //var声明的text是基本类型,不必释放
    }
    el = null;  //手动释放,但会导致alert(el.innerHTML)不能正常工作
 }
 init();

 //如果函数结尾要return el,用以下方法释放el
 //正解
function init(){
    var el = document.getElementById("MyElement"); //这是一个DOM元素的引用,是非基本类型
    var text = el.innerHTML; //字符串,是基本类型,解决alert(el.innerHTML)不能正常工作问题
    el.onclick = function(){  //el.onclick是function匿名函数的引用
         alert(text);  //var声明的text是基本类型,不必释放
    }
    try{
        return el;
    } finally {
        el = null;  //手动释放,但会导致alert(el.innerHTML)不能正常工作
    }
 }
 init();

通过createElement,createTextNode等方法创建的元素会在写入DOM后被释放

function create() {
    var parent = document.getElementById("parent");
    for (var i = 0; i < 5000; i++) {
        var el = document.createElement("div");
        el.innerHTML = "test";
        gc.appendChild(el);  //这里释放了内存
    }
}

循环引用导致引用计数永远不为0,内存无法释放:

//构成一个循环引用
var o1 = {name: "o1"};
var o2 = {name: "o2"};
o1.pro = o2;
o2.pro = o1;

//这种情况需要手动清理内存,在不需要的时候把对象置为null或删除pro属性

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/97463.html

相关文章

  • 《JavaScript 闯关记》之垃圾回收内存管理

    摘要:内存回收此时,局部变量就没有存在的必要了,因此可以释放它们的内存以供将来使用。局部变量会在它们离开执行环境时自动被解除引用,如下面这个例子所示手工解除的引用由于局部变量在函数执行完毕后就离开了其执行环境,因此无需我们显式地去为它解除引用。 JavaScript 具有自动垃圾收集机制(GC:Garbage Collecation),也就是说,执行环境会负责管理代码执行过程中使用的内存。而...

    Sleepy 评论0 收藏0
  • JavaScript深入浅出第3课:什么是垃圾回收算法?

    摘要:摘要是如何回收内存的深入浅出系列深入浅出第课箭头函数中的究竟是什么鬼深入浅出第课函数是一等公民是什么意思呢深入浅出第课什么是垃圾回收算法最近垃圾回收这个话题非常火,大家不能随随便便的扔垃圾了,还得先分类,这样方便对垃圾进行回收再利用。 摘要: JS是如何回收内存的? 《JavaScript深入浅出》系列: JavaScript深入浅出第1课:箭头函数中的this究竟是什么鬼? Jav...

    AaronYuan 评论0 收藏0
  • Java 内存结构备忘录

    摘要:本文详细描述了堆内存模型,垃圾回收算法以及处理内存泄露的最佳方案,并辅之以图表,希望能对理解内存结构有所帮助。该区域也称为内存模型的本地区。在中,内存泄露是指对象已不再使用,但垃圾回收未能将他们视做不使用对象予以回收。 本文详细描述了 Java 堆内存模型,垃圾回收算法以及处理内存泄露的最佳方案,并辅之以图表,希望能对理解 Java 内存结构有所帮助。原文作者 Sumith Puri,...

    wow_worktile 评论0 收藏0
  • Node - 内存管理垃圾回收

    摘要:的内存限制和垃圾回收机制内存限制内存限制一般的后端语言开发中,在基本的内存使用是没有限制的。的内存分代目前没有一种垃圾自动回收算法适用于所有场景,所以的内部采用的其实是两种垃圾回收算法。 前言 从前端思维转变到后端, 有一个很重要的点就是内存管理。以前写前端因为只是在浏览器上运行, 所以对于内存管理一般不怎么需要上心, 但是在服务器端, 则需要斤斤计较内存。 V8的内存限制和垃圾回收机...

    joyqi 评论0 收藏0
  • Node.js内存管理和V8垃圾回收机制

    摘要:垃圾回收内存管理实践先通过一个来看看在中进行垃圾回收的过程是怎样的内存泄漏识别在环境里提供了方法用来查看当前进程内存使用情况,单位为字节中保存的进程占用的内存部分,包括代码本身栈堆。 showImg(https://segmentfault.com/img/remote/1460000019894672?w=640&h=426);作者 | 五月君Node.js 技术栈 | https:...

    JowayYoung 评论0 收藏0
  • 【重温基础】22.内存管理

    摘要:内存泄露内存泄露概念在计算机科学中,内存泄漏指由于疏忽或错误造成程序未能释放已经不再使用的内存。判断内存泄漏,以字段为准。 本文是 重温基础 系列文章的第二十二篇。 今日感受:优化学习方法。 系列目录: 【复习资料】ES6/ES7/ES8/ES9资料整理(个人整理) 【重温基础】1-14篇 【重温基础】15.JS对象介绍 【重温基础】16.JSON对象介绍 【重温基础】1...

    Pandaaa 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<