堆栈内存
-
什么是堆栈内存?
- 浏览器在执行程序时形成两个虚拟的内存,用来存储数据,和提供代码的执行环境;
- 浏览器每打开一个页面,都会开辟一个进程;一个进程包含了好几个线程;
- JS 是单线程;同一个时间只能执行一行代码;
- 堆栈内存: 用来存储数据;
栈内存分配
-
基本类型的数据存储到栈内存(stack)中,函数作用域是消耗的栈内存;越小浏览器性能越高;
-
引用数据类型存储到堆内存(heap)中;[对象、数组,函数],堆内存跟代码的执行环境没有关系;
-
区别:
- heap 堆内存数没有结构的;
- stack 栈内存是有结构的,有次序的;
let num = 100; num++;// 100 let str = "hello"; console.log(str);// 指针 let obj = {name:"zhufeng",age:10}; // 对象的定义: // 1. 去堆内存开辟一个空间地址 // 2. 把对象中的键值对放在堆内存中 // 3. 把堆内存中的空间地址赋值给变量 // 变量和对象的指针存储到了栈内存,当在栈内存中使用这个对象时,通过这个指针找到对应的堆内存地址; console.log(obj);// 指针
-
内存生命周期
- 内存的分配
- 内存的读写
- 不需要时将其释放(GC 垃圾回收机制)
堆内存回收
-
GC 垃圾回收器: GC 每隔一段时间执行一次;
-
目前 IE 火狐 opera 谷歌大都采用标记清除的方式;
-
垃圾回收方法
- 标记清除
- 给每一个空间地址记上标记
- 筛选过滤环境的变量或者被引用的变量去除标记
- 有标记的就被视为被删除的变量
- 浏览器对有标记的进行回收,释放内存
- 引用计数
- 对变量被引用一次就会在这个变量计数+1
- 如果不再引用,那么计数会默认-1;
- 当该变量计数为 0 时,浏览器要回收该块的内存;
- 循环引用:创建两个对象并引用彼此,在函数调用之后,已经可以被释放,但引用计数算法认为,由于两个对象中的每一个至少被引用了一次,所以也不能被垃圾回收。
- 标记清除
-
内存泄漏的情况:
- 意外的全局变量,全局变量不会被回收
- 可以通过严格模式
"use strict"
防止意外的全局变量
//1. function foo(arg) { bar = "some text"; } /* 等价于 function foo(arg) { window.bar = "some text"; }*/ //2. function foo() { this.var1 = "potential accidental global"; } foo();
- 闭包引发的内存泄漏
- 闭包会造成对象引用的生命周期脱离当前函数的上下文,
- 如果闭包如果使用不当,可以导致环形引用,类似于死锁,只能避免,无法发生之后解决,即使有垃圾回收也还是会内存泄露。
function bar() { let a = 10; // 会放到当前函数执行的环境中; return function() { console.log(a); // 10 }; } let b = bar(); b(); //不会被回收 b = null; //释放,会被回收
- 对象中存在 DOM 的引用
//1.没有清理的DOM元素引用 var refA = document.getElementById('refA'); document.body.removeChild(refA); //解决 refA = null; //2.给DOM对象添加的属性是一个对象的引用 var MyObject = {}; document.getElementById('myDiv').myProp = MyObject; //解决 window.onunload(){ document.getElementById('myDiv').myProp = null; } //3.DOM对象与JS对象相互引用 function Encapsulator(element) { this.elementReference = element; element.myProp = this; } new Encapsulator(document.getElementById('myDiv')); //解决 window.onunload(){ document.getElementById('myDiv').myProp = null; } //4.给DOM对象用attachEvent绑定事件 function doClick() {} //解决 element.attachEvent("onclick", doClick);
- 被遗忘的定时器和回调函数
-
在 setInterval 没结束前回调函数变量及本身都无法被回收。如果回调函数内没有做什么事情,并且也没有被 clear 掉的话,就会造成内存泄漏,回调函数内依赖的变量也没法被回收。
-
当不需要 interval 或者 timeout 时,最好调用
clearInterval
或者clearTimeout
。let someResource = getData(); setInterval(() => { const node = document.getElementById('Node'); if(node) { node.innerHTML = JSON.stringify(someResource)); } }, 1000);
- 循环引用
function func() { let obj1 = {}; let obj2 = {}; obj1.a = obj2; // obj1 引用 obj2 obj2.a = obj1; // obj2 引用 obj1 } obj1 = null; //释放 obj2 = null; //释放
函数编译 VOAO
-
函数的执行 VOAO 过程
-
函数的定义: 1.开辟一个堆内存,对应一个引用的空间地址
2. 把函数体中的代码当做字符串存储到堆内存中
3. 把函数体的空间地址赋值给函数名; -
函数调用执行,会引发 VOAO
-
函数的 VO
- 形成一个上下文的执行环境(栈内存)
- 初始化作用域链
- 创建变量函数
- 初始化 arguments 对象和参数并赋值
- 对该上下文中的函数进行声明并且赋值
- 如果函数名已经出现,那么会把第一次初始的变量进行覆盖
- 对该上下文的变量进行声明,初始化值是 undefined;
- 如果变量名重复,直接跳过;
- 确定 this 的指向;(注意:先确定 this 指向再调用的)
- 下面代码执行就是 AO:
- 代码从上到下开始执行
// 函数执行AO function bar(a) { // console.log(arguments); fn(); function fn() { console.log(100); } console.log(fn); function fn() { console.log(200); } var num = 100; var num = 1; } bar(1, 2, 3); console.dir(bar); function fn(num) { console.log(num); function num() {} } fn(1000);
-
函数的作用域链
- 当获取变量对应的值时,首先先看自己的作用域有没有,如果没有会向上一级查找,上一级也没有,会继续向上查找,直到找到 window 为止,如果 window 也没有,那就会报错;这样一级一级形成就是作用域链;
let total = 100;
function fn() {
// let total = 10;
return function() {
return function() {
return function fn() {
console.log(total);
};
};
};
}
let f = fn();
f(); // 在全局作用域下执行的;f的上一级作用域是谁;
console.dir(f);
// 函数的上一级作用域跟函数在哪定义的有关,在哪定义那么上一级作用域就是谁;
// f 在fn执行的作用域中开辟的堆内存;f的上一级作用域就是fn执行形成的作用域
栈内存回收
// 函数执行会形成栈内存(执行上下文)
// 函数执行完成之后,会立即回收;栈内存中存储的值都会回收掉;
/*function fn(){
let num =100;
}
fn();*/
// 这个栈内存是不回收;当关闭页面或浏览器时,回收站内存;
// 当函数体中返回一个引用的数据类型值时,并且这个引用的地址被外界占用,该栈内存不回收;
// 闭包: 函数执行中返回一个函数,里面的函数可以访问外界这个函数执行时存储的变量;
function fn() {
let t = 10;
return { name: 1 };
}
fn();