InterviewMap —— Javascript (五)

栏目: JavaScript · 发布时间: 5年前

内容简介:不想C语言那样,拥有原始底层的内存操作方法如即使是使用高级语言,开发者对内存管理也应该有所了解(至少要有基础的了解)。有时,开发者必须理解自动内存管理会遇到问题(例如:垃圾回收中的错误或者性能问题等),以便能够正确处理它们。(或者是找到适当的解决方法,用最小的代价去解决。)如果一个值不再需要了,但是垃圾回收机制确无法回收,这时候就是内存泄漏了。

不想 C语言 那样,拥有原始底层的内存操作方法如 malloc free 。js使用的是自动垃圾回收机制,也就是说js引擎会自动去判别变量的使用情况来自动回收那些不使用的内存块。

即使是使用高级语言,开发者对内存管理也应该有所了解(至少要有基础的了解)。有时,开发者必须理解自动内存管理会遇到问题(例如:垃圾回收中的错误或者性能问题等),以便能够正确处理它们。(或者是找到适当的解决方法,用最小的代价去解决。)

如果一个值不再需要了,但是垃圾回收机制确无法回收,这时候就是内存泄漏了。

const arr = [1, 2, 3, 4];
console.log('hello world');
复制代码

上面代码中,数组 [1, 2, 3, 4] 是一个值,会占用内存。变量 arr 是仅有的对这个值的引用,因此引用次数为 1 。尽管后面的代码没有用到 arr ,它还是会持续占用内存。

如果增加一行代码,解除 arr[1, 2, 3, 4] 引用,这块内存就可以被垃圾回收机制释放了。

const arr = [1, 2, 3, 4];
console.log('hello world');
arr = null; 
复制代码

以上例子是在全局下的,arr为全局变量,它属于全局变量对象,全局变量对象只有在浏览器窗口关闭的时候才会被销毁,因此我们才会不推荐使用过多的全局变量。

因此,并不是说有了垃圾回收机制,程序员就轻松了。你还是需要关注内存占用:那些很占空间的值,一旦不再用到,你必须检查是否还存在对它们的引用。如果是的话,就必须手动解除引用。

1、内存的生命周期

InterviewMap —— Javascript (五)

内存往往经历: 操作系统分配内存 == 使用内存 == 内存释放 三个阶段。

2、垃圾回收机制

(1)标记清除

该算法由以下步骤组成:

  • 垃圾回收器构建“roots”列表。Roots 通常是代码中保留引用的全局变量。在 JavaScript 中,“window” 对象可以作为 root 全局变量示例。
  • 所有的 roots 被检查并标记为 active(即不是垃圾)。所有的 children 也被递归检查。从 root 能够到达的一切都不被认为是垃圾。
  • 所有未被标记为 active 的内存可以被认为是垃圾了。收集器限制可以释放这些内存并将其返回到操作系统
InterviewMap —— Javascript (五)

如果是该算法,循环引用就不会出现。在函数调用后,两个对象不再被从全局对象可访问的东西所引用。因此,垃圾回收器将发现它们是不可达的。

(2)引用计数

如果一个值的引用次数是0,就表示这个值不再用到了,因此可以将这块内存释放。

InterviewMap —— Javascript (五)

上图中,左下角的两个值,没有任何引用,所以可以释放。

function f() {
  var o1 = {};
  var o2 = {};
  o1.p = o2; // o1 references o2
  o2.p = o1; // o2 references o1. This creates a cycle.
}
 
f();
复制代码

在函数调用之后,它们离开了作用域,因此它们实际上已经无用了,可以被释放了。然而,引用计数算法认为,由于两个对象中的每一个至少被引用了一次,所以也不能被垃圾回收。

InterviewMap —— Javascript (五)

3、什么是内存泄漏

实质上,内存泄漏可以被定义为应用程序不再需要的内存,但由于某种原因,内存不会返回到操作系统或可用内存池中。

4、内存泄漏的例子

(1)意外的全局变量

function foo(arg) { 
    bar = "this is a hidden global variable";
    //等同于window.bar="this is a hidden global variable"
    this.bar2= "potential accidental global";
    //这里的this 指向了全局对象(window),等同于window.bar2="potential accidental global"
}
复制代码

如果是在函数中未使用var声明的变量,那么会将其放到全局window上,会产生一个意外的全局变量。全局变量会一直驻留内存,一次我们要坚决避免这种意外发生。

解决办法就是使用'use strict'开启严格模式。

(2)循环引用

let obj1 = { a: 1 }; // 一个对象(称之为 A)被创建,赋值给 obj1,A 的引用个数为 1   
let obj2 = obj1; // A 的引用个数变为 2  
  
obj1 = null; // A 的引用个数变为 1  
obj2 = null; // A 的引用个数变为 0,此时对象 A 就可以被垃圾回收了
复制代码

但是引用计数有个最大的问题: 循环引用。

function func() {  
    let obj1 = {};  
    let obj2 = {};  
  
    obj1.a = obj2; // obj1 引用 obj2  
    obj2.a = obj1; // obj2 引用 obj1  
}
复制代码

函数执行完毕之后,按道理是可以被销毁的。内部的变量也会被销毁。但根据引用计数方法,obj1 和 obj2 的引用次数都不为 0,所以他们不会被回收。要解决循环引用的问题,最好是在不使用它们的时候手工将它们设为空。上面的例子可以这么做:

obj1 = null;  
obj2 = null;
复制代码

(3)被遗忘的计时器和回调函数

let someResource = getData();  
setInterval(() => {  
    const node = document.getElementById('Node');  
    if(node) {  
        node.innerHTML = JSON.stringify(someResource));  
    }  
}, 1000);
复制代码

每隔一秒执行一次匿名回调函数,该函数由于会被长期调用,因此其内部的变量都不会被回收,引用外部的someResource也不会被回收。那什么才叫结束呢?就是调用了 clearInterval。

比如开发SPA页面,当我们的某一个页面中存在这类定时器,跳转到另一个页面的时候,其实这里的定时器已经暂时没用了,但是我们在另一个页面的时候,内存中还是回你保留上一个页面的定时器资源,因此这就会导致内存泄漏。解决办法就是即使的使用clearInterval来清除定时器。

(4)闭包

JavaScript 开发的一个关键方面就是闭包:一个可以访问外部(封闭)函数变量的内部函数。

值得注意的是闭包本身不会造成内存泄漏,但闭包过多很容易导致内存泄漏。闭包会造成对象引用的生命周期脱离当前函数的上下文,如果闭包如果使用不当,可以导致环形引用(circular reference),类似于死锁,只能避免,无法发生之后解决,即使有垃圾回收也还是会内存泄露。

(5)console

console.log :向web开发控制台打印一条消息,常用来在开发时调试分析。有时在开发时,需要打印一些对象信息,但发布时却忘记去掉 console.log 语句,这可能造成内存泄露。

在传递给 console.log 的对象是不能被垃圾回收 :recycle:,因为在代码运行之后需要在开发 工具 能查看对象信息。所以最好不要在生产环境中 console.log 任何对象。

(6)DOM泄漏

在Js中对DOM操作是非常耗时的。因为JavaScript/ECMAScript引擎独立于渲染引擎,而DOM是位于渲染引擎,相互访问需要消耗一定的资源。

假如将JavaScript/ECMAScript、DOM分别想象成两座孤岛,两岛之间通过一座收费桥连接,过桥需要交纳一定“过桥费”。JavaScript/ECMAScript每次访问DOM时,都需要交纳“过桥费”。因此访问DOM次数越多,费用越高,页面性能就会受到很大影响。

InterviewMap —— Javascript (五)

为了减少DOM访问次数,一般情况下,当需要多次访问同一个DOM方法或属性时,会将DOM引用缓存到一个局部变量中。但如果在执行某些删除、更新操作后,可能会忘记释放掉代码中对应的DOM引用,这样会造成DOM内存泄露。

var refA = document.getElementById('refA');
document.body.removeChild(refA);
 // #refA不能回收,因为存在变量refA对它的引用。将其对#refA引用释放,但还是无法回收#refA。
 
 // 使用refA = null; 来释放内存
复制代码
var MyObject = {}; 
document.getElementById('myDiv').myProp = MyObject; 

解决方法: 
在window.onunload事件中写上: document.getElementById('myDiv').myProp = null; 
复制代码

给DOM对象用attachEvent绑定事件:

function doClick() {} 
element.attachEvent("onclick", doClick); 

解决方法: 
在onunload事件中写上: element.detachEvent('onclick', doClick); 
复制代码

从外到内执行appendChild。这时即使调用removeChild也无法释放。范例:

var parentDiv = document.createElement("div"); 
var childDiv = document.createElement("div"); 
document.body.appendChild(parentDiv); 
parentDiv.appendChild(childDiv); 
解决方法: 
从内到外执行appendChild: 
var parentDiv = document.createElement("div"); 
var childDiv = document.createElement("div"); 
parentDiv.appendChild(childDiv); 
document.body.appendChild(parentDiv); 
复制代码

反复重写同一个属性会造成内存大量占用(但关闭IE后内存会被释放)。范例:

for(i = 0; i < 5000; i++) { 
    hostElement.text = "asdfasdfasdf"; 
} 
这种方式相当于定义了5000个属性! 
解决方法: 
其实没什么解决方法:P~~~就是编程的时候尽量避免出现这种情况咯~~ 
复制代码

5、WeakMap 你了解吗?

前面说过,及时清除引用非常重要。但是,你不可能记得那么多,有时候一疏忽就忘了,所以才有那么多内存泄漏。

最好能有一种方法,在新建引用的时候就声明,哪些引用必须手动清除,哪些引用可以忽略不计,当其他引用消失以后,垃圾回收机制就可以释放内存。这样就能大大减轻 程序员 的负担,你只要清除主要引用就可以了。

ES6 考虑到了这一点,推出了两种新的数据结构:WeakSet 和 WeakMap。它们对于值的引用都是不计入垃圾回收机制的,是一种弱引用,所以名字里面才会有一个"Weak",表示这是弱引用。

const wm = new WeakMap();

const element = document.getElementById('example'); // 引用计数1

wm.set(element, 'some information'); // 此处是弱引用,不计数
wm.get(element) // "some information" 
复制代码

WeakMap 里面对 element 的引用就是弱引用,不会被计入垃圾回收机制。

也就是说, DOM 节点对象的引用计数是 1 ,而不是 2 。这时,一旦消除对该节点的引用,它占用的内存就会被垃圾回收机制释放。 Weakmap 保存的这个键值对,也会自动消失。

总结

虽然当下的浏览器已经对垃圾回收机制做出了一定的改进和提升,但是内存泄漏的问题我们还是需要关注的。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

JavaScript忍者秘籍

JavaScript忍者秘籍

John Resig、Bear Bibeault / 徐涛 / 人民邮电出版社 / 2015-10 / 69.00

JavaScript语言非常重要,相关的技术图书也很多,但没有任何一本书对JavaScript语言的重要部分(函数、闭包和原型)进行深入、全面的介绍,也没有任何一本书讲述跨浏览器代码的编写。本书是jQuery库创始人编写的一本深入剖析JavaScript语言的书。 本书共分四个部分,从准入训练、见习训练、忍者训练和火影训练四个层次讲述了逐步成为JavaScript高手的全过程。全书从高级We......一起来看看 《JavaScript忍者秘籍》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

SHA 加密
SHA 加密

SHA 加密工具

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具