人人都能看懂的JavaScript单线程的那点事

简介: 人人都能看懂的JavaScript单线程的那点事

如果你已经对 Javascript 很熟悉了,那么相信你一定知道它是一门“单线程”语言。

那你知道单线程意味着什么吗?

Javascript 在 V8 引擎上运行,V8 引擎具有内存堆和调用堆栈的特性。

JavaScript 是单线程的,意味着每次只执行一条语句。

在深入探讨在单线程上运行意味着什么之前,我想先解释一下基本的术语。

你首先需要了解一种叫做栈(stack) 的数据结构,它具有后进先出的特性。

同步 (sync) 执行通常是指代码按顺序执行。在同步编程中,程序会逐行执行,一次一行。每次调用函数时,程序执行都会等待,直到这个函数返回(return),然后再继续执行下一行代码。

举个例子,你正在给某人打电话,你需要等他接听之后可以和他们说话。在对方接通电话之前,你不会做任何其他事情。

这应该很好理解。

我们来下看下面的例子:


const one() => {
  const two() => {
    console.log('4');
  }   
  two(); 
} 
one();

在这段代码中,函数调用栈会发生什么?

它会像下面这样:

image.png

函数调用栈的工作是填充指令并在指令执行时弹出指令。

Javascript 虽然是一种单线程语言,但也可以是非阻塞的

单线程是指它只有一个函数调用栈,并且这个函数调用栈永远都会先运行栈顶的函数。

在上面的代码中,函数是按顺序运行的。

如果我们有一个功能需要做繁重的工作怎么办?我们应该让用户一直等到这个过程结束吗?

再来看另一个例子:


const one() {       
  console.log("Hello"); 
} 
const two () {     
  for(i=0; i<= 100000000000000000000000; i++){ } 
}
const three(){        
  console.log("World"); 
}
one(); 
two(); 
three();

在这个例子中,如果我们的 two 函数必须循环遍历很久的时间,这是否意味着 three 函数必须等到 two() 被执行?从技术上讲,是的!

在我们的例子中,它可能没什么意义,但如果我们必须在实际项目中实现类似的逻辑时,那么在这个过程完成之前用户可能无法做任何事情。

异步 (async) 执行是指不按照它在代码中出现的顺序来实际运行。在异步编程中,程序不会等待当前任务完成,而是可以继续执行下一个任务。

举个例子:你打电话给某人,在等待他接电话的同时,你也可以做点别的事情。

不同的语言有不同的方式来实现异步,最流行的是通过多线程。

Java 就是通过创建一个子线程来实现多线程,这个子线程拥有自己的函数调用栈,可以单独执行,然后再和父线程合并。

但是,这可能会遇到死锁问题,可以通过各种死锁预防机制来处理。

具体的内容不在本文讨论范围内。

我们关心的是在 Javascript 中实现异步,我们来看看它是如何做到的。

运行下面的代码,看看会发生什么。


console.log('1');
setTimeout(()=> {
  console.log('2')
}, 3000);
console.log('3');

你会先看到 1 和 3,短暂延迟后,才可以看到 2。为什么会这样?

简而言之,Javascript 中的异步实现是通过函数调用栈、回调队列和 Web API 以及事件循环来完成的。

上面我有讲到,函数调用栈的工作是检查栈顶部的指令并执行它。如果有像 setTimeout() 这样的异步任务需要额外的时间来执行,那么函数调用栈会将它弹出并把它发送到 Web API。

事件循环的工作是不断检查是否发生了某种事件,比如鼠标单击或键盘敲击,然后把它发送给函数调用栈。当然,用户的鼠标单击会比图像加载这种任务具有更高的执行优先级。

在 Javascript 中,所有指令都放在函数调用栈上。当函数调用栈碰到 setTimeout 时,引擎会把它视为 Web API 指令并把它弹出,并将其发送到 Web API。一旦 Web API 完成执行,它会重新被达回调队列。

引擎检查调用堆栈是否为空。如果它是空的,那么我们检查回调队列中包含指令 setTimeout。回调队列把它发送到函数调用栈并执行指令。

具体的流程可以参照下图:

image.png

我们再来看另一种情况,当你发送了一个 API 请求时。例如,你的网站需要从服务器获取一张图像。在图像返回之前如果网页不能加载其他内容,那将是一个糟糕的用户体验。

当函数调用栈发现它需要获取图像时,它会把这个函数弹出,并把它发送到 Web API 并继续执行剩下的函数。对图像请求的回调事件会存储在回调队列中。

当函数调用栈为空时,持续运行的事件循环会查看回调队列中是否有任务。

执行这个过程时,JavaScript 不用考虑程序是运行在具有多少个核心的 CPU 上。所以 V8 实现 JavaScript 异步只需要一个调用堆栈就够了。



相关文章
|
7月前
|
数据采集 并行计算 JavaScript
实战指南:在 Node.js 中利用多线程提升性能
在 Node.js 的世界中,多线程技术一直是一个受到广泛关注的领域。最初,Node.js 设计为单线程模式。随着技术发展,Node.js 引入了多线程支持,进而利用多核处理器的强大性能,提升了应用性能。接下来的内容将深入探讨 Node.js 如何实现多线程,以及在何种场合应该采用这种技术。
|
4月前
|
Web App开发 JavaScript 前端开发
[译] 深入理解 Node.js 中的 Worker 线程
[译] 深入理解 Node.js 中的 Worker 线程
|
6月前
|
前端开发 JavaScript
JavaScript异步处理避免了单线程阻塞,如回调函数、Promise和async/await。
【6月更文挑战第22天】JavaScript异步处理避免了单线程阻塞,如回调函数、Promise和async/await。回调是基础,用于在操作完成后执行函数;Promise管理异步状态,支持链式调用;async/await提供同步代码外观,简化错误处理。每种技术在处理耗时任务时都起着关键作用。
57 3
|
7月前
|
JavaScript 前端开发
JS 单线程还是多线程,如何显示异步操作
JS 单线程还是多线程,如何显示异步操作
58 2
|
7月前
|
消息中间件 前端开发 JavaScript
JavaScript 线程:处理高并发任务的必备知识(下)
JavaScript 线程:处理高并发任务的必备知识(下)
JavaScript 线程:处理高并发任务的必备知识(下)
|
7月前
|
前端开发 JavaScript UED
JavaScript 线程:处理高并发任务的必备知识(上)
JavaScript 线程:处理高并发任务的必备知识(上)
JavaScript 线程:处理高并发任务的必备知识(上)
|
7月前
|
消息中间件 JavaScript 前端开发
Node.js 中的线程 与 并发
Node.js 中的线程 与 并发
65 0
|
7月前
|
前端开发 JavaScript
异步编程:由于JS是单线程执行的,所以对于耗时的操作(如网络请求),需要通过异步编程来处理。回调函数、Promise、async/await都是常用的异步编程方式。
异步编程:由于JS是单线程执行的,所以对于耗时的操作(如网络请求),需要通过异步编程来处理。回调函数、Promise、async/await都是常用的异步编程方式。
104 1
|
JavaScript 前端开发
js单线程、同步、异步
什么是单线程?同步、异步的产生?
117 0
|
JavaScript 前端开发
JavaScript dom 的跨线程操作
JavaScript dom 的跨线程操作
83 0