整合營銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          JS執(zhí)行上下文的兩個(gè)階段做了些啥?

          咱們開始之前,有個(gè)問題大家可以一起討論: JS是解釋語言還是編譯語言?

          (JS)是一種解釋語言,有自己的編譯器形式,運(yùn)行在JS引擎中。

          每個(gè)web瀏覽器都有自己的JS引擎形式,盡管目的一樣。Chrome 有 v8, Mozilla 有 spider monkey等,JS引擎只是將JS源代碼轉(zhuǎn)換成編譯器能夠理解的語言,然后執(zhí)行它。

          執(zhí)行上下文

          JS 代碼運(yùn)行的環(huán)境構(gòu)成了執(zhí)行上下文,執(zhí)行上下文決定哪段代碼可以訪問變量、函數(shù)、對(duì)象等。

          1.全局執(zhí)行上下文

          任何時(shí)候碼第一次運(yùn)行,或者當(dāng)代碼不在任何函數(shù)中時(shí),它都會(huì)進(jìn)入全局執(zhí)行上下文。在整個(gè)代碼執(zhí)行過程中只有一個(gè)全局執(zhí)行上下文。

          對(duì)于瀏覽器全局執(zhí)行上下文,它做兩件事:

          1. 創(chuàng)建window對(duì)象。
          2. 將 this 指向了 windw 對(duì)象 (非嚴(yán)格模式)

          2. 函數(shù)執(zhí)行上下文

          當(dāng)函數(shù)執(zhí)行時(shí),它就創(chuàng)建一個(gè)新的函數(shù)執(zhí)行上下文,可以有任意數(shù)量的函數(shù)執(zhí)行上下文。

          執(zhí)行堆棧/調(diào)用堆棧

          瀏覽器中JS解器是單線程,同一時(shí)間只能干一件事。代碼中有一個(gè)全局的執(zhí)行上下文和無數(shù)個(gè)函數(shù)執(zhí)行上下文,那么他們是按什么順序執(zhí)行的呢?

          這里就需要一個(gè) 執(zhí)行上下文棧 的概念了,JS引擎是通過創(chuàng)建執(zhí)行上下文棧來管理執(zhí)行上下文的。這里可以把執(zhí)行上下文棧描述為一個(gè)存著函數(shù)調(diào)用的棧結(jié)構(gòu),執(zhí)行順序遵循先進(jìn)后出的原則,也就是說一個(gè)函數(shù)的執(zhí)行上下文,在函數(shù)執(zhí)行完畢之后,會(huì)被移除執(zhí)行上下文棧。

          每當(dāng)腳本在瀏覽器中加載時(shí),堆棧中的第一個(gè)元素就是全局執(zhí)行上下文。然而,當(dāng)一個(gè)函數(shù)執(zhí)行時(shí),將創(chuàng)建一個(gè)執(zhí)行上下文,并將其虛擬的放置在全局執(zhí)行上下文之上。函數(shù)一旦執(zhí)行完畢,就會(huì)從執(zhí)行堆棧中彈出并將控制權(quán)交給到它下面的上下文中。

          咱們舉個(gè)例子,來模擬上述的過程:

          步驟1:當(dāng)上述代碼加載到瀏覽器中時(shí),JS引擎創(chuàng)建一個(gè)全局執(zhí)行上下文(global execution context )并將其推入當(dāng)前執(zhí)行堆棧。

          步驟2:假設(shè)最后執(zhí)行func1()調(diào)用,然后JS引擎為該函數(shù)創(chuàng)建一個(gè)新的執(zhí)行上下文(function execution context),并將其推到全局執(zhí)行上下文的頂部。

          步驟3:在func1()中,咱們調(diào)用了func2(),因此JS引擎為該函數(shù)創(chuàng)建一個(gè)新的執(zhí)行上下文,并將其推到func1()執(zhí)行上下文的頂部。

          步驟4:當(dāng)func2()函數(shù)結(jié)束時(shí),它的執(zhí)行上下文從當(dāng)前堆棧中彈出,控制權(quán)交給它下面的執(zhí)行上下文,即func1()函數(shù)的執(zhí)行上下文。

          步驟5:當(dāng)func1()函數(shù)結(jié)束時(shí),它的執(zhí)行堆棧將從堆棧中刪除,控制權(quán)交給全局執(zhí)行上下文。執(zhí)行完所有代碼后,JS 引擎將從當(dāng)前堆棧中刪除全局執(zhí)行上下文。

          執(zhí)行上下文階段

          執(zhí)行上下文主要有兩個(gè)階段。

          1. 創(chuàng)建
          2. 執(zhí)行

          創(chuàng)建階段

          函數(shù)創(chuàng)建時(shí)做的三件事:

          1.首先,為用域鏈內(nèi)的每個(gè)函數(shù)或變量構(gòu)建到外部環(huán)境的連接。告訴執(zhí)行上下文它應(yīng)該包含什么,以及它應(yīng)該在哪里查找解析函數(shù)引用和變量值的方法。

          • 對(duì)于全局環(huán)境,外部環(huán)境為null。然而,全局環(huán)境內(nèi)的所有環(huán)境都是以全局環(huán)境作為其外部環(huán)境的。
          • 例如:如果函數(shù)a包含在函數(shù)b中,這意味著a有一個(gè)外部環(huán)境b。

          2.接著,通過掃描作用鏈,創(chuàng)建一個(gè)環(huán)境記錄,其中全局上下文的創(chuàng)建和引用(web瀏覽器中的window)、變量、函數(shù)和函數(shù)參數(shù)都在內(nèi)存中完成。

          3.最后,在第一步中創(chuàng)建的每個(gè)執(zhí)行上下文中確定this的值(對(duì)于全局執(zhí)行上下文,this指向的是window)。

          因此,咱們可以將創(chuàng)建階段表示為

          創(chuàng)建階段 = {
            scopeChain: {
                  /* 作用域鏈解析 */ 
              },    
            variableObject: {
                  /* arguments, 函數(shù)參數(shù), 內(nèi)部變量 等等*/ 
              },
            this: {},
          }

          variableObject: 初始化函數(shù)的參數(shù)variableObject,提升函數(shù)聲明和變量聲明。

          scopeChain: 在執(zhí)行上下文的創(chuàng)建階段,作用域會(huì)在變量對(duì)象創(chuàng)建之后創(chuàng)建。作用域鏈本身包括變量對(duì)象。作用域負(fù)責(zé)解析變量,當(dāng)被要求解析變量的時(shí)候,會(huì)從代碼嵌套結(jié)構(gòu)的最內(nèi)層開始,如果在最內(nèi)層沒有找到對(duì)應(yīng)變量,則依次向父級(jí)作用域中進(jìn)行查找,直到尋找到最外層作用域。

          this: 確定this的指向,這里需要注意的事this的值是在執(zhí)行的時(shí)候確定的,在定義的時(shí)候并不會(huì)確定。

          執(zhí)行階段

          這是代碼開始在創(chuàng)建階段形成的執(zhí)行上下文中運(yùn)行的階段,并逐行分配變量值。

          在執(zhí)行開始時(shí),JS 引擎在其創(chuàng)建階段對(duì)象中尋找執(zhí)行函數(shù)的引用。如果不能在自己的作用域內(nèi)找到它,它將繼續(xù)向上查找,直到到達(dá)全局環(huán)境。

          如果在全局環(huán)境中沒有找到引用,它將返回一個(gè)錯(cuò)誤。但是,如果找到了一個(gè)引用,并且函數(shù)執(zhí)行正確,那么這個(gè)特定函數(shù)的執(zhí)行上下文將從堆棧中彈出,JS 引擎將移動(dòng)到下一個(gè)函數(shù),在那里,它們的執(zhí)行上下文將被添加到堆棧中并執(zhí)行,依此類推。

          咱們通過示例查看上面的兩個(gè)階段,以便更好地理解它。

          1111

          在創(chuàng)建階段,全局執(zhí)行上下文類似于這樣

          globalExecutionObj = {
              outerEnvironmentConnection: null,
              variableObjectMapping: {
                  name: uninitialized,
                  title: undefined,
                  date: uninitialized,
                  func1: func,
              },
              this: window //Global Object
          }

          **注意:**上面,let (name)和const (date)定義的變量在創(chuàng)建階段沒有任何關(guān)聯(lián)的值,但是var (title)定義的變量會(huì)被設(shè)置為undefined。

          這就是為什么咱們可以在聲明var定義的變量之前訪問它們**(雖然沒有定義)**,但是在聲明let和 const變量之前訪問它們時(shí),會(huì)得到一個(gè)引用錯(cuò)誤。

          這就是咱們所說的變量提升,即所有使用var的變量聲明都被提升它們的局部作用域(在函數(shù)內(nèi)部聲明)或者全局作用域的頂部(在函數(shù)外部聲明的)。

          在執(zhí)行階段,完成變量分配。所以全局執(zhí)行上下文在執(zhí)行階段類似如下:

          globalExectutionObj = {
              outerEnvironmentConnection: null,
              variableObjectMapping: {
                  name: "overflowjs.com",
                  title: "Execution context",
                  date: "5 july 2019",
                  func1: pointer to function func1,
              },
              this: window //Global Object
          }

          **注意:**在執(zhí)行階段,如果JS引擎在源代碼中聲明位置找不到let變量的值,那么它將為其賦值undefined。

          現(xiàn)在,當(dāng)func1執(zhí)行,就會(huì)生成一個(gè)新的函數(shù)執(zhí)行上下文,其創(chuàng)建階段類似如下:

          func1ExecutionObj = {
              outerEnvironmentConnection: Global,
              variableObjectMapping: {
                 arguments: {
                      0: 10,
                      length: 1
                  },
                  num: 10,
          
                  author: undefined,
                  val: uninitialized,
                  func2: undefined
                  fixed: uninitialized
                  addFive: pointer to function addFive()
              },
              this: Global Object or undefined
          }

          在執(zhí)行階段類似如下:

          func1ExecutionObj = {
              outerEnvironmentConnection: Global,
              variableObjectMapping: {
                 arguments: {
                      0: 10,
                      length: 1
                  },
                  num: 10,
          
                  author: "Deepak",
                  val: 3,
                  func2: pointer to function func2() 
                  fixed: "Divine"
                  addFive: pointer to function addFive()
              },
              this: Global Object or undefined
          }

          函數(shù)執(zhí)行完成后,將更新全局環(huán)境。然后全局代碼完成,程序結(jié)束。

          作用域與作用域鏈

          1.作用域

          JavaScript中的作用域分為三種:

          1. 全局作用域
          2. 函數(shù)作用域
          3. 塊級(jí)作用域(ES6)

          作用域最大的作用就是隔離變量或函數(shù),并控制他們的生命周期。作用域是在函數(shù)執(zhí)行上下文創(chuàng)建的時(shí)候定義好的,不是在函數(shù)執(zhí)行的時(shí)候定義的。

          2.什么是作用域鏈

          當(dāng)一個(gè)塊或者函數(shù)嵌套在另一個(gè)塊或函數(shù)中時(shí),就發(fā)生了作用域的嵌套。在當(dāng)前函數(shù)中如果JS引擎無法找到某個(gè)變量,就會(huì)往上級(jí)嵌套的作用域中去尋找,直到找到該變量或抵達(dá)全局作用域,這樣的鏈?zhǔn)疥P(guān)系成為作用域鏈(Scope Chain)。

          來個(gè)例子演示一下:

          var scope = 'global';
          
          function checkscope(s) {
              var scope = 'local scope';
              
              function f() {
                  return scope;
              }
              
              return f();
          }
          
          checkScope('scope');

          首先在checkscope函數(shù)聲明的時(shí)候,內(nèi)部會(huì)綁定一個(gè)[[scope]]的內(nèi)部屬性:

          checkscope.[[scope]] = [
            globalContext.VO
          ];

          接著在checkScope函數(shù)在執(zhí)行之前,創(chuàng)建執(zhí)行上下文checkscopeContext,并推入執(zhí)行上下文棧:

          • 賦值函數(shù)的[[scope]]屬性初始化作用鏈
          • 創(chuàng)建變量對(duì)象
          • 將變量對(duì)象壓入作用域鏈的頂端
          // -> 初始化作用域鏈;
          checkscopeContext = {
            scope: [globalContext.VO],
          }
          
          // -> 創(chuàng)建變量對(duì)象
          checkscopeContext = {
            scope: [globalContext.VO],
            VO = {
              arguments: {
                0: 'scope',
                length: 1,
              },
              s: 'scope', // 傳入的參數(shù)
              f: function f(),
              scope: undefined, // 此時(shí)聲明的變量為undefined
            },
          }
          
          // -> 將變量對(duì)象壓入作用域鏈的最頂端
          checkscopeContext = {
            scope: [VO, globalContext.VO],
            VO = {
              arguments: {
                0: 'scope',
                length: 1,
              },
              s: 'scope', // 傳入的參數(shù)
              f: function f(),
              scope: undefined, // 此時(shí)聲明的變量為undefined
            },
          }

          執(zhí)行階段,修改變量對(duì)象里面對(duì)應(yīng)字段的值:

          checkscopeContext = {
            scope: [VO, globalContext.VO],
            VO = {
              arguments: {
                0: 'scope',
                length: 1,
              },
              s: 'scope', // 傳入的參數(shù)
              f: pointer to function f(),
              scope: 'local scope', // 變量賦值
            }
          }

          在代碼執(zhí)行階段,會(huì)看到f函數(shù)的聲明代碼,給f函數(shù)綁定[[scope]]屬性:

          f.[[scope]] = [
            checkscopeContext.VO, // f函數(shù)的作用域還包括checkscope的變量對(duì)象
            globalContext.VO
          ];

          文本到這就結(jié)束了,希望對(duì)大伙有所幫助。


          作者:DEEPAK GUPTA 譯者:前端小智 來源:overflowjs.com

          原文:https://overflowjs.com/posts/Javascript-Execution-Context-and-Hoisting.html

          篇文章的目的是為了讓你徹底理解 JavaScript 的執(zhí)行,如果你到本文最后還沒有理解,你可以揍我一頓。

          無論你是 JavaScript 新手還是老手,無論你是在面試工作,還是只是做常規(guī)的開發(fā)工作,通常會(huì)發(fā)現(xiàn)給定幾行代碼,你需要知道要輸出什么以及以什么順序輸出 . 由于 JavaScript 是一種單線程語言,我們可以得出以下結(jié)論:

          let a = '1';
          console.log(a);
          let b = '2';
          console.log(b);
          
          

          然而,JavaScript 實(shí)際上是這樣的:

          setTimeout(function(){
              console.log('start')
          });
          new Promise(function(resolve){
              console.log('start for');
              for(var i = 0; i < 10000; i++){
                  i == 99 && resolve();
              }
          }).then(function(){
              console.log('start then')
          });
          console.log('end');
          // Following the idea that JS executes in the order in which the statements appear, I confidently write down the output:
          // start
          // start for
          // start then
          // end
          
          

          在 Chrome 上查看它是完全錯(cuò)誤的

          一、關(guān)于 JavaScript

          JavaScript 是一種單線程語言。Web-worker 是在最新的 HTML5 中提出的,但 JavaScript 是單線程的核心保持不變。所以所有 JavaScript 版本的“多線程”都是用單線程模擬的,所有的 JavaScript 多線程都是紙老虎!

          二、JavaScript 事件循環(huán)

          由于 JavaScript 是單線程的,它就像一個(gè)只有一個(gè)窗口的銀行??蛻粜枰灰慌抨?duì)辦理業(yè)務(wù)。

          同樣,JavaScript 任務(wù)也需要一個(gè)一個(gè)地執(zhí)行。如果一項(xiàng)任務(wù)花費(fèi)的時(shí)間太長,則下一項(xiàng)也必須等待。

          那么問題來了,如果我們想瀏覽新聞,但新聞中包含加載緩慢的超高清圖像,我們的網(wǎng)頁是否應(yīng)該一直卡住直到圖像完全顯示?所以聰明的程序員將任務(wù)分為兩類:

          • 同步任務(wù)
          • 異步任務(wù)

          當(dāng)我們打開一個(gè)網(wǎng)站時(shí),頁面的渲染過程是很多同步任務(wù),比如渲染頁面骨架和頁面元素。

          需要大量時(shí)間的任務(wù),比如加載圖片和音樂,都是異步任務(wù)。這部分有嚴(yán)格的文字定義,但本文的目的是以最小的學(xué)習(xí)成本徹底理解實(shí)現(xiàn)機(jī)制,所以我們用一張圖來說明:

          文字要表達(dá)的內(nèi)容:

          同步和異步任務(wù)去不同的執(zhí)行“地方”,同步任務(wù)去主線程,異步任務(wù)去事件表和注冊(cè)函數(shù)。

          當(dāng)指定的事件完成時(shí),事件表將此函數(shù)移至事件隊(duì)列。

          如果執(zhí)行后主線程中的任務(wù)為空,事件隊(duì)列會(huì)讀取相應(yīng)的函數(shù),進(jìn)入主線程執(zhí)行。

          這個(gè)過程一遍又一遍地重復(fù),稱為事件循環(huán)。

          我們?cè)趺粗乐骶€程棧是空的?JavaScript 引擎有一個(gè)監(jiān)控進(jìn)程,不斷檢查主線程堆棧是否為空,如果是,則檢查 Event Queue 以查看是否有任何函數(shù)等待調(diào)用。

          說了這么多,不如直接寫一段代碼:

          let data = [];
          $.ajax({
              url:www.javascript.com,
              data:data,
              success:() => {
                  console.log('success!');
              }
          })
          console.log('end');
          
          

          這是一個(gè)簡單的ajax請(qǐng)求代碼:

          • ajax 去事件表并注冊(cè)回調(diào)函數(shù)成功。
          • 執(zhí)行 console.log(‘success’)。
          • ajax Event 完成,回調(diào)函數(shù)success 進(jìn)入Event Queue。
          • 主線程從事件隊(duì)列中讀取成功并執(zhí)行。

          相信通過上面的文字和代碼,你對(duì)JS的執(zhí)行順序有了初步的了解。接下來,我們來看看進(jìn)階話題:setTimeout。

          三、愛恨交加超時(shí)

          著名的 setTimeout 無需進(jìn)一步解釋。setTimeout 的第一印象是異步執(zhí)行可以延遲,我們經(jīng)常這樣實(shí)現(xiàn):

          setTimeout(() => {
           console.log(‘Delay 3 seconds’);
          },3000)
          
          

          當(dāng) setTimeout 用得越來越多時(shí),問題也出現(xiàn)了。有時(shí)函數(shù)會(huì)在 3 秒的書面延遲后 5 或 6 秒內(nèi)執(zhí)行。怎么了?

          讓我們從一個(gè)例子開始:

          setTimeout(() => {
              task();
          },3000)
          console.log('console');
          

          按照我們之前的結(jié)論,setTimeout是異步的,應(yīng)該先執(zhí)行console.log。

          //console
          //task()
          

          去看看吧!這是正確的!然后我們修改之前的代碼:

          復(fù)制setTimeout(() => {
              task()
          },3000)
          sleep(10000000)
          
          

          控制臺(tái)上的 task() 在 Chrome 中執(zhí)行需要超過 3 秒的時(shí)間。

          此時(shí),我們需要重新思考setTimeout的定義。

          先說上面的代碼是如何執(zhí)行的:

          • task() 進(jìn)入事件表并注冊(cè),定時(shí)器啟動(dòng)。
          • 執(zhí)行sleep,非常慢,非常慢,計(jì)時(shí)繼續(xù)。
          • task()進(jìn)入Event Queue,但是,sleep太慢無法執(zhí)行。
          • sleep終于結(jié)束了,task()終于從Event Queue執(zhí)行到主線程。

          上述過程完成后,我們知道setTimeout是一個(gè)在指定時(shí)間后將任務(wù)添加到Event Queue(本例中為task())的函數(shù)。

          而且,由于是單線程任務(wù),需要一個(gè)一個(gè)執(zhí)行,如果上一個(gè)任務(wù)耗時(shí)過長,我們只能等待。導(dǎo)致實(shí)際延遲超過 3 秒。

          SetTimeout(fn,0) 是我們經(jīng)常遇到的另一個(gè)代碼??梢粤⒓赐瓿蓡??

          SetTimeout (fn,0) 指定任務(wù)將在主線程上最早可用的空閑時(shí)間執(zhí)行。這意味著一旦堆棧中的所有同步任務(wù)完成并且堆棧為空,主線程將立即執(zhí)行。例如:

          //code1
          console.log('one');
          setTimeout(() => {
              console.log('two')
          },0);
          // result
          // one  
          // two
          //code2
          console.log('one');
          setTimeout(() => {
              console.log('two')
          },3000);
          // result
          // one
          // ... 3s later
          // two
          

          關(guān)于 setTimeout 要補(bǔ)充的一點(diǎn)是,即使主線程是空的,0 毫秒實(shí)際上也是無法到達(dá)的。根據(jù) HTML 標(biāo)準(zhǔn),最小值為 4 毫秒。有興趣的同學(xué)可以自行了解。

          四、恨與愛setInterval

          說了 setTimeout,你不能錯(cuò)過它的孿生兄弟 setInterval。它們是相似的,只是后者是循環(huán)執(zhí)行。對(duì)于執(zhí)行順序,setInterval 將按指定的時(shí)間間隔將注冊(cè)的函數(shù)放入事件隊(duì)列中。如果上一個(gè)任務(wù)耗時(shí)過長,也需要等待。

          唯一需要注意的是,對(duì)于 setInterval(fn,ms),我們已經(jīng)知道不是每 ms 秒執(zhí)行一次 fn,而是每 ms 秒進(jìn)入 Event Queue。一旦 setInterval 的回調(diào) fn 花費(fèi)的時(shí)間超過了延遲 ms,時(shí)間間隔就完全不可見了。請(qǐng)讀者細(xì)細(xì)品味這句話。

          五、Promise 和 process.nextTick(callback)

          我們已經(jīng)看過傳統(tǒng)的計(jì)時(shí)器,然后,我們將探討 Promise 與 process.Nexttick(回調(diào))的性能。

          Promise 的定義和功能這里就不介紹了,process.nexttick(回調(diào))類似于node.js 版本的“setTimeout”,在事件循環(huán)的下一次迭代中調(diào)用回調(diào)函數(shù)。

          我們開始談?wù)掳?。除了廣義的同步和異步任務(wù),我們對(duì)任務(wù)有更詳細(xì)的定義:

          • 宏任務(wù):包括整個(gè)代碼腳本、setTimeout 和 setInterval
          • 微任務(wù):Promise、process.nexttick

          不同類型的任務(wù)會(huì)進(jìn)入對(duì)應(yīng)的Event Queue。例如,setTimeout 和 setInterval 將進(jìn)入同一個(gè)事件隊(duì)列。

          事件循環(huán)的順序決定了 JS 代碼的執(zhí)行順序。輸入整體代碼(宏任務(wù))后,第一個(gè)循環(huán)開始。然后,執(zhí)行所有微任務(wù)。然后再從宏任務(wù)開始,找一個(gè)任務(wù)隊(duì)列完成,然后,執(zhí)行所有的微任務(wù)。如果聽起來有點(diǎn)繞,我們用本文開頭的代碼來說明:

          setTimeout(function() {
              console.log('setTimeout');
          })
          new Promise(function(resolve) {
              console.log('promise');
          }).then(function() {
              console.log('then');
          })
          console.log('console');
          
          • 此代碼作為宏任務(wù)進(jìn)入主線程。
          • 當(dāng)遇到 setTimeout 時(shí),將其回調(diào)函數(shù)注冊(cè)并分發(fā)到宏任務(wù) Event Queue。(注冊(cè)過程同上,下面不再贅述)。
          • 然后,遇到一個(gè) Promise,立即執(zhí)行 New Promise,然后將 then 函數(shù)分派到微任務(wù)事件隊(duì)列中。如果遇到console.log(),立即執(zhí)行。
          • 好的,整個(gè)腳本作為第一個(gè)宏任務(wù)執(zhí)行。什么是微任務(wù)?我們發(fā)現(xiàn) then 是在 microtask Event Queue 中執(zhí)行的。
          • 好了,第一輪的 Event loop 已經(jīng)結(jié)束了,讓我們開始第二輪,當(dāng)然是從宏任務(wù) Event Queue 開始。我們?cè)诤耆蝿?wù)Event Queue中找到setTimeout對(duì)應(yīng)的回調(diào)函數(shù),立即執(zhí)行。
          • 結(jié)束。

          事件循環(huán)、宏任務(wù)和微任務(wù)的關(guān)系如下圖所示:

          讓我們看一些更復(fù)雜的代碼,看看你是否真的了解 JS 的工作原理:

          console.log('1');
          setTimeout(function() {
              console.log('2');
              process.nextTick(function() {
                  console.log('3');
              })
              new Promise(function(resolve) {
                  console.log('4');
                  resolve();
              }).then(function() {
                  console.log('5')
              })
          })
          process.nextTick(function() {
              console.log('6');
          })
          new Promise(function(resolve) {
              console.log('7');
              resolve();
          }).then(function() {
              console.log('8')
          })
          setTimeout(function() {
              console.log('9');
              process.nextTick(function() {
                  console.log('10');
              })
              new Promise(function(resolve) {
                  console.log('11');
                  resolve();
              }).then(function() {
                  console.log('12')
              })
          })
          
          

          第一輪事件循環(huán)流程分析如下:

          整個(gè)腳本作為第一個(gè)宏任務(wù)進(jìn)入主線程,遇到console.log,打印1。

          • 當(dāng)遇到 setTimeout 時(shí),它的回調(diào)函數(shù)被調(diào)度到宏任務(wù)事件隊(duì)列。我們稱之為 setTimeout1。
          • 當(dāng)遇到 process.nexttick() 時(shí),將其回調(diào)函數(shù)調(diào)度到微任務(wù)事件隊(duì)列中。我們稱它為 process1。
          • 如果遇到 Promise,直接執(zhí)行新的 Promise,打印 7,然后,分發(fā)到微任務(wù) Event Queue,讓我們稱之為then1。
          • 再次遇到setTimeout,它的回調(diào)函數(shù)被分發(fā)到宏任務(wù)Event Queue中,我們稱之為setTimeout2。

          • 上表展示了第一輪Event loop的宏任務(wù)結(jié)束時(shí)各個(gè)Event Queue的情況。這時(shí)候已經(jīng)輸出了1和7。
          • 我們找到了兩個(gè)微任務(wù) process1 和 then1。
          • 執(zhí)行process1,輸出6。
          • 執(zhí)行 then1 print 8。

          好了,第一輪事件循環(huán)正式結(jié)束,本輪結(jié)果輸出1,7,6,8。所以第二個(gè)時(shí)間循環(huán)從 setTimeout1 宏任務(wù)開始:

          首先,print2。接下來是process.nexttick(),它也被分派到微任務(wù)事件隊(duì)列中,稱為process2。新的 Promise 立即執(zhí)行輸出 4,然后也被分發(fā)到微任務(wù)事件隊(duì)列中,記為 then2。

          • 第二輪事件循環(huán)宏任務(wù)完成,我們發(fā)現(xiàn) process2 和 then2 微任務(wù)可以執(zhí)行。
          • 3的輸出。
          • 5的輸出。
          • 第二個(gè)事件循環(huán)結(jié)束,第二輪輸出2,4,3,5。
          • 第三個(gè)事件循環(huán)開始,此時(shí)只剩下setTimeout2,執(zhí)行。
          • 所以它只print 9。
          • 將 process.nexttick() 分發(fā)到微任務(wù)事件隊(duì)列。記得process 3。
          • 只需執(zhí)行 new Promise,print 11。
          • then 分配 microtask Event Queue,記為 then3。

          • 第三輪事件循環(huán)宏任務(wù)執(zhí)行完成,執(zhí)行兩個(gè)微任務(wù)process3和then3。
          • 10 的輸出。
          • 12的輸出。
          • 第三輪事件循環(huán)結(jié)束。第三輪輸出9,11,10,12。

          整個(gè)代碼,一共經(jīng)過了3次事件循環(huán),完整的輸出為1,7,6,8,2,4,3,5,9,11,10,12。

          六、寫在最后

          1、異步 JavaScript

          我們從一開始就說過 JavaScript 是單線程語言,無論什么新的框架和語法實(shí)現(xiàn)被稱為異步,實(shí)際上都是以同步的方式模擬的,所以牢牢掌握單線程很重要。

          2、事件循環(huán)

          事件循環(huán)是實(shí)現(xiàn)異步 JavaScript 的一種方法,也是 JavaScript 的執(zhí)行機(jī)制。

          3、JavaScript的執(zhí)行和運(yùn)行

          在 Node.js、瀏覽器、Ringo 等不同的環(huán)境中執(zhí)行和運(yùn)行 JavaScript 是有很大區(qū)別的。雖然運(yùn)行多指 JavaScript 解析引擎,但它是統(tǒng)一的。

          4、立即設(shè)置

          還有許多其他類型的微任務(wù)和宏任務(wù),例如 setImmediate,它們不進(jìn)行中介。

          5、在一天結(jié)束時(shí)

          JavaScript 是一種單線程語言。事件循環(huán)是 JavaScript 的執(zhí)行機(jī)制。

          牢牢把握兩個(gè)基本點(diǎn),以認(rèn)真學(xué)習(xí)JavaScript為中心,早日實(shí)現(xiàn)成為前端高手的偉大夢(mèng)想!

          來源: WEB前端開發(fā)社區(qū)

          在JavaScript的世界里,事件循環(huán)(Event Loop)是一個(gè)核心概念,它決定了JavaScript代碼的執(zhí)行順序,尤其是異步代碼。理解事件循環(huán)對(duì)于編寫高效、響應(yīng)迅速的JavaScript程序至關(guān)重要。本文將深入探討事件循環(huán)的原理,并通過實(shí)際代碼示例展示其在JavaScript編程中的應(yīng)用。

          1. 理解事件循環(huán)

          1.1 JavaScript的執(zhí)行模型

          JavaScript有一個(gè)基于單線程的事件循環(huán)執(zhí)行模型。這意味著JavaScript代碼在一個(gè)單獨(dú)的線程上執(zhí)行,一次只能執(zhí)行一個(gè)任務(wù)。為了處理高延遲操作(如I/O),JavaScript采用了異步編程模型。

          1.2 任務(wù)隊(duì)列

          JavaScript中的任務(wù)分為兩種:宏觀任務(wù)(macrotasks)和微觀任務(wù)(microtasks)。宏觀任務(wù)包括例如setTimeout、setInterval、I/O操作等。微觀任務(wù)則包括例如Promise的回調(diào)、MutationObserver等。

          1.3 事件循環(huán)的工作流程

          事件循環(huán)的工作流程大致如下:

          1. JavaScript引擎首先執(zhí)行全局腳本(main script)。
          2. 當(dāng)執(zhí)行到異步代碼時(shí)(如setTimeout),這些異步操作會(huì)被添加到相應(yīng)的任務(wù)隊(duì)列中。
          3. 一旦全局腳本執(zhí)行完畢,事件循環(huán)開始工作,首先處理所有完成的微觀任務(wù)。
          4. 處理完所有微觀任務(wù)后,事件循環(huán)選擇一個(gè)宏觀任務(wù)隊(duì)列中的任務(wù),并執(zhí)行它。
          5. 重復(fù)步驟3和4。

          2. 事件循環(huán)的實(shí)際應(yīng)用

          2.1 setTimeout和setInterval

          示例代碼:

          console.log('開始');
          
          setTimeout(() => {
            console.log('setTimeout');
          }, 0);
          
          console.log('結(jié)束');
          

          輸出順序:開始 -> 結(jié)束 -> setTimeout

          2.2 Promise和async/await

          示例代碼:

          console.log('開始');
          
          Promise.resolve().then(() => {
            console.log('Promise');
          });
          
          console.log('結(jié)束');
          

          輸出順序:開始 -> 結(jié)束 -> Promise

          2.3 宏觀任務(wù)和微觀任務(wù)的交互

          示例代碼:

          console.log('開始');
          
          setTimeout(() => {
            console.log('setTimeout 1');
            Promise.resolve().then(() => {
              console.log('Promise 1');
            });
          }, 0);
          
          setTimeout(() => {
            console.log('setTimeout 2');
          }, 0);
          
          Promise.resolve().then(() => {
            console.log('Promise 2');
          });
          
          console.log('結(jié)束');
          

          輸出順序:開始 -> 結(jié)束 -> Promise 2 -> setTimeout 1 -> Promise 1 -> setTimeout 2

          3. 事件循環(huán)的性能考量

          雖然事件循環(huán)使得JavaScript能夠高效地處理異步操作,但在編寫代碼時(shí),應(yīng)避免過多地使用微觀任務(wù),特別是在性能敏感的應(yīng)用中。此外,理解事件循環(huán)對(duì)于調(diào)試異步代碼也非常重要。

          總結(jié)

          事件循環(huán)是JavaScript中一個(gè)核心的概念,它決定了異步代碼的執(zhí)行順序。通過理解事件循環(huán),我們可以更有效地編寫和管理異步操作,從而創(chuàng)建響應(yīng)迅速且高效的JavaScript程序。在實(shí)際應(yīng)用中,無論是使用setTimeout、Promise,還是async/await,事件循環(huán)都扮演著至關(guān)重要的角色。然而,在享受事件循環(huán)帶來的便利的同時(shí),也需要注意性能和代碼結(jié)構(gòu)的問題,確保程序的高效運(yùn)行和可維護(hù)性。


          主站蜘蛛池模板: 亚洲成a人一区二区三区| 国产一区二区视频在线观看| 国产SUV精品一区二区88| 国产精品伦子一区二区三区| 一区二区三区在线免费观看视频| 精品成人乱色一区二区| 在线观看午夜亚洲一区| 久久一区二区三区精华液使用方法| 蜜臀AV免费一区二区三区| 国产精品第一区第27页| 国产精品综合一区二区三区| 日本在线视频一区| 久久久久久人妻一区二区三区| 亚洲av色香蕉一区二区三区 | 一区二区视频传媒有限公司| 日韩视频免费一区二区三区| 亚洲色精品VR一区区三区| 天堂资源中文最新版在线一区 | 国产情侣一区二区| 人妻av无码一区二区三区| 亚洲V无码一区二区三区四区观看| 亚欧色一区W666天堂| 91亚洲一区二区在线观看不卡| 中文字幕在线看视频一区二区三区 | 中文字幕一区二区三区在线播放| 国产成人精品a视频一区| 免费人妻精品一区二区三区| 精品女同一区二区三区免费播放| 伊人精品视频一区二区三区| 久久无码精品一区二区三区| 亚洲线精品一区二区三区| 精品一区二区视频在线观看| 88国产精品视频一区二区三区| 欧洲精品免费一区二区三区| 国产乱码精品一区二区三区四川| 国产亚洲自拍一区| 亚洲AV乱码一区二区三区林ゆな | 国产成人精品无码一区二区| 亚洲AV噜噜一区二区三区| 欧洲精品码一区二区三区| 国产一区二区三精品久久久无广告|