js是一个单线程非阻塞的脚本语言。
单线程意味着,javascript代码在执行的任何时候,都只有一个主线程来处理所有的任务。
非阻塞则是当代码需要进行一项异步任务(无法立刻返回结果,需要花一定时间才能返回的任务,如I/O事件)的时候,主线程会挂起(pending)这个任务,然后在异步任务返回结果的时候再根据一定规则去执行相应的回调。
为什么要单线程?因为js是和浏览器交互的,需要操作各种dom, 如果javascript是多线程的,那么当两个线程同时对dom进行一项操作,例如一个向其添加事件,而另一个删除了这个dom,此时该如何处理呢?因此,为了保证不会 发生类似于这个例子中的情景,javascript选择只用一个主线程来执行代码,这样就保证了程序执行的一致性。
非阻塞如何实现,通过--------event loop(事件循环)。
执行栈(先进后出):
当一个js文件执行的时候,会产生一个全局执行上下文,每调用一个函数会产生一个新的执行上下文,全局执行上下文会先放入栈中,然后执行一个函数时会把这个上下文放进栈中,然后进入函数中进行执行,遇到新的函数执行会把新的执行上下文再次放入栈中,然后执行函数里面的代码,知道代码执行完毕,会把对应执行上下文(出栈)销毁,等待垃圾回收。
全局上下文只有唯一的一个,它在浏览器关闭时出栈。
console.log(1);
function pFn() {
console.log(2);
(function cFn() {
console.log(3);
}());
console.log(4);
}
pFn();
console.log(5);
//输出:1 2 3 4 5
以上的过程说的都是同步代码的执行。
那么当一个异步代码(如发送ajax请求数据)执行后会如何呢?前文提过,js的另一大特点是非阻塞,实现这一点的关键在于下面要说的这项机制——事件队列(Task Queue)。
遇到js异步事件不会马上执行,会把这个异步事件挂起,接着执行 执行栈中的其他任务。当一个异步事件返回结果后,js会把这个异步事件放入事件队列(先进先出)中,被放入的异步事件不会马上执行,而是等待当前执行栈中的所有任务都执行完毕, 主线程处于闲置状态时,主线程会去查找事件队列是否有任务。如果有,那么主线程会从中取出排在第一位的事件,并把这个事件对应的回调放入执行栈中,然后执行其中的同步代码,这样就形成了一个无限的循环。这就是这个过程被称为“事件循环(Event Loop)”的原因。
stack表示我们所说的执行栈,web apis则是代表一些异步事件,而callback queue即事件队列。