写作不易,Star是最大鼓励,感觉写的不错的可以给个Star⭐,请多多指教。Github地址。
数据/变量/内存
什么是数据?
数据是存储在内存中代表特定信息的内容,本质上是0101... 数据的特点:可传递、可运算 一切皆数据 内存中所有操作的目标都是数据 算术运算、逻辑运算、赋值、运行函数
什么是内存?
- 内存条通电后产生的可存储数据的空间(临时的);
- 内存的产生和死亡: 内存条(集成电路板) ==> 通电 ==> 产生一定容量的内存存储空间 ==> 存储各种数据 ==> 断电 ==> 内存空间和数据都消失;
- 内存的空间是临时的,而硬盘的空间是持久的;
- 一块内存包含2个数据:
- 内部存储的数据
- 地址值
- 内存分类:
- 栈内存:全局变量/局部变量(空间较小)
- 堆内存:对象本身在堆内存中,标识对象的变量在栈内存中(空间较大)
什么是变量?
可变化的量,由变量名和变量值组成 每个变量都对应一块小内存,变量名用来查找对应的内存,变量值就是内存中保存的数据。
内存,数据,变量三者之间的关系 内存用来存储数据的空间 变量是内存的标识
内存溢出与内存泄露
- 内存溢出
- 一种程序运行出现的错误
- 当程序运行需要的内存超过了剩余的内存时,就会抛出内存溢出的错误
- 内存泄露
- 占用的内存没有及时释放
- 内存泄露积累多了就容易导致内存溢出
- 常见的内存泄露:
- 意外的全局变量
- 没有及时清理的计时器或回调函数
- 闭包
内存溢出
// 1. 内存溢出
var obj = {};
for (var i = 0; i < 10000; i++) {
obj[i] = new Array(10000000);
console.log('------');
}
2
3
4
5
6
内存泄漏常见情况
javascript具有自动垃圾回收机制,一旦数据不再使用,可以将其设为null来释放引用。
意外的全局变量
// 意外的全局变量
function fn() {
a = 1;
console.log(a);
}
fn(); // fn执行完后,变量a不会被释放,造成内存泄露
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; // 这样就发生了循环引用
2
3
4
闭包(常见)
通过闭包引用其包含函数的局部变量,当闭包结束后该局部变量无法被垃圾回收机制回收,造成内存泄漏。
// 闭包
function fn1() {
var a = 1;
function fn2() {
a++;
console.log(a);
}
return fn2;
}
var f = fn1(); // 产生一个闭包
f(); // 2
// f = null;
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;
}
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可以被释放了
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;
}
2
3
4
5
6
7
8
9
10
11
12
13
// 启动循环定时器后不清除
var timer = setInterval(function() {
console.log('---');
}, 1000);
// clearInterval(timer);
2
3
4
5
当函数内部的定时器引用了外部函数的变量对象时,该变量对象不会被销毁。
(function() {
var a = 1;
setInterval(function() {
console.log(a++);
}, 1000);
})()
2
3
4
5
6
垃圾回收
标记清除
js中最常用的垃圾回收方式就是标记清除。垃圾收集器在运行的时候回给存储在内存中的所有变量都加上标记。然后,它会去掉环境中的变量以及被环境中的变量引用的变量的标记(闭包)。而在此之后再被加上标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。最后,垃圾收集器完成内存清除工作,消除那些带标记的值并回收它们所占用的内存空间。 例如:在函数中声明一个变量,就将这个变量标记为"进入环境"。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为只要执行流进入相应的环境,就可能会用到它们。而当变量离开环境时,则将其标记为"离开环境"。
function test() {
var x = 10 ; //被标记,进入环境
var y = 20 ; //被标记,进入环境
}
test(); //执行完毕后 x、y又被标离开环境,被回收。
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
}
2
3
4
5
6
引用计数策略的问题(循环引用)
function test() {
var a = {};
var b = {};
a.name = b;
b.name = a;
}
test();
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;//产生了循环引用
2
3
4
这个例子在一个DOM元素(element)与一个原生js对象(myObject)之间创建了循环引用。其中,变量myObject有一个名为element的属性指向element对象;而变量element也有一个someObject属性指向myObject。由于存在这个循环引用,即使例子中的DOM从页面中移除,它也永远不会被回收。
myObject.element = null;
element.someObject = null;
2
为了避免类似这样的循环引用问题,最好在不使用它们的时候手工断开原生js对象与DOM元素之间的连接。将变量设置为null意味着切断变量与它此前引用的值之间的连接。当垃圾收集器下次运行时,就会删除这些值并回收它们占用的内存。
注意: 为了解决上述问题,IE9把BOM和DOM对象都转换成了真正的js对象。这样,就避免了两种垃圾收集算法并存导致的问题,也消除了常见的内存泄漏现象。