Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 日韩美女一级毛片a,亚洲日本视频在线观看,日本一级毛片中文字幕

          整合營銷服務(wù)商

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

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

          HTML <area>

          HTML <area> 標(biāo)簽

          帶有可點(diǎn)擊區(qū)域的圖像映射:

          <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> 標(biāo)簽。

          標(biāo)簽定義及使用說明

          <area> 標(biāo)簽定義圖像映射內(nèi)部的區(qū)域(圖像映射指的是帶有可點(diǎn)擊區(qū)域的圖像)。

          <area> 元素始終嵌套在 <map> 標(biāo)簽內(nèi)部。

          注釋: <img> 標(biāo)簽中的 usemap 屬性與 <map> 元素中的 name 相關(guān)聯(lián),以創(chuàng)建圖像與映射之間的關(guān)系。

          HTML 4.01 與 HTML5之間的差異

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

          HTML 與 XHTML 之間的差異

          在 HTML 中,<area> 標(biāo)簽沒有結(jié)束標(biāo)簽。

          在 XHTML 中,<area> 標(biāo)簽必須正確地關(guān)閉。

          屬性

          New :HTML5 中的新屬性。

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

          全局屬性

          <area> 標(biāo)簽支持 HTML 的全局屬性。

          事件屬性

          <area> 標(biāo)簽支持 HTML 的事件屬性。

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

          . JS的運(yùn)行機(jī)制

          介紹

          眾所周知JavaScript是一門單線程的語言,所以在JavaScript的世界中默認(rèn)的情況下,同一個時間節(jié)點(diǎn)只能做一件事情,這樣的設(shè)定就造成了JavaScript這門語言的一些局限性,比如在我們的頁面中加載一些遠(yuǎn)程數(shù)據(jù)時,如果按照單線程同步的方式運(yùn)行,一旦有HTTP請求向服務(wù)器發(fā)送,就會出現(xiàn)等待數(shù)據(jù)返回之前網(wǎng)頁假死的效果出現(xiàn)。因?yàn)?/span>JavaScript在同一個時間只能做一件事,這就導(dǎo)致了頁面渲染和事件的執(zhí)行,在這個過程中無法進(jìn)行。顯然在實(shí)際的開發(fā)中我們并沒有遇見過這種情況。

          關(guān)于同步和異步

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

          同步(阻塞)

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

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

          接下來通過下列的案例升級一下代碼的運(yùn)行場景:

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

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

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

          異步(非阻塞):

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

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

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

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

          通俗的講:

          通俗的講,同步和異步的關(guān)系是這樣的:

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

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

          總結(jié):

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

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

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

          JS的線程組成

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

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

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

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

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

          1. 【主線程】:這個線程用來執(zhí)行頁面的渲染,JavaScript代碼的運(yùn)行,事件的觸發(fā)等等
          2. 【工作線程】:這個線程是在幕后工作的,用來處理異步任務(wù)的執(zhí)行來實(shí)現(xiàn)非阻塞的運(yùn)行模式

          2. JavaScript的運(yùn)行模型

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

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

          從代碼片段開始分析

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

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

          按照字面分析:

          按照字面分析,我們創(chuàng)建了四個函數(shù)代表4個任務(wù),函數(shù)本身都是同步代碼。在執(zhí)行的時候會按照1,2,3,4進(jìn)行解析,解析過程中我們發(fā)現(xiàn)任務(wù)2和任務(wù)3被setTimeout進(jìn)行了定時托管,這樣就只能先運(yùn)行任務(wù)1和任務(wù)4了。當(dāng)任務(wù)1和任務(wù)4運(yùn)行完畢之后500毫秒后運(yùn)行任務(wù)3,1000毫米后運(yùn)行任務(wù)2。

          那么他們在實(shí)際運(yùn)行時又是經(jīng)歷了怎么樣的流程來運(yùn)行的呢?大概的流程我們以圖解的形式分析一下。

          圖解分析:

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

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

          我們發(fā)現(xiàn)同步任務(wù)的執(zhí)行速度是飛快的,這樣一下執(zhí)行棧已經(jīng)空了,而任務(wù)2和任務(wù)3還沒有到時間,這樣我們的事件循環(huán)就會開始工作等待任務(wù)隊(duì)列中的任務(wù)進(jìn)入,接下來就是執(zhí)行異步任務(wù)的時候了。

          我們發(fā)現(xiàn)任務(wù)隊(duì)列并不是一下子就會將任務(wù)2和任務(wù)三一起放進(jìn)去,而是哪個計時器到時間了哪個放進(jìn)去,這樣我們的事件循環(huán)就會發(fā)現(xiàn)隊(duì)列中的任務(wù),并且將任務(wù)拿到執(zhí)行棧中進(jìn)行消費(fèi),此時會輸出任務(wù)3的內(nèi)容。

          到這就是最后一次執(zhí)行,當(dāng)執(zhí)行完畢后工作線程中沒有計時任務(wù),任務(wù)隊(duì)列的任務(wù)清空程序到此執(zhí)行完畢。

          總結(jié)

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

          關(guān)于執(zhí)行棧

          執(zhí)行棧是一個棧的數(shù)據(jù)結(jié)構(gòu),當(dāng)我們運(yùn)行單層函數(shù)時,執(zhí)行棧執(zhí)行的函數(shù)進(jìn)棧后,會出棧銷毀然后下一個進(jìn)棧下一個出棧,當(dāng)有函數(shù)嵌套調(diào)用的時候棧中就會堆積棧幀,比如我們查看下面的例子:

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

          我們根據(jù)字面閱讀就能很簡單的分析出輸出的結(jié)果會是

          /*
          task1執(zhí)行
          task2執(zhí)行
          task3執(zhí)行
          task3執(zhí)行完畢
          task2執(zhí)行完畢
          task1執(zhí)行完畢
          */

          那么這種嵌套函數(shù)在執(zhí)行棧中的操作流程是什么樣的呢?

          第一次執(zhí)行的時候調(diào)用task1函數(shù)執(zhí)行到console.log的時候先進(jìn)行輸出,接下來會遇到task2函數(shù)的調(diào)用會出現(xiàn)下面的情況:

          執(zhí)行到此時檢測到task2中還有調(diào)用task3的函數(shù),那么就會繼續(xù)進(jìn)入task3中執(zhí)行,如下圖:

          在執(zhí)行完task3中的輸出之后task3內(nèi)部沒有其他代碼,那么task3函數(shù)就算執(zhí)行完畢那么就會發(fā)生出棧工作。

          此時我們會發(fā)現(xiàn)task3出棧之后程序運(yùn)行又會回到task2的函數(shù)中繼續(xù)他的執(zhí)行。接下來會發(fā)生相同的事情。

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

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

          關(guān)于遞歸

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

          關(guān)于執(zhí)行棧的深度:

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

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

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

          如何跨越遞歸限制

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

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

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

          在了解原因之前我們先看控制臺的輸出,結(jié)合控制臺輸出我們發(fā)現(xiàn)確實(shí)超過了界限也沒有報錯。

          圖解原因:

          這個是因?yàn)槲覀冞@里使用了異步任務(wù)去調(diào)用遞歸中的函數(shù),那么這個函數(shù)在執(zhí)行的時候就不只使用棧進(jìn)行執(zhí)行了。

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


          再看有了異步任務(wù)的遞歸:

          有了異步任務(wù)之后我們的遞歸就不會疊加棧幀了,因?yàn)榉湃牍ぷ骶€程之后該函數(shù)就結(jié)束了,可以出棧銷毀,那么在執(zhí)行棧中就永遠(yuǎn)都是只有一個任務(wù)在運(yùn)行,這樣就防止了棧幀的無限疊加,從而解決了無限遞歸的問題,不過異步遞歸的過程是無法保證運(yùn)行速度的,在實(shí)際的工作場景中,如果考慮性能問題,還需要使用 while 循環(huán)等解決方案,來保證運(yùn)行效率的問題,在實(shí)際工作場景中,盡量避免遞歸循環(huán),因?yàn)檫f歸循環(huán)就算控制在有限棧幀的疊加,其性能也遠(yuǎn)遠(yuǎn)不及指針循環(huán)。


          3.宏任務(wù)和微任務(wù)

          在明確了事件循環(huán)模型以及JavaScript的執(zhí)行流程后,我們認(rèn)識了一個叫做任務(wù)隊(duì)列的容器,他的數(shù)據(jù)結(jié)構(gòu)式隊(duì)列的結(jié)構(gòu)。所有除同步任務(wù)外的代碼都會在工作線程中,按照他到達(dá)的時間節(jié)點(diǎn)有序的進(jìn)入任務(wù)隊(duì)列,而且任務(wù)隊(duì)列中的異步任務(wù)又分為【宏任務(wù)】和【微任務(wù)】。

          舉個例子:

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

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

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

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

          從取卡到辦理追加業(yè)務(wù)完成的這個過程,就是微任務(wù)的實(shí)際體現(xiàn)。在JavaScript運(yùn)行環(huán)境中,包括主線程代碼在內(nèi),可以理解為所有的任務(wù)內(nèi)部都存在一個微任務(wù)隊(duì)列,在每下一個宏任務(wù)執(zhí)行前,事件循環(huán)系統(tǒng)都會先檢測當(dāng)前的代碼塊中是否包含已經(jīng)注冊的微任務(wù),并將隊(duì)列中的微任務(wù)優(yōu)先執(zhí)行完畢,進(jìn)而執(zhí)行下一個宏任務(wù)。所以實(shí)際的任務(wù)隊(duì)列的結(jié)構(gòu)是這樣的,如圖:

          宏任務(wù)與微任務(wù)的介紹

          由上述內(nèi)容得知JavaScript中存在兩種異步任務(wù),一種是宏任務(wù)一種是微任務(wù),他們的特點(diǎn)如下:

          宏任務(wù)

          宏任務(wù)是JavaScript中最原始的異步任務(wù),包括setTimeoutsetIntervalAJAX等,在代碼執(zhí)行環(huán)境中按照同步代碼的順序,逐個進(jìn)入工作線程掛起,再按照異步任務(wù)到達(dá)的時間節(jié)點(diǎn),逐個進(jìn)入異步任務(wù)隊(duì)列,最終按照隊(duì)列中的順序進(jìn)入函數(shù)執(zhí)行棧進(jìn)行執(zhí)行。

          微任務(wù)

          微任務(wù)是隨著ECMA標(biāo)準(zhǔn)升級提出的新的異步任務(wù),微任務(wù)在異步任務(wù)隊(duì)列的基礎(chǔ)上增加了【微任務(wù)】的概念,每一個宏任務(wù)執(zhí)行前,程序會先檢測其中是否有當(dāng)次事件循環(huán)未執(zhí)行的微任務(wù),優(yōu)先清空本次的微任務(wù)后,再執(zhí)行下一個宏任務(wù),每一個宏任務(wù)內(nèi)部可注冊當(dāng)次任務(wù)的微任務(wù)隊(duì)列,再下一個宏任務(wù)執(zhí)行前運(yùn)行,微任務(wù)也是按照進(jìn)入隊(duì)列的順序執(zhí)行的。

          總結(jié)

          JavaScript的運(yùn)行環(huán)境中,代碼的執(zhí)行流程是這樣的:

          1. 默認(rèn)的同步代碼按照順序從上到下,從左到右運(yùn)行,運(yùn)行過程中注冊本次的微任務(wù)和后續(xù)的宏任務(wù):
          2. 執(zhí)行本次同步代碼中注冊的微任務(wù),并向任務(wù)隊(duì)列注冊微任務(wù)中包含的宏任務(wù)和微任務(wù)
          3. 將下一個宏任務(wù)開始前的所有微任務(wù)執(zhí)行完畢
          4. 執(zhí)行最先進(jìn)入隊(duì)列的宏任務(wù),并注冊當(dāng)次的微任務(wù)和后續(xù)的宏任務(wù),宏任務(wù)會按照當(dāng)前任務(wù)隊(duì)列的隊(duì)尾繼續(xù)向下排列

          常見的宏任務(wù)和微任務(wù)劃分

          宏任務(wù)

          #

          瀏覽器

          Node

          I/O

          ?

          ?

          setTimeout

          ?

          ?

          setInterval

          ?

          ?

          setImmediate

          ?

          ?

          requestAnimationFrame

          ?

          ?

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

          微任務(wù)

          #

          瀏覽器

          Node

          process.nextTick

          ?

          ?

          MutationObserver

          ?

          ?

          Promise.then catch finally

          ?

          ?

          經(jīng)典筆試題

          代碼輸出順序問題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')

          解析:

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

          按照同步先行,異步靠后的原則,閱讀代碼時,先分析同步代碼和異步代碼,Promise對象雖然是微任務(wù),但是new Promise時的回調(diào)函數(shù)是同步執(zhí)行的,所以優(yōu)先輸出promise 1 和 promise 2。

          resolve執(zhí)行時Promise對象的狀態(tài)變更為已完成,所以then函數(shù)的回調(diào)被注冊到微任務(wù)事件中,此時并不執(zhí)行,所以接下來應(yīng)該輸出end

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

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

          參考如下代碼:

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

          該代碼在瀏覽器運(yùn)行時,控制臺會每間隔1秒進(jìn)行一次輸出,輸出的i就是loop函數(shù)執(zhí)行的次數(shù),如下圖:

          這個輸出意味著requestAnimationFrame函數(shù)的執(zhí)行頻率是每秒鐘60次左右,他是按照瀏覽器的刷新率來進(jìn)行執(zhí)行的,也就是當(dāng)屏幕刷新一次時該函數(shù)就會觸發(fā)一次,相當(dāng)于運(yùn)行間隔是16毫秒左右。

          繼續(xù)參考下列代碼:

          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('經(jīng)過了1秒')
            }
            setTimeout(loop,0)
          }
          loop()

          該代碼結(jié)構(gòu)與上面的案例類似,循環(huán)是采用setTimeout進(jìn)行控制的,所以參考運(yùn)行結(jié)果,如圖:


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

          由于這兩個異步的宏任務(wù)出發(fā)時機(jī)和執(zhí)行頻率不同,會導(dǎo)致三個宏任務(wù)的觸發(fā)結(jié)果不同,如果我們打開網(wǎng)頁時,恰好趕上5毫秒內(nèi)執(zhí)行了網(wǎng)頁的重繪事件,requestAnimationFrame在工作線程中就會到達(dá)觸發(fā)時機(jī)優(yōu)先進(jìn)入任務(wù)隊(duì)列,所以此時會輸出:UI update->timer1->timer2

          而當(dāng)打開網(wǎng)頁時上一次的重繪剛結(jié)束,下一次重繪的觸發(fā)是16毫秒后,此時setTimeout注冊的兩個任務(wù)在工作線程中就會優(yōu)先到達(dá)觸發(fā)時機(jī),這時輸出的結(jié)果是:timer1->timer2->UI update

          所以此案例的運(yùn)行結(jié)果如下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);
          })

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

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

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

          為什么不是2,4,1,3呢?由于事件執(zhí)行時并不會阻斷JS默認(rèn)代碼的運(yùn)行,所以事件任務(wù)也是異步任務(wù),并且是宏任務(wù),所以兩個事件相當(dāng)于按順序執(zhí)行的兩個宏任務(wù)。

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

          而下一個事件的內(nèi)容是相同道理,所以輸出順序?yàn)椋?,1,4,3。

          總結(jié)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

          12、原型鏈污染:如果您的頁面中存在來自第三方庫或插件的代碼,并且它們可能污染了全局作用域或 JavaScript 原型鏈,那么 onclick 事件可能會受到影響。在這種情況下,您需要確保您的代碼與其他庫或插件不會產(chǎn)生沖突,并且您的代碼使用了適當(dāng)?shù)拿臻g和封裝技術(shù)。

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

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

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

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

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

          onclick 事件沒有反應(yīng)可能會由多種原因引起,包括錯誤的語法、事件綁定方式、作用域問題、元素狀態(tài)問題、跨域安全性限制、網(wǎng)絡(luò)連接問題等等。您需要仔細(xì)檢查您的代碼,并采取適當(dāng)?shù)拇胧﹣斫鉀Q這些問題。如果您無法解決問題,您可以尋求其他開發(fā)者或社區(qū)的幫助,并使用調(diào)試工具和控制臺來進(jìn)一步分析和解決問題。使用現(xiàn)代的前端框架或庫可以幫助您簡化事件處理和交互邏輯,并提高代碼的可維護(hù)性和可重用性。


          主站蜘蛛池模板: 激情内射日本一区二区三区| 国产乱码精品一区三上| 国产成人无码一区二区三区| 亚洲狠狠狠一区二区三区| 国产福利一区视频| 91香蕉福利一区二区三区| 熟女少妇丰满一区二区| 国产伦精品一区二区三区视频小说| 青青青国产精品一区二区| 久久无码一区二区三区少妇| 国产凸凹视频一区二区| 国产成人久久精品区一区二区 | 国产一区二区三区视频在线观看| 久久99国产精一区二区三区| 久久精品国产亚洲一区二区三区 | 国产高清在线精品一区小说 | 日韩免费无码视频一区二区三区| 亚洲国产一区二区三区| 美女AV一区二区三区| 春暖花开亚洲性无区一区二区 | 无码av免费一区二区三区| 狠狠做深爱婷婷综合一区 | 麻豆va一区二区三区久久浪| 国产午夜精品片一区二区三区| 色噜噜狠狠一区二区三区果冻| 国产AV午夜精品一区二区三区| 高清一区二区三区免费视频| 久久99精品一区二区三区| 久久国产精品一区免费下载| 午夜福利一区二区三区高清视频| 久久国产精品视频一区| 国产日韩一区二区三区| 国产一区二区好的精华液| 国产一区二区三区不卡在线观看 | 麻豆天美国产一区在线播放| 一区二区三区国产精品| 国产精品一区在线播放| 亚洲Av无码国产一区二区| 精品无码成人片一区二区| 国产另类ts人妖一区二区三区| 亚洲日韩精品无码一区二区三区|