写作不易,Star是最大鼓励,感觉写的不错的可以给个Star⭐,请多多指教。Github地址

数据/变量/内存

什么是数据?

数据是存储在内存中代表特定信息的内容,本质上是0101... 数据的特点:可传递、可运算 一切皆数据 内存中所有操作的目标都是数据 算术运算、逻辑运算、赋值、运行函数

什么是内存?

  • 内存条通电后产生的可存储数据的空间(临时的);
  • 内存的产生和死亡: 内存条(集成电路板) ==> 通电 ==> 产生一定容量的内存存储空间 ==> 存储各种数据 ==> 断电 ==> 内存空间和数据都消失;
  • 内存的空间是临时的,而硬盘的空间是持久的;
  • 一块内存包含2个数据:
    • 内部存储的数据
    • 地址值
  • 内存分类:
    • 栈内存:全局变量/局部变量(空间较小)
    • 堆内存:对象本身在堆内存中,标识对象的变量在栈内存中(空间较大)

什么是变量?

可变化的量,由变量名和变量值组成 每个变量都对应一块小内存,变量名用来查找对应的内存,变量值就是内存中保存的数据。

内存,数据,变量三者之间的关系 内存用来存储数据的空间 变量是内存的标识

内存溢出与内存泄露

  1. 内存溢出
  • 一种程序运行出现的错误
  • 当程序运行需要的内存超过了剩余的内存时,就会抛出内存溢出的错误
  1. 内存泄露
  • 占用的内存没有及时释放
  • 内存泄露积累多了就容易导致内存溢出
  • 常见的内存泄露:
    • 意外的全局变量
    • 没有及时清理的计时器或回调函数
    • 闭包

内存溢出

// 1. 内存溢出
var obj = {};
for (var i = 0; i < 10000; i++) {
    obj[i] = new Array(10000000);
    console.log('------');
}
1
2
3
4
5
6

内存泄漏常见情况

javascript具有自动垃圾回收机制,一旦数据不再使用,可以将其设为null来释放引用。

意外的全局变量

// 意外的全局变量
function fn() {
    a = 1;
    console.log(a);
}
fn(); // fn执行完后,变量a不会被释放,造成内存泄露
1
2
3
4
5
6

循环引用

分为两种情况:不同对象之间相互引用和一个对象引用自身。

一个经典的例子: 一个DOM对象被一个Javascript对象引用,与此同时又引用同一个或其它的Javascript对象,这个DOM对象可能会引发内存泄露。这个DOM对象的引用**将不会在脚本停止的时候被垃圾回收器回收。**要想清除循环引用,引用DOM元素的对象或DOM对象的引用需要被赋值为null。

var oBox = document.getElementById('box');
var obj = {};
oBox.name = obj;
obj.age = oBox; // 这样就发生了循环引用
1
2
3
4

闭包(常见)

通过闭包引用其包含函数的局部变量,当闭包结束后该局部变量无法被垃圾回收机制回收,造成内存泄漏。

// 闭包
function fn1() {
    var a = 1;
    function fn2() {
        a++;
        console.log(a);
    }
    return fn2;
}
var f = fn1(); // 产生一个闭包
f(); // 2
// f = null;
1
2
3
4
5
6
7
8
9
10
11
12

DOM泄漏

闭包会引用包含函数的整个变量对象,如果闭包的作用域链中保存着一个HTML元素,那么就意味着该元素无法被销毁。所以我们有必要在对这个元素操作完之后主动销毁。

function fn() {
    let el = document.querySelector('#someElement');
    let id = el.id;
    el.onclick = function() {
        console.log(id);
    }
    el = null;
}
1
2
3
4
5
6
7
8

当原有的DOM被移除时,子节点引用没有被移除则无法回收。

var select = document.querySelector;
var treeRef = select('#tree');
// 在COM树中leafRef是treeFre的一个子结点
var leafRef = select('#leaf');
var body = select('body');
body.removeChild(treeRef);
// #tree不能被回收入,因为treeRef还在
// 解决方法:
treeRef = null;
// tree还不能被回收,因为叶子结果leafRef还在
leafRef = null;
// 现在#tree可以被释放了
1
2
3
4
5
6
7
8
9
10
11
12

定时器泄漏

for (var i = 0; i < 1000; i++) {
  var obj = {
    fn: function() {
      var that = this;
      var val = setTimeout(function() {
        that.fn();
      }, 500);
    }
  }
  obj.fn();
  // 虽然你想回收但是timer还在
  obj = null;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
// 启动循环定时器后不清除
var timer = setInterval(function() {
    console.log('---');
}, 1000);
// clearInterval(timer);
1
2
3
4
5

当函数内部的定时器引用了外部函数的变量对象时,该变量对象不会被销毁。

(function() {
    var a = 1;
    setInterval(function() {
        console.log(a++);
    }, 1000);
})()
1
2
3
4
5
6

垃圾回收

标记清除

js中最常用的垃圾回收方式就是标记清除。垃圾收集器在运行的时候回给存储在内存中的所有变量都加上标记。然后,它会去掉环境中的变量以及被环境中的变量引用的变量的标记(闭包)。而在此之后再被加上标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。最后,垃圾收集器完成内存清除工作,消除那些带标记的值并回收它们所占用的内存空间。 例如:在函数中声明一个变量,就将这个变量标记为"进入环境"。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为只要执行流进入相应的环境,就可能会用到它们。而当变量离开环境时,则将其标记为"离开环境"。

function test() {
 var x = 10 ; //被标记,进入环境
 var y = 20 ; //被标记,进入环境
}
test(); //执行完毕后 x、y又被标离开环境,被回收。
1
2
3
4
5

引用计数

引用计数就是:跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数就是1。如果同一个值又被赋给另一个变量,则该值的引用次数加1。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数减1。当这个值的引用次数变成0时,则说明没有办法再访问这个值了,因而就可以将其占用的内存空间回收回来。这样,当垃圾回收器下次再运行时,它就会释放那些引用次数为0的值所占用的内存

function test(){
 var x = {} ; //x的引用次数为0
 var y = x ; //x的引用次数加1,为1
 var z =x; //x的引用次数再加1,为2
 var y ={}; //x的引用次数减1,为1
}
1
2
3
4
5
6

引用计数策略的问题(循环引用)

function test() {
 var a = {};
 var b = {};
 a.name = b;
 b.name = a;
}
test();
1
2
3
4
5
6
7

以上代码a和b的引用次数都是2,fn()执行完毕后,两个对象都已经离开环境,在标记清除方式下是没有问题的,但是在引用计数策略下,因为a和b的引用次数不为0,所以不会被垃圾回收器回收内存,如果fn函数被大量调用,就会造成内存泄露。在IE7与IE8上,内存直线上升。 我们知道,IE中有一部分对象并不是原生js对象。例如,其BOM和DOM中的对象就是使用C++以COM对象的形式实现的,而COM对象的垃圾回收机制采用的就是引用计数策略。因此,即使IE的js引擎采用标记清除策略来实现,但js访问的COM对象依然是基于引用计数策略的。换句话说,只要在IE中涉及COM对象,就会存在循环引用的问题

var element = document.getElementById('some_element');
var myObject = new Object();
myObject.element = element;
element.someObject = myObject;//产生了循环引用
1
2
3
4

这个例子在一个DOM元素(element)与一个原生js对象(myObject)之间创建了循环引用。其中,变量myObject有一个名为element的属性指向element对象;而变量element也有一个someObject属性指向myObject。由于存在这个循环引用,即使例子中的DOM从页面中移除,它也永远不会被回收。

myObject.element = null;
element.someObject = null;
1
2

为了避免类似这样的循环引用问题,最好在不使用它们的时候手工断开原生js对象与DOM元素之间的连接。将变量设置为null意味着切断变量与它此前引用的值之间的连接。当垃圾收集器下次运行时,就会删除这些值并回收它们占用的内存。

注意: 为了解决上述问题,IE9把BOM和DOM对象都转换成了真正的js对象。这样,就避免了两种垃圾收集算法并存导致的问题,也消除了常见的内存泄漏现象。

评 论: