首頁 > web前端 > js教程 > 主體

經典技巧之JavaScript的單執行緒與非同步

WBOY
發布: 2022-02-03 07:00:30
轉載
2025 人瀏覽過

這篇文章為大家帶來了關於JavaScript中單執行緒和非同步的相關知識,希望對大家有幫助。

經典技巧之JavaScript的單執行緒與非同步

寫這篇文章呢,也是查閱的很多文章,但是大部分寫的都很簡單,而且概念性的東西都很模糊,於是我就找了有些課程聽了一下,做了一些筆記,在這我就簡單總結一下,方便以後複習~

 進程與執行緒

1. 進程:程式的一次執行, 它佔有一片獨有的記憶體空間 ---- 可以透過windows任務管理器檢視進程;

#2. 執行緒: 是進程內的一個

獨立

執行單元;是程式執行的一個完整流程;CPU的基本調度單位;

3. 行程與執行緒的關係:

  * 一個行程中一般至少有一個運作的執行緒:

主執行緒

-- 行程啟動後自動建立;  * 一個行程中也可以同時執行多個執行緒, 我們會說程式是

多執行緒

運行的;

  * 一個行程內的資料可以供其中的多個執行緒直接共享;

  * 多個進程之間的資料是不能直接共享的

4.瀏覽器運行是單一進程還是多進程?

  * 有的是單一進程

    * firefox

  * 有的是多重流程

    * chrome

##5. 如何檢視瀏覽器是否為進程運行的呢?

  * 任務管理器==>進程

6. 瀏覽器運行是單執行緒還是多執行緒?


  * 都是多執行緒運行的

#單執行緒

1、什麼是單執行緒

JavaScript語言的一大特點就是單線程,也就是說同一時間只能做一件事。

//栗子
console.log(1)
console.log(2)
console.log(3)
//输出顺序 1 2 3
登入後複製
    2. JavaScript為什麼是單線程
  • 首先是歷史原因,在創建javascript 這門語言時,多進程、多線程的架構並不流行,硬體支援並不好。
  • 其次是因為多執行緒的複雜性,多執行緒操作需要加鎖,編碼的複雜性會增加​​。
最後與它的用途有關,作為瀏覽器腳本語言,JavaScript的主要用途是與使用者互動,以及操作DOM,如果同時操作DOM ,在多執行緒不加鎖的情況下,最終會導致DOM 渲染的結果不可預期。


為了利用多核心CPU的運算能力,HTML5提出Web Worker標準,允許JavaScript腳本建立多個線程,但是子執行緒完全受主執行緒控制,且不得操作DOM。所以,這個新標準並沒有改變JavaScript單執行緒的本質。

同步與非同步

1、JS的同步任務/非同步任務同步任務: 在主執行緒上排隊執行的任務,只有一個任務執行完畢,才能執行

一個任務;                 所有同步任務在主執行緒上執行,形成一個

執行堆疊

(execution context stack)。  非同步任務:主執行緒外執行的任務;在主執行緒之外還存在一個「任務佇列」(task queue ),當非同步任務執行完成後會以回調函數的方式放入任務佇列中等待,等主執行緒空閒時,主執行緒就會去事件佇列中取出等待的回呼函數放入主執行緒中進行執行。這個過程反覆執行就形成了js的

事件循環機制

(Event Loop)。

//栗子
// 同步
console.log(1)

// 异步
setTimeout(()=>{
    console.log(2)
},100)

// 同步
console.log(3)

//输出顺序 1 3 2
登入後複製

2、 JavaScript為什麼需要非同步如果在JS程式碼執行過程中,某段程式碼執行過久,後面的程式碼遲遲不能執行,產生

阻斷

# (即卡死),會影響使用者體驗。

3、JavaScript怎麼實作非同步 

1)執行端與任務佇列 其實上面我們已經提到了,JS實作異步時透過

事件循環

;

###我們先理解幾個概念:###
  • JS任务 分为同步任务(synchronous)和异步任务(asynchronous)
  • 同步任务都在 JS引擎线程(主线程) 上执行,形成一个执行栈(call stack)
  • 事件触发线程 管理一个 任务队列(Task Queue)
  • 异步任务 触发条件达成,将 回调事件 放到任务队列(Task Queue)中
  • 执行栈中所有同步任务执行完毕,此时JS引擎线程空闲,系统会读取任务队列,将可运行的异步任务回调事件添加到执行栈中,开始执行

 当一个JS文件第一次执行的时候,js引擎会 解析这段代码,并将其中的同步代码 按照执行顺序加入执行栈中,然后从头开始执行。如果当前执行的是一个方法,那么js会向执行栈中添加这个方法的执行环境,然后进入这个执行环境继续执行其中的代码。当这个执行环境中的代码 执行完毕并返回结果后,js会退出这个执行环境并把这个执行环境销毁,回到上一个方法的执行环境。这个过程反复进行,直到执行栈中的代码全部执行完毕。

栗子 

//(1)
console.log(1)

//(2)
setTimeout(()=>{
    console.log(2)
},100)

//(3)
console.log(3)
登入後複製
  1. 先解析整段代码,按照顺序加入到执行栈中,从头开始执行
  2. 先执行(1),是同步的,所以直接打印 1
  3. 执行(2),发现是 setTimeout,于是调用浏览器的方法(webApi)执行,在 100ms后将 console.log(2) 加入到任务队列
  4. 执行(3),同步的,直接打印 3
  5. 执行栈已经清空了,现在检查任务队列,(执行太快的话可能此时任务队列还是空的,没到100ms,还没有将(2)的打印加到任务队列,于是不停的检测,直到队列中有任务),发现有 console.log(2),于是添加到执行栈,执行console.log(2),同步代码,直接打印 2 (如果这里是异步任务,同样会再走一遍循环:-->任务队列->执行栈)

所以结果是 1 3 2;

注意:setTimeout/Promise等我们称之为任务源。而进入任务队列的是他们指定的回调;

2)宏任务(macro task)与微任务(micro task)

 上面的循环只是一个宏观的表述,实际上异步任务之间也是有不同的,分为 宏任务(macro task) 与 微任务(micro task),最新的标准中,他们被称为 task与 jobs

  • 宏任务有哪些:script(整体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering(渲染)
  • 微任务有哪些:process.nextTick, Promise, Object.observe(已废弃), MutationObserver(html5新特性)

下面我们再详细讲解一下执行过程:

 执行栈在执行的时候,会把宏任务放在一个宏任务的任务队列,把微任务放在一个微任务的任务队列,在当前执行栈为空的时候,主线程会 查看微任务队列是否有事件存在。如果微任务队列不存在,那么会去宏任务队列中 取出一个任务 加入当前执行栈;如果微任务队列存在,则会依次执行微任务队列中的所有任务,直到微任务队列为空(同样,是吧队列中的事件加到执行栈执行),然后去宏任务队列中取出最前面的一个事件加入当前执行栈...如此反复,进入循环。

注意:

  • 宏任务和微任务的任务队列都可以有多个
  • 当前执行栈执行完毕时会立刻先处理所有微任务队列中的事件,然后再去宏任务队列中取出一个事件。同一次事件循环中,微任务永远在宏任务之前执行。
  • 不同的运行环境 循环策略可能有不同,这里探讨chrome、node环境

 栗子

//(1)
setTimeout(()=>{
    console.log(1)   // 宏任务
},100)

//(2)
setTimeout(()=>{
    console.log(2)  // 宏任务
},100)

//(3)
new Promise(function(resolve,reject){
    //(4)
    console.log(3)  // 直接打印
    resolve(4)
}).then(function(val){
    //(5)
    console.log(val); // 微任务
})

//(6)
new Promise(function(resolve,reject){
    //(7)
    console.log(5)   // 直接打印
    resolve(6)
}).then(function(val){
    //(8)
    console.log(val);  // 微任务
})

//(9)
console.log(7)  // 直接打印

//(10)
setTimeout(()=>{
    console.log(8) // 宏任务,单比(1)(2)宏任务早
},50)
登入後複製

 上面的代码在node和chrome环境的正确打印顺序是 3 5 7 4 6 8 1 2

下面分析一下执行过程:

  1. 全部程式碼在解析後加入執行堆疊
  2. 執行(1),巨集任務,呼叫webapi setTimeout,這個方法會在100ms後將回呼函數放入巨集任務的任務佇列
  3. 執行(2),同(1),但是會比(1)稍後一點
  4. 執行(3),同步執行new Promise,然後執行(4),直接列印3 ,然後resolve(4),然後.then(),把(5)放入微任務的任務隊列
  5. 執行(6),同上,先打印5 ,再執行resolve(6),然後.then( )裡面的內容(8)加入到微任務的任務佇列
  6. 執行(9),同步程式碼,直接列印7
  7. 執行(10),同(1)和(2) ,只是時間更短,會在50ms 後將回調console.log(8) 加入巨集任務的任務佇列
  8. 現在執行堆疊清空了,開始檢查微任務佇列,發現(5),加入執行堆疊執行,是同步程式碼,直接列印4
  9. 任務佇列又執行完了,又檢查微任務佇列,發現(8),列印6
  10. 任務佇列又執行完了,檢查微任務隊列,沒有任務,再檢查宏任務隊列,此時如果超過了50ms的話,會發現console.log(8) 在宏任務隊列中,於是執行打印8
  11. 依次打印1 2

註:因為渲染也是巨集任務,需要在一次執行堆疊執行完後才會執行渲染,所以如果執行堆疊中同時有幾個同步的改變同一個樣式的程式碼,在渲染時只會渲染最後一個。

相關推薦:javascript學習教學

#

以上是經典技巧之JavaScript的單執行緒與非同步的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:csdn.net
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板