js内存机制
js具有自动垃圾回收机制,周期性检查没有使用的变量,进行回收释放,所以在闭包中,如果引用了外部变量,则变量无法释放和回收,一般会传参进去。 在js中,每一个数据都需要一个内存空间,内存空间分为栈内存(stack)和堆内存(heap)。
栈内存存储基本类型
Number String undefined Boolean Null Symbol
例子
var num =1
我们定义一个变量num,系统会自动分配储存空间,我们可以直接操作保存在栈空间的值,因此基础类型都是按值访问。
数据在栈内存中的存储与使用方式类似于数据结构中的堆栈数据结构,遵循 后进先出的原则。
堆内存存储引用类型
var user = { name:'jack' }
var arr = [1,3,5]
js的引用数据类型,比如数组Array,他们值的大小都是不固定的,引用数据类型的值是保存在堆内存中的对象。JavaScript不允许直接访问堆内存中的位置,因此我们不能直接操作对象的堆内存空间。
var num = 1; // 栈
var name = '前端未来'; // 栈
// 变量user存在于栈中,{name:'河畔'}存在于堆内存中
var user = { name: '河畔' };
// 变量arr存在于栈中,[1, 2, 3] 作为对象存在于堆内存中
var arr = [1, 3, 5];
因此我们要访问堆内存中的引用类型数据时,首先我们时从占内存中获取了该对象的指针,然后在堆内存中取出我们需要的数据。
所以我们经常说,基本类型赋值相互不影响,引用类型赋值,会影响原对象。
总结:
- js具备自动垃圾回收机制
- js内存分为栈内存和堆内存
- 引用类型在栈中保存指针,在堆中保存对象值
- 栈内存数据遵循先进后出
eventloop
现在前端面试,大家都喜欢问eventloop,为了更好的理解事件机制,我们需要先介绍一下执行栈。
执行栈
js代码在运行前,都会创建执行上下文,也可以理解为执行环境,js有三种执行上下文:
- 全局执行上下文,默认的,在浏览器中是window对象
- 函数执行上下文,js的函数每当被调用时会创建上下文
- eval执行上下文,eval会产生自己的执行上下文
接下来我们看一下例子
function foo(){
console.log(1)
bar()
console.log(3)
}
function bar(){
console.log(2)
}
foo()
// 1,2,3
这个毫无疑问大家都知道结果,执行栈怎么调用的? 首先执行这个js文件,创建一个全局上下文,并压入执行栈中,当foo函数被调用时,将foo函数执行上下文压入执行栈中,接着执行输出1;当bar函数被调用时,将bar函数的执行上下文压入执行栈中,接着执行输出2;bar函数执行完成后,被弹出执行栈,foo函数接着执行输出3;foo函数执行完成后,被弹出执行栈,最后清空这个执行栈,这就是先进后出,foo先被压入执行栈,最后才被弹出执行栈。
总结:
- 所有js代码运行,都要放入执行栈中
- 执行上下文包含(全局,函数,eval)
- 栈是一种数据结构,遵循先进后出
接下来看一道面试题
console.log(1)
new Promise(function(resolve){
console.log(3)
resolve(100)
}).then(function(data){
console.log(data)
})
setTimeout(function(){
console.log(4);
})
console.log(2)
上面的面试题打印结果: 1 3 2 100 4
你能说出具体的步骤吗? 我们都知道js是单线程的,一次只能干一件事,那么像定时器,promise这些是怎么处理的呢?实际上就要介绍异步队列了。
主线程执行同步代码块,遇到定时器promise,settimeout等异步任务时,会创建事件队列,把他们丢到队列里去,等到主线程执行完成后,在去执行异步队列。 所以,我们js执行主要包括同步任务和异步任务。整个同步任务都会放到主线程中,最后放到执行栈中执行。 浏览器的js,又分为宏任务和微任务,微任务和宏任务都属于队列,而不是放在栈中。微任务会创建一个队列,宏任务创建一个队列,而主线程执行完成以后,会优先执行微任务,把微任务全部放到执行栈中执行,最后再从宏任务中取出一个放入执行栈进行执行,执行完后,再取一个,直到执行完所有的宏任务。