咱们写的JS
代码浏览器是如何解析的?浏览器在执行JS
代码的过程当中发生了什么?…这些问题可能在完成业务功能代码的过程当中并无多么重要,可是做为一名前端开发工程师,了解这些会提高本身的思惟能力、会让本身更深层次的理解JavaScript
语言。这篇文章将详细阐述浏览器中堆栈内存的底层处理机制、垃圾回收机制以及内存泄漏的几种状况。javascript
词法解析:这个过程会将由字符组成的字符串分解成有意义的代码块(词法单元)。前端
语法分析:这个过程将词法单元流转换成一个由元素逐级嵌套所组成的表明了程序语法的树(抽象语法树 => AST
)。java
代码生成:将AST
转换为可执行代码的过程被称为代码生成。web
而后将构建出的代码交给引擎(V8),这个时候可能会遇到变量提高
、做用域和做用域链/闭包
、变量对象
、堆栈内存
、GO/VO/AO/EC/ECStack
、…。面试
引擎在编译执行代码的过程当中,首先会建立一个执行栈,也就是栈内存(ECStack
=> 执行环境栈),而后执行代码。浏览器
在代码执行会建立EC
(执行上下文),执行上下文分为全局执行上下文(EC(G)
)和函数执行上下文(EC(...)
),其中函数的执行上下文是私有的。闭包
建立执行上下文的过程当中,可能会建立:app
GO(Global Object)
:全局对象 浏览器端,会把GO赋值给windowVO(Varible Object)
:变量对象,存储当前上下文中的变量。AO(Active Object)
:活动对象而后将进栈执行,建立好的上下文将压缩到栈中执行,执行后一些没用的上下文将出栈,有用的上下文会压缩到栈底(闭包)。栈底永远是全局执行上下文,栈顶则永远是当前执行上下文。dom
下面的一张图表达了整个流程函数
第一步,建立变量,这个过程叫作声明(declare
)。
第二步,建立值。基本类型值会直接在栈中建立和存储;因为引用类型值是复杂的结构,因此需开辟一个存储对象中键值对(存储函数中代码)的内存空间,这个内存就是堆内存,全部的堆内存都有可被后续查找的16
进制地址,后续关联赋值时,是把堆内存地址给予变量操做。
最后一步,将变量和值关联,这个过程叫作定义(defined
)。这里,若是值只通过了声明,而没有进行赋值操做,这个值就是未定义(undefined
)。
我将以画图的形式展现
// 1.
let a = 12;
let b = a;
b = 13;
console.log(a);
// 2.
let a = {n:12};
let b = a;
b['n'] = 13;
console.log(a.n);
// 3.
let a = {n:12};
let b = a;
b = {n:13};
console.log(a.n);
复制代码
建立执行栈,造成全局执行上下文,而且建立GO
,进入栈中执行代码
基本类型直接在栈中建立和存储
因此本问最终输出的a
值为12
。
建立执行栈,造成全局执行上下文,而且建立GO
,进入栈中执行代码
引用类型值比较复杂,将建立堆内存
因此本问最终输出的a.n
为13
建立执行栈,造成全局执行上下文,而且建立GO
,进入栈中执行代码
引用类型值比较复杂,将建立堆内存
因此本问最终输出的a.n
的值为12
。
let a = {
n: 10
};
let b = a;
b.m = b = {
n: 20
};
console.log(a);
console.log(b);
复制代码
建立执行栈,造成全局执行上下文,而且建立GO
,进入栈中执行代码
引用类型值比较复杂,将建立堆内存
因此最终输出的a
为{n: 10, m: {n: 20}}
;b
为{n: 20}
let x = [12, 23];
function fn(y) {
y[0] = 100;
y = [100];
y[1] = 200;
console.log(y);
}
fn(x);
console.log(x);
复制代码
首先会建立ECStack
,造成全局执行上下文,建立VO
(变量对象), 而后进入栈中执行代码
变量赋值
接下来会执行fn(x)
函数,函数执行会造成一个全新的执行上下文,会产生AO
。上面说过,栈顶永远是当前执行上下文,栈底是全局执行上下文,因此函数执行,函数执行上下文将进栈,会将全局执行上下文压入栈底。
而后进行代码的执行操做,执行后会出栈
继续执行,打印出x
,通过上述分析:
答案是:[100, 200]
[100, 23]
var x = 10;
~ function (x) {
console.log(x);
x = x || 20 && 30 || 40;
console.log(x);
}();
console.log(x);
复制代码
因此,最终的结果为:undefined
30
10
let x = [1, 2],
y = [3, 4];
~ function (x) {
x.push('A');
x = x.slice(0);
x.push('B');
x = y;
x.push('C');
console.log(x, y);
}(x);
console.log(x, y);
复制代码
因此本题最终的输出结果为[3, 4, 'C'] [3, 4, 'C']
[1, 2, 'A'] [3, 4, 'C']
。
浏览器的
Javascript
具备自动垃圾回收机制(GC:Garbage Collecation
),垃圾收集器会按期(周期性)找出那些不在继续使用的变量,而后释放其内存。
在js
中,最经常使用的垃圾回收机制是标记清除:当变量进入执行环境时,被标记为“进入环境”,当变量离开执行环境时,会被标记为“离开环境”。垃圾回收器会销毁那些带标记的值并回收它们所占用的内存空间。
function demo() {
var a = 1; // 标记"进入环境"
var b = 2; // 标记"进入环境"
}
demo(); // 函数执行完毕,a和b标记为"离开环境"
复制代码
浏览器会跟踪记录值的引用次数,每多引用一次,引用次数就会加1,取消占用,引用次数就会减1,当引用次数为0时,浏览器会进行垃圾回收。
下面的例子说明a
引用次数的变化
function demo() {
var a = {}; // +1
var b = a; // +1 => 2
b = null; // -1 => 1
a = null; // -1 => 0 ====> 此时会回收
}
复制代码
内存泄露是指程序中的某些函数或者任务执行完毕以后,本该释放的内存空间,因为各类缘由,没有被释放,致使程序愈来愈耗内存,最终可能引起程序崩溃等各类严重后果。
在 JS 中,常见的内存泄露主要有 4 种
一个例子直接说明:
var obj = null;
function foo(){
obj = { name:"小红" };
}
foo();
复制代码
上述代码中,obj
是一个全局变量,这样,全部和obj
做用域同层级的函数均可以访问到obj
对象,因此obj
对象不会被回收。
闭包是 JS 中最容易引发内存泄露的特性
function foo(){
var obj = {name:"小红"}
return function(){
return obj.name;
}
}
var func = foo(); // foo返回的值是一个函数,func也变成了一个外部函数
func(); // 外部函数func能狗访问foo内部的user对象。
复制代码
上述代码中,foo
函数执行完后,由于在func()
中依然可以访问到obj
,因此变量obj
没有被释放,这就致使了内存泄漏,咱们能够用下面的方法解决
function foo(){
var obj = {name:"小红"}
return function(){
var obj1 = obj;
obj = null;
return obj1.name;
}
}
var func = foo(); // foo返回的值是一个函数,func也变成了一个外部函数
func();
复制代码
上面的代码中,在foo
函数返回的函数中,及时将obj
释放了,这个时候,在func
函数执行时,就不会访问到局部变量obj
了。
对DOM
元素的引用中,会出现内存泄漏
DOM
元素删除了,可是JS
对象中的引用没删除<body>
<div id="app"></div>
<script>
var appDom = document.getElementById("app");
appDom.onclick = function() {
document.body.removeChild(document.getElementById("app"));
}
</script>
</body>
复制代码
上面的例子中,点击#app
时,清除了该DOM
节点,可是appDom
依然保留对其的引用,致使#app
没有被释放。
setInterval
函数的定时器会一直循环,除非手动清除,这就出现了内存隐患,因此咱们应该在使用完定时器时对定时器及时清除。
var count = setInterval(() => {
console.log(1);
}, 1000);
// 使用完成
clearInterval(count)
复制代码
以上是致使内存泄漏的四种状况(例子不仅有文中的几个,在平时的开发工做中还会有不少的例子),在咱们的平常开发工做中,应该避免这四种状况的发生,因此咱们写代码的额过程当中,要多多注意。
本文详细讲解了在浏览器中是如何对堆栈内存进行处理的,也简单说了一下垃圾回收机制的几种方法和形成内存泄漏的几种状况。还但愿你们在仔细阅读后(自动忽略掉我写的丑字🐶),可以指出其中不合理甚至错误的地方,咱们共同窗习,共同进步~
最后,分享一下个人公众号「web前端日记」,但愿你们多多关注