整合營銷服務商

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

          免費咨詢熱線:

          HTML <area>

          HTML <area> 標簽

          帶有可點擊區域的圖像映射:

          <imgsrc="planets.gif"width="145"height="126"alt="Planets"usemap="#planetmap"><mapname="planetmap"><areashape="rect"coords="0,0,82,126"alt="Sun"href="sun.htm"><areashape="circle"coords="90,58,3"alt="Mercury"href="mercur.htm"><areashape="circle"coords="124,58,8"alt="Venus"href="venus.htm"></map>

          瀏覽器支持

          所有主流瀏覽器都支持 <area> 標簽。

          標簽定義及使用說明

          <area> 標簽定義圖像映射內部的區域(圖像映射指的是帶有可點擊區域的圖像)。

          <area> 元素始終嵌套在 <map> 標簽內部。

          注釋: <img> 標簽中的 usemap 屬性與 <map> 元素中的 name 相關聯,以創建圖像與映射之間的關系。

          HTML 4.01 與 HTML5之間的差異

          HTML5 提供了一些新屬性,同時不再支持 HTML 4.01 中的某些屬性。

          HTML 與 XHTML 之間的差異

          在 HTML 中,<area> 標簽沒有結束標簽。

          在 XHTML 中,<area> 標簽必須正確地關閉。

          屬性

          New :HTML5 中的新屬性。

          屬性描述
          alttext規定區域的替代文本。如果使用 href 屬性,則該屬性是必需的。
          coordscoordinates規定區域的坐標。
          hrefURL規定區域的目標 URL。
          hreflangNewlanguage_code規定目標 URL 的語言。
          mediaNewmedia query規定目標 URL 是為何種媒介/設備優化的。默認:all。
          nohrefvalueHTML5 不支持。 規定沒有相關鏈接的區域。
          relNewalternateauthorbookmarkhelplicensenextnofollownoreferrerprefetchprevsearchtag規定當前文檔與目標 URL 之間的關系。
          shapedefaultrectcirclepoly規定區域的形狀。
          target_blank_parent_self_topframename規定在何處打開目標 URL。
          typeNewMIME_type規定目標 URL 的 MIME 類型。注:MIME=Multipurpose Internet Mail Extensions。

          全局屬性

          <area> 標簽支持 HTML 的全局屬性。

          事件屬性

          <area> 標簽支持 HTML 的事件屬性。

          如您還有不明白的可以在下面與我留言或是與我探討QQ群308855039,我們一起飛!

          . JS的運行機制

          介紹

          眾所周知JavaScript是一門單線程的語言,所以在JavaScript的世界中默認的情況下,同一個時間節點只能做一件事情,這樣的設定就造成了JavaScript這門語言的一些局限性,比如在我們的頁面中加載一些遠程數據時,如果按照單線程同步的方式運行,一旦有HTTP請求向服務器發送,就會出現等待數據返回之前網頁假死的效果出現。因為JavaScript在同一個時間只能做一件事,這就導致了頁面渲染和事件的執行,在這個過程中無法進行。顯然在實際的開發中我們并沒有遇見過這種情況。

          關于同步和異步

          基于以上的描述,我們知道在JavaScript的世界中,應該存在一種解決方案,來處理單線程造成的詬病。這就是同步【阻塞】和異步【非阻塞】執行模式的出現。

          同步(阻塞)

          同步的意思是JavaScript會嚴格按照單線程(從上到下、從左到右的方式)執行代碼邏輯,進行代碼的解釋和運行,所以在運行代碼時,不會出現先運行4、5行的代碼,再回頭運行1、3行的代碼這種情況。比如下列操作。

          
          var a=1
          var b=2
          var c=a + b
          //這個例子總c一定是3不會出現先執行第三行然后在執行第二行和第一行的情況
          console.log(c)

          接下來通過下列的案例升級一下代碼的運行場景:

          var a=1
          var b=2
          var d1=new Date().getTime()
          var d2=new Date().getTime()
          while(d2-d1<2000){
            d2=new Date().getTime()
          }
          //這段代碼在輸出結果之前網頁會進入一個類似假死的狀態
          console.log(a+b)

          當我們按照順序執行上面代碼時,我們的代碼在解釋執行到第4行時,還是正常的速度執行,但是在下一行就會進入一個持續的循環中。d2d1在行級間的時間差僅僅是毫秒內的差別,所以在執行到while循環的時候d2-d1的值一定比2000小,那么這個循環會執行到什么時候呢?由于每次循環時,d2都會獲取一次當前的時間發生變化,直到d2-d1==2000等情況,這時也就是正好過了2秒的時間,我們的程序才能跳出循環,進而再輸出a+b的結果。那么這段程序的實際執行時間至少是2秒以上。這就導致了程序阻塞的出現,這也是為什么將同步的代碼運行機制叫做阻塞式運行的原因。

          阻塞式運行的代碼,在遇到消耗時間的代碼片段時,之后的代碼都必須等待耗時的代碼運行完畢,才能得到執行資源,這就是單線程同步的特點。

          異步(非阻塞):

          在上面的闡述中,我們明白了單線程同步模型中的問題所在,接下來引入單線程異步模型的介紹。異步的意思就是和同步對立,所以異步模式的代碼是不會按照默認順序執行的。JavaScript執行引擎在工作時,仍然是按照從上到下從左到右的方式解釋和運行代碼。在解釋時,如果遇到異步模式的代碼,引擎會將當前的任務“掛起”并略過。也就是先不執行這段代碼,繼續向下運行非異步模式的代碼,那么什么時候來執行異步代碼呢?直到同步代碼全部執行完畢后,程序會將之前“掛起”的異步代碼按照“特定的順序”來進行執行,所以異步代碼并不會【阻塞】同步代碼的運行,并且異步代碼并不是代表進入新的線程同時執行,而是等待同步代碼執行完畢再進行工作。我們閱讀下面的代碼分析:

          var a=1
          var b=2
          setTimeout(function(){
            console.log('輸出了一些內容')
          },2000)
          //這段代碼會直接輸出3并且等待2秒左右的時間在輸出function內部的內容
          console.log(a+b)

          這段代碼的setTimeout定時任務規定了2秒之后執行一些內容,在運行當前程序執行到setTimeout時,并不會直接執行內部的回調函數,而是會先將內部的函數在另外一個位置(具體是什么位置下面會介紹)保存起來,然后繼續執行下面的console.log進行輸出,輸出之后代碼執行完畢,然后等待大概2秒左右,之前保存的函數再執行。

          非阻塞式運行的代碼,程序運行到該代碼片段時,執行引擎會將程序保存到一個暫存區,等待所有同步代碼全部執行完畢后,非阻塞式的代碼會按照特定的執行順序,分步執行。這就是單線程異步的特點。

          通俗的講:

          通俗的講,同步和異步的關系是這樣的:

          【同步的例子】:比如我們在核酸檢測站,進行核酸檢測這個流程就是同步的。每個人必須按照來的時間,先后進行排隊,而核酸檢測人員會按照排隊人的順序嚴格的進行逐一檢測,在第一個人沒有檢測完成前,第二個人就得無條件等待,這個就是一個阻塞流程。如果排隊過程中第一個人在檢測時出了問題,如棉簽斷了需要換棉簽,這樣更換時間就會追加到這個人身上,直到他順利的檢測完畢,第二個人才能輪到。如果在檢測中間棉簽沒有了,或者是錄入信息的系統崩潰了,整個隊列就進入無條件掛起狀態所有人都做不了了。這就是結合生活中的同步案例。

          【異步的例子】:還是結合生活中,當我們進餐館吃飯時,這個場景就屬于一個完美的異步流程場景。每一桌來的客人會按照他們來的順序進行點單,假設只有一個服務員的情況,點單必須按照先后順序,但是服務員不需要等第一桌客人點好的菜出鍋上菜,就可以直接去收集第二桌第三桌客人的需求。這樣可能在十分鐘之內,服務員就將所有桌的客人點菜的菜單統計出來,并且發送給了后廚。之后的菜也不會按照點餐顧客的課桌順序,因為后廚收集到菜單之后可能有1,2,3桌的客人都點了鍋包肉,那么他可能會先一次出三份鍋包肉,這樣鍋包肉在上菜的時候1,2,3桌的客人都能得到,并且其他的菜也會亂序的逐一上菜,這個過程就是異步的。如果按照同步的模式點餐,默認在飯店點菜就會出現飯店在第一桌客人上滿菜之前第二桌之后的客人就只能等待連單都不能點的狀態。

          總結:

          JavaScript的運行順序就是完全單線程的異步模型:同步在前,異步在后。所有的異步任務都要等待當前的同步任務執行完畢之后才能執行。請看下面的案例:

          var a=1
          var b=2
          var d1=new Date().getTime()
          var d2=new Date().getTime()
          setTimeout(function(){
            console.log('我是一個異步任務')
          },1000)
          while(d2-d1<2000){
            d2=new Date().getTime()
          }
          //這段代碼在輸出3之前會進入假死狀態,'我是一個異步任務'一定會在3之后輸出
          console.log(a+b)

          觀察上面的程序我們實際運行之后就會感受到單線程異步模型的執行順序了,并且這里我們會發現setTimeout設置的時間是1000毫秒但是在while的阻塞2000毫秒的循環之后并沒有等待1秒而是直接輸出了我是一個異步任務,這是因為setTimout的時間計算是從setTimeout()這個函數執行時開始計算的。

          JS的線程組成

          上面我們通過幾個簡單的例子大概了解了一下JS的運行順序,那么為什么是這個順序,這個順序的執行原理是什么樣的,我們應該如何更好更深的探究真相呢?這里需要介紹一下瀏覽器中一個Tab頁面的實際線程組成。

          在了解線程組成前要了解一點,雖然瀏覽器是單線程執行JavaScript代碼的,但是瀏覽器實際是以多個線程協助操作來實現單線程異步模型的,具體線程組成如下:

          1. GUI渲染線程
          2. JavaScript引擎線程
          3. 事件觸發線程
          4. 定時器觸發線程
          5. http請求線程
          6. 其他線程

          按照真實的瀏覽器線程組成分析,我們會發現實際上運行JavaScript的線程其實并不是一個,但是為什么說JavaScript是一門單線程的語言呢?因為這些線程中實際參與代碼執行的線程并不是所有線程,比如GUI渲染線程為什么單獨存在,這個是防止我們在html網頁渲染一半的時候突然執行了一段阻塞式的JS代碼而導致網頁卡在一半停住這種效果。JavaScript代碼運行的過程中實際執行程序時,同時只存在一個活動線程,這里實現同步異步就是靠多線程切換的形式來進行實現的

          所以我們通常分析時,將上面的細分線程歸納為下列兩條線程:

          1. 【主線程】:這個線程用來執行頁面的渲染,JavaScript代碼的運行,事件的觸發等等
          2. 【工作線程】:這個線程是在幕后工作的,用來處理異步任務的執行來實現非阻塞的運行模式

          2. JavaScript的運行模型

          上圖是JavaScript運行時的一個工作流程和內存劃分的簡要描述,我們根據圖中可以得知主線程就是我們JavaScript執行代碼的線程,主線程代碼在運行時,會按照同步和異步代碼將其分成兩個去處,如果是同步代碼執行,就會直接將該任務放在一個叫做“函數執行棧”的空間進行執行,執行棧是典型的【棧結構】(先進后出),程序在運行的時候會將同步代碼按順序入棧,將異步代碼放到【工作線程】中暫時掛起,【工作線程】中保存的是定時任務函數、JS的交互事件、JS的網絡請求等耗時操作。

          當【主線程】將代碼塊篩選完畢后,進入執行棧的函數會按照從外到內的順序依次運行,運行中涉及到的對象數據是在堆內存中進行保存和管理的。當執行棧內的任務全部執行完畢后,執行棧就會清空。執行棧清空后,“事件循環”就會工作,“事件循環”會檢測【任務隊列】中是否有要執行的任務,那么這個任務隊列的任務來源就是工作線程,程序運行期間,工作線程會把到期的定時任務、返回數據的http任務等【異步任務】按照先后順序插入到【任務隊列】中,等執行棧清空后,事件循環會訪問任務隊列,將任務隊列中存在的任務,按順序(先進先出)放在執行棧中繼續執行,直到任務隊列清空。

          從代碼片段開始分析

          function task1(){
              console.log('第一個任務')
          }
          function task2(){
              console.log('第二個任務')
          }
          function task3(){
              console.log('第三個任務')
          }
          function task4(){
              console.log('第四個任務')
          }
          task1()
          setTimeout(task2,1000)
          setTimeout(task3,500)
          task4()
          

          剛才的文字閱讀可能在大腦中很難形成一個帶動畫的圖形界面來幫助我們分析JavaScript的實際運行思路,接下來我們將這段代碼肢解之后詳細的研究一下。

          按照字面分析:

          按照字面分析,我們創建了四個函數代表4個任務,函數本身都是同步代碼。在執行的時候會按照1,2,3,4進行解析,解析過程中我們發現任務2和任務3被setTimeout進行了定時托管,這樣就只能先運行任務1和任務4了。當任務1和任務4運行完畢之后500毫秒后運行任務3,1000毫米后運行任務2。

          那么他們在實際運行時又是經歷了怎么樣的流程來運行的呢?大概的流程我們以圖解的形式分析一下。

          圖解分析:

          如上圖,在上述代碼剛開始運行的時候我們的主線程即將工作,按照順序從上到下進行解釋執行,此時執行棧、工作線程、任務隊列都是空的,事件循環也沒有工作。接下來我們分析下一個階段程序做了什么事情。

          結合上圖可以看出程序在主線程執行之后就將任務1、4和任務2、3分別放進了兩個方向,任務1和任務4都是立即執行任務所以會按照1->4的順序進棧出棧(這里由于任務1和4是平行任務所以會先執行任務1的進出棧再執行任務4的進出棧),而任務2和任務3由于是異步任務就會進入工作線程掛起并開始計時,并不影響主線程運行,此時的任務隊列還是空置的。

          我們發現同步任務的執行速度是飛快的,這樣一下執行棧已經空了,而任務2和任務3還沒有到時間,這樣我們的事件循環就會開始工作等待任務隊列中的任務進入,接下來就是執行異步任務的時候了。

          我們發現任務隊列并不是一下子就會將任務2和任務三一起放進去,而是哪個計時器到時間了哪個放進去,這樣我們的事件循環就會發現隊列中的任務,并且將任務拿到執行棧中進行消費,此時會輸出任務3的內容。

          到這就是最后一次執行,當執行完畢后工作線程中沒有計時任務,任務隊列的任務清空程序到此執行完畢。

          總結

          我們通過圖解之后腦子里就會更清晰的能搞懂異步任務的執行方式了,這里采用最簡單的任務模型進行描繪復雜的任務在內存中的分配和走向是非常復雜的,我們有了這次的經驗之后就可以通過觀察代碼在大腦中先模擬一次執行,這樣可以更清晰的理解JS的運行機制。

          關于執行棧

          執行棧是一個棧的數據結構,當我們運行單層函數時,執行棧執行的函數進棧后,會出棧銷毀然后下一個進棧下一個出棧,當有函數嵌套調用的時候棧中就會堆積棧幀,比如我們查看下面的例子:

          function task1(){
            console.log('task1執行')
            task2()
            console.log('task2執行完畢')
          }
          function task2(){
            console.log('task2執行')
            task3()
            console.log('task3執行完畢')
          }
          function task3(){
            console.log('task3執行')
          }
          task1()
          console.log('task1執行完畢')
          

          我們根據字面閱讀就能很簡單的分析出輸出的結果會是

          /*
          task1執行
          task2執行
          task3執行
          task3執行完畢
          task2執行完畢
          task1執行完畢
          */

          那么這種嵌套函數在執行棧中的操作流程是什么樣的呢?

          第一次執行的時候調用task1函數執行到console.log的時候先進行輸出,接下來會遇到task2函數的調用會出現下面的情況:

          執行到此時檢測到task2中還有調用task3的函數,那么就會繼續進入task3中執行,如下圖:

          在執行完task3中的輸出之后task3內部沒有其他代碼,那么task3函數就算執行完畢那么就會發生出棧工作。

          此時我們會發現task3出棧之后程序運行又會回到task2的函數中繼續他的執行。接下來會發生相同的事情。

          再之后就剩下task1自己了,他在task2銷毀之后輸出task2執行完畢后他也會隨著出棧而銷毀。

          task1執行完畢之后它隨著銷毀最后一行輸出,就會進入執行棧執行并銷毀,銷毀之后執行棧和主線程清空。這個過程就會出現123321的這個順序,而且我們在打印輸出時,也能通過打印的順序來理解入棧和出棧的順序和流程。

          關于遞歸

          關于上面的執行棧執行邏輯清楚后,我們就順便學習一下遞歸函數,遞歸函數是項目開發時經常涉及到的場景。我們經常會在未知深度的樹形結構,或其他合適的場景中使用遞歸。那么遞歸在面試中也會經常被問到風險問題,如果了解了執行棧的執行邏輯后,遞歸函數就可以看成是在一個函數中嵌套n層執行,那么在執行過程中會觸發大量的棧幀堆積,如果處理的數據過大,會導致執行棧的高度不夠放置新的棧幀,而造成棧溢出的錯誤。所以我們在做海量數據遞歸的時候一定要注意這個問題。

          關于執行棧的深度:

          執行棧的深度根據不同的瀏覽器和JS引擎有著不同的區別,我們這里就Chrome瀏覽器為例子來嘗試一下遞歸的溢出:

          var i=0;
          function task(){
            let index=i++
            console.log(`遞歸了${index}次`)
            task()
            console.log(`第${index}次遞歸結束`)
          }
          
          task()

          我們發現在遞歸了11378次之后會提示超過棧深度的錯誤,也就是我們無法在Chrome或者其他瀏覽器做太深層的遞歸操作。

          如何跨越遞歸限制

          發現問題后,我們再考慮如何能通過技術手段跨越遞歸的限制。可以將代碼做如下更改,這樣就不會出現遞歸問題了。

          var i=0;
          function task(){
            let index=i++
            console.log(`遞歸了${index}次`)
            setTimeout(function(){
              task()
            })
            console.log(`第${index}次遞歸結束`)
          }
          task()

          我們發現只是做了一個小小的改造,這樣就不會出現溢出的錯誤了。這是為什么呢?

          在了解原因之前我們先看控制臺的輸出,結合控制臺輸出我們發現確實超過了界限也沒有報錯。

          圖解原因:

          這個是因為我們這里使用了異步任務去調用遞歸中的函數,那么這個函數在執行的時候就不只使用棧進行執行了。

          先看沒有異步流程時候的執行圖例:


          再看有了異步任務的遞歸:

          有了異步任務之后我們的遞歸就不會疊加棧幀了,因為放入工作線程之后該函數就結束了,可以出棧銷毀,那么在執行棧中就永遠都是只有一個任務在運行,這樣就防止了棧幀的無限疊加,從而解決了無限遞歸的問題,不過異步遞歸的過程是無法保證運行速度的,在實際的工作場景中,如果考慮性能問題,還需要使用 while 循環等解決方案,來保證運行效率的問題,在實際工作場景中,盡量避免遞歸循環,因為遞歸循環就算控制在有限棧幀的疊加,其性能也遠遠不及指針循環。


          3.宏任務和微任務

          在明確了事件循環模型以及JavaScript的執行流程后,我們認識了一個叫做任務隊列的容器,他的數據結構式隊列的結構。所有除同步任務外的代碼都會在工作線程中,按照他到達的時間節點有序的進入任務隊列,而且任務隊列中的異步任務又分為【宏任務】和【微任務】。

          舉個例子:

          在了解【宏任務】和【微任務】前,還是哪生活中的實際場景舉個例子:

          比如: 在去銀行辦理業務時,每個人都需要在進入銀行時找到取票機進行取票,這個操作會把來辦理業務的人按照取票的順序排成一個有序的隊列。假設銀行只開通了一個辦事窗口,窗口的工作人員會按照排隊的順序進行叫號,到達號碼的人就可以前往窗口辦理業務,在第一個人辦理業務的過程中,第二個以后的人都需要進行等待。

          這個場景與JavaScript的異步任務隊列執行場景是一模一樣的,如果把每個辦業務的人當作JavaScript中的每一個異步的任務,那么取號就相當于將異步任務放入任務隊列。銀行的窗口就相當于【函數執行棧】,在叫號時代表將當前隊列的第一個任務放入【函數執行棧】運行。這時可能每個人在窗口辦理的業務內容各不相同,比如第一個人僅僅進行開卡的操作,這樣銀行工作人員就會為其執行開卡流程,這就相當于執行異步任務內部的代碼。

          如果第一個人的銀行卡開通完畢,銀行的工作人員不會立即叫第二個人過來,而是會詢問第一個人,“您是否需要為剛才開通的卡辦理一些增值業務,比如做個活期儲蓄。”,這時相當于在原始開卡的業務流程中臨時追加了一個新的任務,按照JavaScript的執行順序,這個人的新任務應該回到取票機拿取一張新的號碼,并且在隊尾重新排隊,這樣工作的話辦事效率就會急劇下降。所以銀行實際的做法是在叫下一個人辦理業務前,如果前面的人臨時有新的業務要辦理,工作人員會繼續為其辦理業務,直到這個人的所有事情都辦理完畢。

          從取卡到辦理追加業務完成的這個過程,就是微任務的實際體現。在JavaScript運行環境中,包括主線程代碼在內,可以理解為所有的任務內部都存在一個微任務隊列,在每下一個宏任務執行前,事件循環系統都會先檢測當前的代碼塊中是否包含已經注冊的微任務,并將隊列中的微任務優先執行完畢,進而執行下一個宏任務。所以實際的任務隊列的結構是這樣的,如圖:

          宏任務與微任務的介紹

          由上述內容得知JavaScript中存在兩種異步任務,一種是宏任務一種是微任務,他們的特點如下:

          宏任務

          宏任務是JavaScript中最原始的異步任務,包括setTimeoutsetIntervalAJAX等,在代碼執行環境中按照同步代碼的順序,逐個進入工作線程掛起,再按照異步任務到達的時間節點,逐個進入異步任務隊列,最終按照隊列中的順序進入函數執行棧進行執行。

          微任務

          微任務是隨著ECMA標準升級提出的新的異步任務,微任務在異步任務隊列的基礎上增加了【微任務】的概念,每一個宏任務執行前,程序會先檢測其中是否有當次事件循環未執行的微任務,優先清空本次的微任務后,再執行下一個宏任務,每一個宏任務內部可注冊當次任務的微任務隊列,再下一個宏任務執行前運行,微任務也是按照進入隊列的順序執行的。

          總結

          JavaScript的運行環境中,代碼的執行流程是這樣的:

          1. 默認的同步代碼按照順序從上到下,從左到右運行,運行過程中注冊本次的微任務和后續的宏任務:
          2. 執行本次同步代碼中注冊的微任務,并向任務隊列注冊微任務中包含的宏任務和微任務
          3. 將下一個宏任務開始前的所有微任務執行完畢
          4. 執行最先進入隊列的宏任務,并注冊當次的微任務和后續的宏任務,宏任務會按照當前任務隊列的隊尾繼續向下排列

          常見的宏任務和微任務劃分

          宏任務

          #

          瀏覽器

          Node

          I/O

          ?

          ?

          setTimeout

          ?

          ?

          setInterval

          ?

          ?

          setImmediate

          ?

          ?

          requestAnimationFrame

          ?

          ?

          有些地方會列出來UI Rendering,說這個也是宏任務,可是在讀了HTML規范文檔以后,發現這很顯然是和微任務平行的一個操作步驟 requestAnimationFrame姑且也算是宏任務吧,requestAnimationFrame在MDN的定義為,下次頁面重繪前所執行的操作,而重繪也是作為宏任務的一個步驟來存在的,且該步驟晚于微任務的執行

          微任務

          #

          瀏覽器

          Node

          process.nextTick

          ?

          ?

          MutationObserver

          ?

          ?

          Promise.then catch finally

          ?

          ?

          經典筆試題

          代碼輸出順序問題1

          setTimeout(function() {console.log('timer1')}, 0)
           
          requestAnimationFrame(function(){
              console.log('UI update')
          })
           
          setTimeout(function() {console.log('timer2')}, 0)
           
          new Promise(function executor(resolve) {
              console.log('promise 1')
              resolve()
              console.log('promise 2')
          }).then(function() {
              console.log('promise then')
          })
           
          console.log('end')

          解析:

          本案例輸出的結果為:猜對我就告訴你,先思考,猜對之后結合運行結果分析。

          按照同步先行,異步靠后的原則,閱讀代碼時,先分析同步代碼和異步代碼,Promise對象雖然是微任務,但是new Promise時的回調函數是同步執行的,所以優先輸出promise 1 和 promise 2。

          resolve執行時Promise對象的狀態變更為已完成,所以then函數的回調被注冊到微任務事件中,此時并不執行,所以接下來應該輸出end

          同步代碼執行結束后,觀察異步代碼的宏任務和微任務,在本次的同步代碼塊中注冊的微任務會優先執行,參考上文中描述的列表,Promise為微任務,setTimeoutrequestAnimationFrame為宏任務,所以Promise的異步任務會在下一個宏任務執行前執行,所以promise then是第四個輸出的結果。

          接下來參考setTimeoutrequestAnimationFrame兩個宏任務,這里的運行結果是多種情況。如果三個宏任務都為setTimeout的話會按照代碼編寫的順序執行宏任務,而中間包含了一個requestAnimationFrame ,這里就要學習一下他們的執行時機了。setTimeout是在程序運行到setTimeout時立即注冊一個宏任務,所以兩個setTimeout的順序一定是固定的timer1timer2會按照順序輸出。而requestAnimationFrame是請求下一次重繪事件,所以他的執行頻率要參考瀏覽器的刷新率。

          參考如下代碼:

          let i=0;
          let d=new Date().getTime()
          let d1=new Date().getTime()
          function loop(){
            d1=new Date().getTime()
            i++
            //當間隔時間超過1秒時執行
            if((d1-d)>=1000){
              d=d1
              console.log(i)
              i=0
              console.log('經過了1秒')
            }
            requestAnimationFrame(loop)
          }
          loop()

          該代碼在瀏覽器運行時,控制臺會每間隔1秒進行一次輸出,輸出的i就是loop函數執行的次數,如下圖:

          這個輸出意味著requestAnimationFrame函數的執行頻率是每秒鐘60次左右,他是按照瀏覽器的刷新率來進行執行的,也就是當屏幕刷新一次時該函數就會觸發一次,相當于運行間隔是16毫秒左右。

          繼續參考下列代碼:

          let i=0;
          let d=new Date().getTime()
          let d1=new Date().getTime()
          
          function loop(){
            d1=new Date().getTime()
            i++
            if((d1-d)>=1000){
              d=d1
              console.log(i)
              i=0
              console.log('經過了1秒')
            }
            setTimeout(loop,0)
          }
          loop()

          該代碼結構與上面的案例類似,循環是采用setTimeout進行控制的,所以參考運行結果,如圖:


          根據運行結果得知,setTimeout(fn,0)的執行頻率是每秒執行200次左右,所以他的間隔是5毫秒左右。

          由于這兩個異步的宏任務出發時機和執行頻率不同,會導致三個宏任務的觸發結果不同,如果我們打開網頁時,恰好趕上5毫秒內執行了網頁的重繪事件,requestAnimationFrame在工作線程中就會到達觸發時機優先進入任務隊列,所以此時會輸出:UI update->timer1->timer2

          而當打開網頁時上一次的重繪剛結束,下一次重繪的觸發是16毫秒后,此時setTimeout注冊的兩個任務在工作線程中就會優先到達觸發時機,這時輸出的結果是:timer1->timer2->UI update

          所以此案例的運行結果如下2圖所示:


          代碼輸出順序問題2

          document.addEventListener('click', function(){
              Promise.resolve().then(()=> console.log(1));
              console.log(2);
          })
           
          document.addEventListener('click', function(){
              Promise.resolve().then(()=> console.log(3));
              console.log(4);
          })

          解析:仍然是猜對了告訴你哈~,先運行一下試試吧。

          這個案例代碼簡單易懂,但是很容易引起錯誤答案的出現。由于該事件是直接綁定在document上的,所以點擊網頁就會觸發該事件,在代碼運行時相當于按照順序注冊了兩個點擊事件,兩個點擊事件會被放在工作線程中實時監聽觸發時機,當元素被點擊時,兩個事件會按照先后的注冊順序放入異步任務隊列中進行執行,所以事件1和事件2會按照代碼編寫的順序觸發。

          這里就會導致有人分析出錯誤答案:2,4,1,3。

          為什么不是2,4,1,3呢?由于事件執行時并不會阻斷JS默認代碼的運行,所以事件任務也是異步任務,并且是宏任務,所以兩個事件相當于按順序執行的兩個宏任務。

          這樣就會分出兩個運行環境,第一個事件執行時,console.log(2);是第一個宏任務中的同步代碼,所以他會立即執行,而Promise.resolve().then(()=> console.log(1));屬于微任務,他會在下一個宏任務觸發前執行,所以這里輸出2后會直接輸出1.

          而下一個事件的內容是相同道理,所以輸出順序為:2,1,4,3。

          總結

          關于事件循環模型今天就介紹到這里,在NodeJS中的事件循環模型和瀏覽器中是不一樣的,本文是以瀏覽器的事件循環模型為基礎進行介紹,事件循環系統在JavaScript異步編程中占據的比重是非常大的,在工作中可使用場景也是眾多的,掌握了事件循環模型就相當于,異步編程的能力上升了一個新的高度。

          HTML 中添加 onclick 事件時,可能會遇到事件沒有反應的情況。這可能由多種原因引起,如錯誤的語法、事件綁定方式、作用域問題、元素狀態問題、跨域安全性限制、網絡連接問題等等。為解決這些問題,需要仔細檢查代碼、采取適當措施,如使用正確的事件綁定方式、檢查作用域、避免跨域安全性限制等。如果仍無法解決問題,可以尋求其他開發者或社區的幫助,并使用調試工具和控制臺來進一步分析和解決問題。使用現代的前端框架或庫也可以幫助簡化事件處理和交互邏輯,提高代碼的可維護性和可重用性。

          如果您已經確認您的 HTML 代碼正確編寫,但是 onclick 事件仍然沒有任何反應,則可能有以下原因:

          1、代碼錯誤:請確保您的 onclick 事件代碼正確,沒有語法錯誤或拼寫錯誤。您可以嘗試將其替換為簡單的 console.log 語句以確保事件綁定成功,并在控制臺中查看是否有輸出。

          2、元素被覆蓋:如果其他元素位于您的<span>元素上方,則可能會阻止您的 onclick 事件被觸發。您可以嘗試使用 z-index 屬性將您的<span>元素置于其他元素之上。

          3、CSS 問題:如果您的<span>元素被其他 CSS 樣式影響,例如 display:none; 或 visibility:hidden;,則它可能不會顯示在頁面上,并且 onclick 事件將無法被觸發。請檢查您的 CSS 樣式并確保它們不會影響您的<span>元素。

          4、JavaScript 框架沖突:如果您的頁面中使用了其他 JavaScript 框架或庫,它們可能會干擾 onclick 事件的觸發。您可以嘗試使用純 JavaScript 或禁用其他框架以解決問題。

          5、瀏覽器兼容性問題:某些瀏覽器可能不支持某些 JavaScript 特性或事件,這可能會導致 onclick 事件無法被觸發。您可以嘗試在不同的瀏覽器中測試您的代碼,或者使用 JavaScript 庫或框架來處理瀏覽器兼容性問題。

          6、安全性問題:如果您的 onclick 事件代碼涉及到用戶輸入或數據傳輸,那么請確保您的代碼已經進行了必要的安全性檢查和過濾,以避免安全漏洞或攻擊。

          7、異步加載問題:如果您的<span>元素是通過異步加載或動態添加到頁面中的,那么 onclick 事件可能無法被正確地綁定。在這種情況下,您需要使用事件委托或者在元素添加到 DOM 后再綁定 onclick 事件。

          8、使用了禁用事件的 CSS 屬性:某些 CSS 屬性可能會禁用元素的默認事件處理行為。例如,使用 pointer-events: none; 將阻止鼠標事件的觸發,這也會包括 onclick 事件。因此,您需要確保您的 CSS 樣式不會禁用任何事件處理。

          9、其他事件監聽器或腳本的干擾:如果您的頁面中存在其他事件監聽器或腳本,它們可能會干擾 onclick 事件的綁定或觸發。您可以嘗試在頁面上禁用其他事件監聽器或腳本來診斷問題,或者使用 JavaScript 調試工具來查看代碼的執行順序和事件觸發情況。

          10、onclick 事件被其他元素覆蓋:如果您的<span>元素被其他元素完全或部分覆蓋,那么 onclick 事件可能會被覆蓋或無法觸發。您可以嘗試更改元素的位置或層次關系,或者使用 z-index 屬性將其置于其他元素之上來解決問題。

          11、元素的尺寸或位置問題:如果您的<span>元素的尺寸或位置不正確,那么 onclick 事件可能會無法被正確觸發。請確保您的元素在頁面上顯示正確,并且它們具有足夠的大小和可點擊區域以便用戶能夠點擊它們。

          12、原型鏈污染:如果您的頁面中存在來自第三方庫或插件的代碼,并且它們可能污染了全局作用域或 JavaScript 原型鏈,那么 onclick 事件可能會受到影響。在這種情況下,您需要確保您的代碼與其他庫或插件不會產生沖突,并且您的代碼使用了適當的命名空間和封裝技術。

          13、元素狀態問題:如果您的<span>元素處于禁用狀態、只讀狀態或其他狀態,那么 onclick 事件可能會被阻止。請確保您的元素的狀態正確設置,并且您的事件處理代碼能夠正確地處理這些狀態。

          14、跨域安全性限制:如果您的頁面中存在來自不同域名或協議的內容,并且它們試圖通過 onclick 事件來交互或傳遞數據,那么可能會受到瀏覽器的安全限制。在這種情況下,您需要了解并遵守瀏覽器的安全性限制,并使用跨域通信技術來傳遞數據。

          15、使用了錯誤的語法或方法:如果您的 onclick 事件處理代碼中存在語法錯誤或錯誤的方法調用,那么事件可能無法正確觸發。請確保您的代碼使用正確的語法和方法,以及正確的事件綁定方式。

          16、其他 JavaScript 錯誤:如果您的頁面中存在其他 JavaScript 錯誤,它們可能會影響 onclick 事件的綁定或觸發。在這種情況下,您需要檢查控制臺中是否有其他錯誤消息,并修復這些錯誤。

          17、網絡連接問題:如果您的頁面中包含需要從服務器加載的資源,例如腳本文件或圖像,那么網絡連接問題可能會影響 onclick 事件的綁定或觸發。在這種情況下,您需要確保您的網絡連接正常,并且您的頁面能夠正確地加載所有需要的資源。

          onclick 事件沒有反應可能會由多種原因引起,包括錯誤的語法、事件綁定方式、作用域問題、元素狀態問題、跨域安全性限制、網絡連接問題等等。您需要仔細檢查您的代碼,并采取適當的措施來解決這些問題。如果您無法解決問題,您可以尋求其他開發者或社區的幫助,并使用調試工具和控制臺來進一步分析和解決問題。使用現代的前端框架或庫可以幫助您簡化事件處理和交互邏輯,并提高代碼的可維護性和可重用性。


          主站蜘蛛池模板: 国产丝袜视频一区二区三区| 精品无码成人片一区二区98| 人妻视频一区二区三区免费| 亚洲Aⅴ无码一区二区二三区软件| 中字幕一区二区三区乱码 | 国产精品成人一区二区三区| 亚洲AV无码片一区二区三区 | 97精品国产一区二区三区 | 国产精品无码一区二区三级| 波多野结衣的AV一区二区三区| 国产精品污WWW一区二区三区| 亚洲国产精品一区二区第四页| 精品一区二区三区无码视频| 国产精品成人99一区无码| 日韩精品久久一区二区三区| 免费看AV毛片一区二区三区| 鲁大师成人一区二区三区| 无码日本电影一区二区网站| 国产精品熟女视频一区二区| 久久国产三级无码一区二区| 久久4k岛国高清一区二区| 国产精品日韩一区二区三区| 蜜桃臀无码内射一区二区三区| 午夜AV内射一区二区三区红桃视| 美女免费视频一区二区| 亚洲一区二区三区乱码在线欧洲| 精品亚洲av无码一区二区柚蜜| 成人精品视频一区二区三区不卡 | 亚洲日韩精品无码一区二区三区| 亚洲线精品一区二区三区| 日本免费一区二区三区四区五六区 | 日韩精品无码一区二区视频| 国产a∨精品一区二区三区不卡 | 在线电影一区二区| 国产91精品一区二区麻豆亚洲 | 亚洲一区二区三区免费视频| 国模无码一区二区三区| 99精品久久精品一区二区| 99无码人妻一区二区三区免费| 日韩十八禁一区二区久久| 色国产精品一区在线观看|