线程和异步
进程
当一个应用程序运行时,需要使用内存和CPU资源,这些资源需要向操作系统申请,操作系统以进程的方式来分配这些资源,一个进程就代表着一块其他进程的内存空间
一个应用程序要运行,必须至少有一个进程启动
进程的最大的特点就是独立,一个进程不能随意访问其他进程的资源。这就保证了多个程序在操作系统上运行互不干扰。
线程
任何一个进程在启动的时候,操作系统都会给其分配一个线程,应用程序的入口函数在主线程中运行。
在应用程序的运行过程中,可能有多个任务需要同时执行,于是可以向操作系统申请分配更多的线程来执行不同的任务。
比如,浏览器启动后,会开启多个线程来处理不同的事情。
浏览器进程中的多个线程
- 执行线程
- GUI
- 事件监听
- 计时
- 网络
进程与进程之间的资源是隔离的,不共享资源。线程之间的资源不是隔离的,它们可以共享数据,并且线程可以被调度。
<h1 id="title">Bella</h1>
<button onclick="test()">click me</button>
<script>
function test(){
title.innerHTML = "青霞";
while(true){}
}
</script>
这段代码执行完后页面中的Bella并不能变成青霞,因为while一直在循环,执行线程还没有执行完GUI(渲染)线程是不会开始的。因为浏览器中的执行线程和GUI线程就是被调度为互斥的,当GUI线程执行渲染时,执行线程会被阻塞。
我们所说的 [ JS中是单线程 ] 的语言,是指在宿主环境中,执行JS代码的线程只有一个
面试题
1、怎样理解JS的异步?
JS是一个单线程的语言,意味着宿主仅为其分配了一个执行线程
而在实际的开发中,JS有时需要执行一些耗时的操作,比如等待一个DOM事件发生、等待网络通信完成、等待计时结束等等。如果在执行线程上去等待,就浪费线程的宝贵执行时间,阻塞后续操作。更可怕的是,由于浏览器的GUI线程和JS线程是互斥的,这就导致浏览器界面会JS的等待处于卡死状态。
因此,JS通过异步来解决这个问题,当需要等待的时候,通知宿主的其他线程去做处理,执行线程则继续后续执行。当其他线程完成处理后,会发出通知,此时执行线程转而去执行事先定义好的回调函数即可。
异步的方式充分解放了执行线程,让执行线程可以毫无阻塞的运行,也就避免了浏览器宿主因为等待操作完成出现卡死现象
执行栈
function A(){
console.log("A");
B();
}
function B(){
console.log("B");
}
A();
console.log("global")
//输出顺序为A、B、global
//执行过程为:
/**
执行栈里面先有一个全局上下文,接着走到A(),创建一个函数调用执行上下文,并且入栈,函数执行的时候会从call stack里取栈顶的执行,接着运行A函数内部,console.log("A");会创建log执行上下文,输出A后,log执行上下文出栈,继续执行A,再创建B函数执行上下文,接着运行......
**/
执行过程
执行栈(call stack):在执行栈中一开始会有全局上下文Global Context(可以认为整个script中的代码),每一次遇见函数调用,就会创建一个新的上下文,叫做函数调用执行上下文。然后入栈(栈的顶端),js执行只会栈顶端的上下文。
面试题
function A(){
A();
}
A();//报错——会导致执行栈溢出 因为一直在创建A的执行栈上下文
function B(){
var n = 0;
while(n>=0){
n++
}
}
B();//进入了死循环
事件循环
setTimeout(function func1(){
console.log(1);
a();
},0)
function a(){
setTimeout(function func2(){
console.log(2)
},0)
console.log(3)
}
a();
console.log(4);
执行过程:
先创建全局执行上下文,依次执行代码,创建setTimeout fun1()上下文,时间到了后将其进入执行队列,接着创建a的执行上下文,接着执行a里面的代码,再创建setTimeout func2执行上下文,将其放入执行队列中等待执行,接着输出3,将a函数执行上下文移出call stack,输出4,call stack中已经清空了,最后将执行队列里的任务放到call stack中,先放入 func1,输出1后,再创建a函数执行上下文,再创建setTimeout的执行上下文,最后将setTimeout移入执行队列中,当call stack中空了就会运行执行队列里的代码。
最后输出3、4、1、3、2、2