函数创建和执行的堆栈运行机制
function func() {
let total = 10 + 10;
console.log(total)
}
func(); //=>每执行一次会形成一个新的执行上下文
创建一个函数
1.创建值
- 首先开辟一个堆内存
- 把函数体中的代码当做字符串存储到堆中
- 把堆地址放到栈中
2.创建变量
3.让变量和地址关联
只创建函数,其实就是创建了一个存储一堆字符串的堆而已,并没有实际作用
执行一个函数
1.创建一个全新的执行上下文,把执行上下文压缩到栈内存中去执行(=>进栈执行)
2.在这个上下文中,也存在一个变量对象,用来存储当前上下文代码执行中所创建的变量
3.执行代码
4.当上下文中的代码都执行完后,如果该上下文中的信息没有被外面占用的情况,则执行完全出栈(以此减少栈内存中的空间)
return返回值
功能一
function func() {
let total = 10 + 10;
console.log(total) // 20
// return total
}
func();
console.log(total) //err->total is not defined
上面的代码,函数自上而下开始执行,创建一个函数,在栈中存储变量和堆地址,在堆中存储字符串;函数执行,在栈中开辟私有作用域,在私有作用域中执行方法,在私有作用域中,我们可以找到total,输出20。在全局作用域中输出total,会报错;这就是作用域的作用,私有作用域的变量只能是私有作用域中查找,全局变量对象中是没有total。
如果我们在私有作用域中把 私有作用域的total 变量返回出去,这样我们就可以在全部变量里面找到total了。这个就是return的作用。我们把这种在外部作用域可以拿到私有作用域变量的方法叫做闭包
在执行函数的时候,有这么一条规则:当上下文中的代码都执行完后,如果该上下文中的信息没有被外面占用的情况,则执行完全出栈(销毁当前作用域)。现在使用了return,func这个私有作用域的total被外部使用,那么这个私有作用域就会保留在执行环境栈ECStack中,占用内存。如果调用total变量的这个函数一直没有销毁,那么func这个私有作用就会一直保留,造成内存泄漏。
功能二
function func(x){
// 验证X是否为有效数字,如果不是,则不再继续执行后面的代码
if(isNaN(x)){
return
}
let result = x +1;
return result
}
return在函数中,除了返回信息外,还有告知函数体中,下面的代码不再执行的作用
Event Loop
Eevet Loop指的是计算机操作系统的一种运行机制。JavaScript就是采用这种机制来解决单线程运行带来的一些问题。
Event Loop是一个程序结构,用于等待和发送消息和事件;简单的说,就是在程序中设置两个线程:一个负责程序本身的运行,称为“主线程”;另一个负责主线程与其他进程(主要是各种I/O操作)的通信,被称为“Event Loop线程”。
JavaScript主线程会维护一个执行栈,然后逐行执行代码,如果碰到异步代码(Promise、ajax、settimeout等等),那么它会把这些代码交给浏览器的其他相关的线程去执行,然后跳过这些代码继续往下执行,直到当前代码段结束,
同时浏览器的其他相关线程处理好该异步代码执行前需要的准备工作时(比如AJAX数据,鼠标事件触发,定时器时间到了等),浏览器会把当前的异步代码放入一个异步任务队列中(queue先进先出)
每次当主线程逐行执行完当前代码时,都会去检查异步队列中是否有任务需要执行,如果有,那么就把那个任务拿出来在执行代码,如果在其中有碰到了异步代码,那么再交给浏览器的其他线程去处理,然后跳过这些代码继续执行,直到当前代码段结束,然后在检查异步队列,如此循环往复...这就叫事件循环
异步队列Microtask和Macrotask
异步队列并非只有一个,主要分为Microtask(微任务)队列和Macrotask(宏任务)队列
Microtask:
- process.nextTick、Promise、Object.observe、MutationObserver
Macrotask:
- setTimeout、setInterval、setImmediate、I/O、UI渲染、