整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          HTML5技術 引用類型與基本類型

          HTML5技術 引用類型與基本類型

          文將從以下六個方面講解引用類型和基本類型

          1. 概念

          2. 內存圖

          3. 引用類型和基本類型作為函數的參數體現的區別

          4. 引用類型的優點:

          5. 引用類型的賦值(對比基本類型)

          6. 淺拷貝和深拷貝

          以下為詳細內容:

          1. 概念:

          基本類型也叫簡單類型,存儲的數據是單一的,如:學生的個數就是一個數字而已;引用類型也叫復雜類型,存儲的數據是復雜的,如:學生,包括學號,姓名,性別,年齡等等很多信息。從內存(大家如果不懂內存,請查閱相關資料)的角度來說:基本類型只占用一塊內存區域;引用類型占用兩塊內存區域。即定義基本類型的變量時,在內存中只申請一塊空間,變量的值直接存放在該空間;定義引用類型的變量時(容易理解的是,我門看到new運算符,一般就是定義引用類型的變量),在內存中申請兩塊空間,第一塊空間存儲的是第二塊空間的地址,第二塊空間存儲的是真正的數據;第一塊空間叫作第二塊空間的引用(地址),所以叫作引用類型。

          javaScript中的基本類型包括:數字(Number),字符串(String),布爾(Boolean),Null,Undefined五種;

          javascript的引用類型是:Object。而Array,Date是屬于Obejct類型。

          2. 內存圖:

          如下代碼(都是定義了兩個局部變量):

          以上兩行代碼的內存圖:

          可以看到,num變量只占用了一塊內存區域;arr變量占用了兩塊內存區域,arr變量在棧區(不懂棧區的人,先不要想太多)申請了一塊內存區域,存儲著地址,存儲的地址是堆區的地址。而堆區中真正才存儲著數據,所以說,arr變量占用了兩塊內存區域。這樣看來,引用類型的變量好像還占用內存多了。哈哈,不要著急,后面了解了引用類型的優點后,你就會覺得這是問題了。

          當我們讀取num變量的值時,直接就能讀到,但是當我們要讀取arr里的值時,先找到arr中的地址,然后根據地址再找到對應的數據。

          引用類型,類似于windows操作系統中的快捷方式。快捷方式就是一個地址,真正的內容是快捷方式所指向的路徑的內容。如:我們把d:\t.txt文件創建一個快捷方式放在桌面上,那么,桌面上的快捷方式會占用桌面的空間,而d:\t.txt會占用d盤的空間,所以,占用了兩塊空間。

          基本類型就相當于文件。

          引用類型,類似于我們在入學報名填寫報名表時,填寫家庭地址,這個家庭地址就相當于第一塊空間,真正你家(第二塊內存空間)不在報名表上。學校要找你家,先在報名表上找到你家的地址,然后根據地址,才能找到你家去。

          3. 引用類型的優點:

          引用類型作為函數的參數時,優點特別明顯,第一,形參傳遞給實參時,只需要傳遞地址,而不需要搬動大量的數據(節約了內存開銷);第二,形參對應的數據改變時,實參對應的數據也在改變(很多時候,我們希望這樣)。

          如以下代碼:

          先定義函數(冒泡排序)



          當調用冒泡排序時,

          看看內存以上代碼執行時的,內存變化:

          圖中,當執行,①對應的代碼(var arr1=[250,2,290,35,12,99];)時,內存中會產生①對應的變化,即在棧中申請一塊內存區域,起名為arr1,在堆區中申請內存空間放置250,2,290,35,12,99,并把堆區中的內存的地址賦給arr1的內存中;當執行②對應的代碼bubble(arr1)時,調用函數。這時候會定義形參arr(內存中③對應的變化),即在棧中申請一塊內存區域,起名為arr,并把arr1保存的地址賦給了arr(內存中②表示的賦值),這樣,形參arr和實參arr1就指向了同一塊內存區域。數組中的值250,2,290,35,12,99在內存中只有一份。即,不用把數組中每個元素的值再復制一份,節約了內存。如果對內存圖看懂了,那么,當形參arr對應的數據順序改變了,實參arr1對應的數據順序也就改變了。即,實現了形參數據改變時,實參數據也改變了。所以,bubble函數不需要返回值,依然可以達到排序的目的。可以運行我示例中的代碼,看看是不是達到了排序的效果。

          補充,基本類型作為函數參數的內存變化:

          內存圖:

          4. 引用類型變量的賦值:

          引用類型變量賦值時,賦的是地址。即兩個引用類型變量里存儲的是同一塊地址,也就是說,兩個引用類型變量都指向同一塊內存區域。所以,兩個引用類型變量對應的數據時一樣的。

          基本類型變量賦值時的內存變化。

          5. 淺拷貝和深拷貝

          先說對象的復制,上面說了,引用類型(對象)的賦值,只是賦的地址,那么要真正復制一份新的對象(即克隆)時,又該怎么辦。



          但是,當一個對象的屬性又是一個引用類型時,會出現淺拷貝和深拷貝的問題。用一個自定義的object類型來說明問題。

          如:

          person2.name="張四"; //不會改變掉person1的name屬性。

          person2.address.country="北京";//會改變掉person1的address.country

          大家注意看,person1和person2的name屬性各有各的空間,但是person1.address.country和person2.address.country是同一塊空間。所以,改變person2.address.country的值時,person1.address.country的值也會改變。這就說明拷貝(克隆)的不到位,這種拷貝叫作淺拷貝,而進一步把person1.address.country和person1.address.name也拷貝(克隆)了,就是深拷貝。要做到深拷貝,就需要對每個屬性的類型進行判斷,如果是引用類型,就再循環進行拷貝(需要用到遞歸)。

          (未完待續)


          千鋒HTML5教學部 田江

          avaScript語言的一大特點就是單線程,也就是說,同一個時間只能做一件事。那么,為什么JavaScript不能有多個線程呢?這樣能提高效率啊。

          JavaScript的單線程,與它的用途有關。作為瀏覽器腳本語言,JavaScript的主要用途是與用戶互動,以及操作DOM。這決定了它只能是單線程,否則會帶來很復雜的同步問題。比如,假定JavaScript同時有兩個線程,一個線程在某個DOM節點上添加內容,另一個線程刪除了這個節點,這時瀏覽器應該以哪個線程為準?

          所以,為了避免復雜性,從一誕生,JavaScript就是單線程,這已經成了這門語言的核心特征,將來也不會改變。

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

          二、瀏覽器js運行機制

          簡介

          單線程就意味著,所有任務需要排隊,前一個任務結束,才會執行后一個任務。如果前一個任務耗時很長,后一個任務就不得不一直等著。

          如果排隊是因為計算量大,CPU忙不過來,倒也算了,但是很多時候CPU是閑著的,因為IO設備(輸入輸出設備)很慢(比如Ajax操作從網絡讀取數據),不得不等著結果出來,再往下執行。

          JavaScript語言的設計者意識到,這時主線程完全可以不管IO設備,掛起處于等待中的任務,先運行排在后面的任務。等到IO設備返回了結果,再回過頭,把掛起的任務繼續執行下去。

          于是,所有任務可以分成兩種,一種是同步任務(synchronous),另一種是異步任務(asynchronous)。同步任務指的是,在主線程上排隊執行的任務,只有前一個任務執行完畢,才能執行后一個任務;異步任務指的是,不進入主線程、而進入"任務隊列"(task queue)的任務,只有"任務隊列"通知主線程,某個異步任務可以執行了,該任務才會進入主線程執行。

          具體來說,異步執行的運行機制如下。(同步執行也是如此,因為它可以被視為沒有異步任務的異步執行。)

          (1)所有同步任務都在主線程上執行,形成一個執行棧(execution context stack)。
          
          (2)主線程之外,還存在一個"任務隊列"(task queue)。只要異步任務有了運行結果,就在"任務隊列"之中放置一個事件。
          
          (3)一但"執行棧"中的所有同步任務執行完畢,系統就會讀取"任務隊列",看看里面有哪些事件。那些對應的異步任務,于是結束等待狀態,進入執行棧,開始執行。
          
          (4)主線程不斷重復上面的第三步。
          復制代碼

          只要主線程空了,就會去讀取"任務隊列",這就是JavaScript的運行機制。這個過程會不斷重復。

          三. 瀏覽器的 Event Loop 事件循環

          簡介

          主線程從"任務隊列"中讀取事件,這個過程是循環不斷的,所以整個的這種運行機制又稱為Event Loop(事件循環)。為了更好地理解Event Loop,請看下圖

          事件循環可以簡單描述為:

          函數入棧,當Stack中執行到異步任務的時候,就將他丟給WebAPIs,接著執行同步任務,直到Stack為空; 在此期間WebAPIs完成這個事件,把回調函數放入CallbackQueue中等待; 當執行棧為空時,Event Loop把Callback Queue中的一個任務放入Stack中,回到第1步。

          • Event Loop是由javascript宿主環境(像瀏覽器)來實現的;
          • WebAPIs是由C++實現的瀏覽器創建的線程,處理諸如DOM事件、http請求、定時器等異步事件;
          • JavaScript 的并發模型基于"事件循環";
          • Callback Queue(Event Queue 或者 Message Queue) 任務隊列,存放異步任務的回調函數

          接下來看一個異步函數執行的例子:

          var start=new Date();
          setTimeout(function cb(){
              console.log("時間間隔:",new Date()-start+'ms');
          },500);
          while(new Date()-start<1000){};
          復制代碼
          1. main(Script) 函數入棧,start變量開始初始化
          2. setTimeout入棧,出棧,丟給WebAPIs,開始定時500ms;
          3. while循環入棧,開始阻塞1000ms;
          4. 500ms過后,WebAPIs把cb()放入任務隊列,此時while循環還在棧中,cb()等待;
          5. 又過了500ms,while循環執行完畢從棧中彈出,main()彈出,此時棧為空,Event Loop,cb()進入棧,log()進棧,輸出'時間間隔:1003ms',出棧,cb()出棧

          四. 宏任務(Macrotasks)和微任務(Microtasks)

          簡介

          JS的異步有一個機制的,就是會分為宏任務和微任務。宏任務和微任務會放到不同的event queue中,先將所有的宏任務放到一個event queue(macro-task),再將微任務放到一個event queue(micro-task)中。執行完宏任務之后,就會先從微任務中取這個回調函數執行。

          講的詳細一點的話

          最開始, 執行棧為空, 微任務隊列為空, 宏任務隊列有一個 script 標簽(內含整體代碼)

          將第一個宏任務出隊, 這里即為上述的 script 標簽

          整體代碼執行過程中, 如果是同步代碼, 直接執行(函數執行的話會有入棧出棧操作), 如果是異步代碼, 會根據任務類型推入不同的任務隊列中(宏任務或微任務)

          當執行棧執行完為空時, 會去處理微任務隊列的任務, 將微任務隊列的任務一個個推入調用棧執行完

          微任務執行完后,檢查是否需要重新渲染 UI。

          ...往返循環直到宏任務和微任務隊列為空

          總結一下上述循環機制的特點:

          出隊一個宏任務 -> 調用棧為空后, 執行一隊微任務 -> 更新界面渲染 -> 回到第一步

          宏任務 macro-task(Task)

          一個event loop有一個或者多個task隊列。task任務源非常寬泛,比如ajax的onload,click事件,基本上我們經常綁定的各種事件都是task任務源,還有數據庫操作(IndexedDB ),需要注意的是setTimeout、setInterval、setImmediate也是task任務源。總結來說task任務源:

          • script
          • setTimeout
          • setInterval
          • setImmediate
          • I/O
          • requestAnimationFrame
          • UI rendering

          微任務 micro-task(Job)

          microtask 隊列和task 隊列有些相似,都是先進先出的隊列,由指定的任務源去提供任務,不同的是一個 event loop里只有一個microtask 隊列。另外microtask執行時機和Macrotasks也有所差異

          • process.nextTick
          • promises
          • Object.observe
          • MutationObserver

          宏任務和微任務的區別

          • 宏隊列可以有多個,微任務隊列只有一個,所以每創建一個新的settimeout都是一個新的宏任務隊列,執行完一個宏任務隊列后,都會去checkpoint 微任務。
          • 一個事件循環后,微任務隊列執行完了,再執行宏任務隊列
          • 一個事件循環中,在執行完一個宏隊列之后,就會去check 微任務隊列

          宏任務和微任務的運行

          下圖是一個事件循環的流程

          舉個簡單的例子,假設一個script標簽的代碼如下:

          Promise.resolve().then(function promise1 () {
                 console.log('promise1');
              })
          setTimeout(function setTimeout1 (){
              console.log('setTimeout1')
              Promise.resolve().then(function  promise2 () {
                 console.log('promise2');
              })
          }, 0)
          
          setTimeout(function setTimeout2 (){
             console.log('setTimeout2')
          }, 0)

          運行過程:

          script里的代碼被列為一個task,放入task隊列。

          循環1:

          • 【task隊列:script ;microtask隊列:】從task隊列中取出script任務,推入棧中執行。promise1列為microtask,setTimeout1列為task,setTimeout2列為task。
          • 【task隊列:setTimeout1 setTimeout2;microtask隊列:promise1】script任務執行完畢,執行microtask checkpoint,取出microtask隊列的promise1執行。

          循環2:

          • 【task隊列:setTimeout1 setTimeout2;microtask隊列:】從task隊列中取出setTimeout1,推入棧中執行,將promise2列為microtask。
          • 【task隊列:setTimeout2;microtask隊列:promise2】執行microtask checkpoint,取出microtask隊列的promise2執行。(循環2中的 setTimeout2為什么不是跟在setTimeout1的后面輸出? 這里我覺得應該是setTimeout1和setTimeout2不是在同一個task隊列中, 是兩個task隊列。在執行完setTimeout1的task隊列后, event loop去檢查microtask隊列是否有事件,并且把事推入到主棧。) 復制代碼

          循環3:

          • 【task隊列:setTimeout2;microtask隊列:】從task隊列中取出setTimeout2,推入棧中執行。setTimeout2任務執行完畢,執行microtask checkpoint。
          • 【task隊列:;microtask隊列:】

          注:有些文章說的一個事件循環的開始是先執行微任務再執行宏任務,有有些說的是先執行宏任務再執行微任務,我個人覺得這兩種只是看法的角度不一致

          • 如果把script載入到主堆棧這一過程看成是執行了宏任務,那么就是宏任務先開始。
          • 如果不把這個script的運行當做是宏任務,只看異步函數中的宏任務(setTimeout)那么就是微任務先開始。

          宏任務與微任務示例

          EXP1 在主線程上添加宏任務與微任務

          console.log('-------start--------');
          
          setTimeout(()=> {
            console.log('setTimeout');  // 將回調代碼放入另一個宏任務隊列
          }, 0);
          
          new Promise((resolve, reject)=> {
            for (let i=0; i < 5; i++) {
              console.log(i);
            }
            resolve()
          }).then(()=>{
            console.log('Promise'); // 將回調代碼放入微任務隊列
          })
          
          console.log('-------end--------');

          運行結果:

          -------start--------
          0
          1
          2
          3
          4
          -------end--------
          Promise
          setTimeout

          由EXP1,我們可以看出,當JS執行完主線程上的代碼,會去檢查在主線程上創建的微任務隊列,執行完微任務隊列之后才會執行宏任務隊列上的代碼

          運行順序:

          主線程=> 主線程上創建的微任務=> 主線程上創建的宏任務

          script里的代碼被列為一個task,放入task隊列。

          循環1:

          • 【task隊列:script ;microtask隊列:】從task隊列中取出script任務,推入棧中執行。promise列為microtask,setTimeout列為task。
          • 【task隊列:setTimeout ;microtask隊列:promise】script任務執行完畢,執行microtask checkpoint,取出microtask隊列的promise執行。

          循環2:

          • 【task隊列:setTimeout ;microtask隊列:】從task隊列中取出setTimeout,推入棧中執行setTimeout任務執行完畢,執行microtask checkpoint。
          • 【task隊列:;microtask隊列:】

          EXP2 在微任務中創建微任務

          setTimeout(_=> console.log('setTimeout4'))
          
          new Promise(resolve=> {
            resolve()
            console.log('Promise1')
          }).then(_=> {
            console.log('Promise3')
            Promise.resolve().then(_=> {
              console.log('before timeout')
            }).then(_=> {
              Promise.resolve().then(_=> {
                console.log('also before timeout')
              })
            })
          })
          
          console.log(2)

          運行結果:

          Promise1
          2
          Promise3
          before timeout
          also before timeout
          setTimeout4

          由EXP2,我們可以看出,在微任務隊列執行時創建的微任務,還是會排在主線程上創建出的宏任務之前執行(因為微任務只有一條,自增鏈不斷的話 會一直往下執行微任務,不會被中斷)

          運行順序:

          主線程=> 主線程上創建的微任務1=> 微任務1上創建的微任務2=> 主線程上創建的宏任務

          script里的代碼被列為一個task,放入task隊列。

          循環1:

          • 【task隊列:script ;microtask隊列:】從task隊列中取出script任務,推入棧中執行。promise3 列為microtask,setTimeout4 列為task。
          • 【task隊列:setTimeout4;microtask隊列:promise3】script任務執行完畢,執行microtask checkpoint,取出microtask隊列的promise3執行。將before timeout 列為 microtask。
          • 【task隊列:setTimeout4;microtask隊列:before timeout】before timeout 執行將also before timeout 列為microtask
          • 【task隊列:setTimeout4;microtask隊列:also before timeout】also before timeout 執行

          循環2:

          • 【task隊列:setTimeout4 ;microtask隊列:before timeout】從task隊列中取出setTimeout4,推入棧中執行setTimeout4任務執行完畢,執行microtask checkpoint。
          • 【task隊列:;microtask隊列:】

          EXP3: 宏任務中創建微任務

          // 宏任務隊列 1
          setTimeout(()=> {
            // 宏任務隊列 1.1
            console.log('timer_1');
            setTimeout(()=> {
              // 宏任務隊列 3
              console.log('timer_3')
            }, 0)
            new Promise(resolve=> {
              resolve()
              console.log('new promise')
            }).then(()=> {
              // 微任務隊列 1
              console.log('promise then')
            })
          }, 0)
          
          setTimeout(()=> {
            // 宏任務隊列 2.2
            console.log('timer_2')
          }, 0)
          
          console.log('==========Sync queue==========')
          

          運行結果:

          ==========Sync queue==========timer_1
          new promise
          promise then
          timer_2
          timer_3

          運行順序:

          主線程(宏任務隊列 1)=> 宏任務隊列 1.1=> 微任務隊列 1=> 宏任務隊列 3=>宏任務隊列2.2

          循環1:

          • 【task隊列:script ;microtask隊列:】從task隊列中取出script任務,推入棧中執行。timer_1 列為task,timer_2 列為task。
          • 【task隊列:timer_1,timer_2;microtask隊列:】script任務執行完畢,執行microtask checkpoint,無microtask隊列可執行。

          循環2

          • 【task隊列::timer_1,timer_2;microtask隊列:】從task隊列中取出 timer_1 推入棧中執行。將 timer_3 列為task,promise then 列為microtask
          • 【task隊列:timer_2,timer_3;microtask隊列:promise then】執行microtask checkpoint,取出microtask隊列的promise then執行

          循環3

          • 【task隊列:timer_2,timer_3;microtask隊列:】從task隊列中取出timer_2,推入棧中執行
          • 【task隊列:timer_3;microtask隊列:】執行microtask checkpoint,無microtask隊列可執行

          循環4

          • 【task隊列:timer_3;microtask隊列:】從task隊列中取出timer_3,推入棧中執行
          • 【task隊列:;microtask隊列:】執行microtask checkpoint,無microtask隊列可執行

          EXP4:微任務隊列中創建的宏任務

          // 宏任務1
          new Promise((resolve)=> {
            console.log('new Promise(macro task 1)');
            resolve();
          }).then(()=> {
            // 微任務1
            console.log('micro task 1');
            setTimeout(()=> {
              // 宏任務3
              console.log('macro task 3');
            }, 0)
          })
          
          setTimeout(()=> {
            // 宏任務2
            console.log('macro task 2');
          }, 0)
          
          console.log('==========Sync queue(macro task 1)==========');

          運行結果:

          ==========Sync queue(macro task 1)==========new Promise(macro task 1)
          micro task 1
          macro task 2
           task 3
          復制代碼

          異步宏任務隊列只有一個,當在微任務中創建一個宏任務之后,他會被追加到異步宏任務隊列上(跟主線程創建的異步宏任務隊列是同一個隊列)

          運行順序:

          主線程=> 主線程上創建的微任務=> 主線程上創建的宏任務=> 微任務中創建的宏任務

          循環1:

          • 【task隊列:script ;microtask隊列:】從task隊列中取出script任務,推入棧中執行。macro task 2 列為task,micro task 1 列為microtask。
          • 【task隊列:macro task 2;microtask隊列:micro task 1】script任務執行完畢,執行microtask checkpoint,microtask隊列中取出micro task 1 執行。執行micro task 1 的時候,把macro task 3列為task

          循環2

          • 【task隊列:macro task 2,macro task 3;microtask隊列:】
          • 從task隊列中取出 micro task2,推入棧中執行。執行microtask checkpoint,無microtask隊列可執行

          循環2

          • 【task隊列:macro task 3;microtask隊列:】從task隊列中取出 micro task3,推入棧中執行。執行microtask checkpoint,無microtask隊列可執行
          • 【task隊列:;microtask隊列:】

          小總結

          • 微任務隊列優先于宏任務隊列執行,
          • 微任務隊列上創建的宏任務會被后添加到當前宏任務隊列的尾端,微任務隊列中創建的微任務會被添加到微任務隊列的尾端。
          • 只要微任務隊列中還有任務,宏任務隊列就只會等待微任務隊列執行完畢后再執行。

          五. 當 Event Loop 遇上事件冒泡

          手動觸發

          代碼

          <div class="outer">
            <div class="inner"></div>
          </div>
          var outer=document.querySelector('.outer');
          var inner=document.querySelector('.inner');
          
          function onClick() {
            console.log('click');
          
            setTimeout(function() {
              console.log('timeout');
            }, 0);
          
            Promise.resolve().then(function() {
              console.log('promise');
            });
          
          }
          
          inner.addEventListener('click', onClick);
          outer.addEventListener('click', onClick);

          點擊 inner,最終打印結果為:

          click
          promise
          click
          promise
          timeout
          timeout

          分析

          為什么打印結果是這樣的呢?我們來分析一下: (0)將 script 標簽內的代碼(宏任務)放入執行棧執行,執行完后,宏任務微任務隊列皆空。

          (1)點擊 inner,onClick 函數入執行棧執行,打印 "click"。執行完后執行棧為空,因為事件冒泡的緣故,事件觸發線程會將向上派發事件的任務放入宏任務隊列。

          (2)遇到 setTimeout,在最小延遲時間后,將回調放入宏任務隊列。遇到 promise,將 then 的任務放進微任務隊列

          (3)此時,執行棧再次為空。開始清空微任務,打印 "promise"

          (4)此時,執行棧再次為空。從宏任務隊列拿出一個任務執行,即前面提到的派發事件的任務,也就是冒泡。

          (5)事件冒泡到 outer,執行回調,重復上述 "click"、"promise" 的打印過程。

          (6)從宏任務隊列取任務執行,這時我們的宏任務隊列已經累計了兩個 setTimeout 的回調了,所以他們會在兩個 Event Loop 周期里先后得到執行。

          可以看成是:

          function onClick() {
            //模擬outer click事件
            setTimeout(function(){onClick1()},0)
            console.log('click');
          
            setTimeout(function() {
              console.log('timeout');
            }, 0);
          
            Promise.resolve().then(function() {
              console.log('promise');
            });
          }
          function onClick1() {
            console.log('click1');
          
            setTimeout(function() {
              console.log('timeout1');
            }, 0);
          
            Promise.resolve().then(function() {
              console.log('promise1');
            });
          }
          //模擬inner click事件
          onClick()

          代碼觸發

          代碼

          inner.click()

          打印結果為:

          click
          click
          promise
          promise
          timeout
          timeout

          分析

          依舊分析一下:

          (0)將 script(宏任務)放入執行棧執行,執行到 inner.click() 的時候,執行 onClick 函數,打印 "click"

          (1)當執行完 onClick 后,此時的 script(宏任務)還沒返回,執行棧不為空,不會去清空微任務,而是會將事件往上冒泡派發

          ...(關鍵步驟分析完后,續步驟就不分析了)

          可以看成是:

          function onClick() {
            console.log('click');
          
            setTimeout(function() {
              console.log('timeout');
            }, 0);
          
            Promise.resolve().then(function() {
              console.log('promise');
            });
          }
          onClick();
          onClick();

          總結

          在一般情況下,微任務的優先級是更高的,是會優先于事件冒泡的,但如果手動 .click() 會使得在 script代碼塊 還沒彈出執行棧的時候,觸發事件派發。

          Event Loop總結

          瀏覽器進行事件循環工作方式

          1. 選擇當前要執行的任務隊列,選擇任務隊列中最先進入的任務,如果任務隊列為空即null,則執行跳轉到微任務(MicroTask)的執行步驟。
          2. 將事件循環中的任務設置為已選擇任務。
          3. 執行任務。
          4. 將事件循環中當前運行任務設置為null。
          5. 將已經運行完成的任務從任務隊列中刪除。
          6. microtasks步驟:進入microtask檢查點。
          7. 更新界面渲染。
          8. 返回第一步

          【執行進入microtask檢查點時,瀏覽器會執行以下步驟:】

          • 設置microtask檢查點標志為true。
          • 當事件循環microtask執行不為空時:選擇一個最先進入的microtask隊列的microtask,將事件循環的microtask設置為已選擇的microtask,運行microtask,將已經執行完成的microtask為null,移出microtask中的microtask。
          • 清理IndexDB事務
          • 設置進入microtask檢查點的標志為false。

          重點

          總結以上規則為一條通俗好理解的:

          1. 順序執行先執行同步方法,碰到MacroTask直接執行,并且把回調函數放入MacroTask執行隊列中(下次事件循環執行);碰到microtask直接執行。把回調函數放入microtask執行隊列中(本次事件循環執行)
          2. 當同步任務執行完畢后,去執行微任務microtask。(microtask隊列清空)
          3. 由此進入下一輪事件循環:執行宏任務 MacroTask (setTimeout,setInterval,callback)

          [總結]所有的異步都是為了按照一定的規則轉換為同步方式執行。

          備的一篇干貨文章,對你有用的話就收藏或者轉發吧!

          HTML5 DOM 選擇器

          javascript 代碼

          阻止默認行為

          javascript 代碼

          阻止冒泡

          javascript 代碼

          鼠標滾輪事件

          javascript 代碼

          檢測瀏覽器是否支持svg

          javascript 代碼

          檢測瀏覽器是否支持canvas

          javascript 代碼

          檢測是否是微信瀏覽器

          javascript 代碼

          常用的一些正則表達式

          javascript 代碼

          js時間戳、毫秒格式化

          javascript 代碼

          getBoundingClientRect() 獲取元素位置

          javascript 代碼

          HTML5全屏

          javascript 代碼

          關注我

          定期分享前端干貨、新技術解讀

          如果你越學越迷茫,沒思路

          加我微信 webwula

          備注“想提升自己

          我教你學前端的方法


          主站蜘蛛池模板: 99久久国产精品免费一区二区| 日韩精品一区二区三区毛片| 无码国产精品一区二区免费式直播| 一区二区三区亚洲视频| 日韩一区二区三区精品| 久久精品一区二区三区四区| 国产乱码精品一区二区三| 一区二区三区免费电影| 影院无码人妻精品一区二区| 精品国产精品久久一区免费式 | 国产成人一区二区三区高清| 国产经典一区二区三区蜜芽| 精品久久久久一区二区三区| 无码精品不卡一区二区三区| 色婷婷综合久久久久中文一区二区| 日韩一区二区三区免费体验| 国产精品一区二区无线| 国产精品自在拍一区二区不卡| 一区二区三区内射美女毛片| 久久精品国产第一区二区| 国产av成人一区二区三区| 久久久精品人妻一区二区三区| 日本一区二区三区在线观看| 97一区二区三区四区久久| 日韩免费一区二区三区在线播放| 91在线一区二区| 国产成人av一区二区三区不卡| 亚洲一区无码精品色| 日韩一区二区在线播放| 国产精品盗摄一区二区在线| 国产91精品一区二区麻豆亚洲| 中文字幕亚洲一区二区三区| 国产成人无码一区二区在线观看 | 日韩精品一区二区三区影院| 国产精品无圣光一区二区| 性色av无码免费一区二区三区| 国产在线不卡一区二区三区| 亚洲AV无码一区二区三区电影| 内射女校花一区二区三区| 91久久精品午夜一区二区| 一区二区三区四区视频在线|