詳細(xì)拆解導(dǎo)航流程:從輸入U(xiǎn)RL到頁(yè)面展示,這中間發(fā)生了什么?
文章摘錄:極客專欄———《瀏覽器工作原理與實(shí)踐》
編者薦語(yǔ):
“在瀏覽器里,從輸入 URL 到頁(yè)面展示,這中間發(fā)生了什么?”這是一道經(jīng)典的面試題,能比較全面地考察應(yīng)聘者知識(shí)的掌握程度,其中涉及到了網(wǎng)絡(luò)、操作系統(tǒng)、Web 等一系列的知識(shí)。
導(dǎo)航的過(guò)程
所謂導(dǎo)航,就是用戶發(fā)出 URL 請(qǐng)求到頁(yè)面開始解析的這個(gè)過(guò)程,就叫做導(dǎo)航。
那么今天我們就一起來(lái)探索下這個(gè)流程,下圖是我梳理出的“從輸入 URL 到頁(yè)面展示完整流程示意圖”:

從圖中可以看出,整個(gè)過(guò)程需要各個(gè)進(jìn)程之間的配合,所以在開始正式流程之前,我們還是先來(lái)快速回顧下瀏覽器進(jìn)程、渲染進(jìn)程和網(wǎng)絡(luò)進(jìn)程的主要職責(zé)。
瀏覽器進(jìn)程主要負(fù)責(zé)用戶交互、子進(jìn)程管理和文件儲(chǔ)存等功能。 網(wǎng)絡(luò)進(jìn)程是面向渲染進(jìn)程和瀏覽器進(jìn)程等提供網(wǎng)絡(luò)下載功能。 渲染進(jìn)程主要職責(zé)是把從網(wǎng)絡(luò)下載的 HTML、JavaScript、CSS、圖片等資源解析為可以顯示和交互的頁(yè)面。
回顧了瀏覽器的進(jìn)程架構(gòu)后,我們?cè)俳Y(jié)合上圖來(lái)看下這個(gè)完整的流程,可以看出,整個(gè)流程包含了許多步驟,我把其中幾個(gè)核心的節(jié)點(diǎn)用藍(lán)色背景標(biāo)記出來(lái)了。這個(gè)過(guò)程可以大致描述為如下。
首先,瀏覽器進(jìn)程接收到用戶輸入的 URL 請(qǐng)求,瀏覽器進(jìn)程便將該 URL 轉(zhuǎn)發(fā)給網(wǎng)絡(luò)進(jìn)程。 然后,在網(wǎng)絡(luò)進(jìn)程中發(fā)起真正的 URL請(qǐng)求。 接著網(wǎng)絡(luò)進(jìn)程接收到了響應(yīng)頭數(shù)據(jù),便解析響應(yīng)頭數(shù)據(jù),并將數(shù)據(jù)轉(zhuǎn)發(fā)給瀏覽器進(jìn)程。 瀏覽器進(jìn)程接收到網(wǎng)絡(luò)進(jìn)程的響應(yīng)頭數(shù)據(jù)之后,發(fā)送“提交導(dǎo)航 (CommitNavigation)”消息到渲染進(jìn)程; 渲染進(jìn)程接收到“提交導(dǎo)航”的消息之后,便開始準(zhǔn)備接收 HTML 數(shù)據(jù),接收數(shù)據(jù)的方式是直接和網(wǎng)絡(luò)進(jìn)程建立數(shù)據(jù)管道; 最后渲染進(jìn)程會(huì)向瀏覽器進(jìn)程“確認(rèn)提交”,這是告訴瀏覽器進(jìn)程:“已經(jīng)準(zhǔn)備好接受和解析頁(yè)面數(shù)據(jù)了”。 瀏覽器進(jìn)程接收到渲染進(jìn)程“提交文檔”的消息之后,便開始移除之前舊的文檔,然后更新瀏覽器進(jìn)程中的頁(yè)面狀態(tài)。
補(bǔ)充:安全沙箱的意義
因?yàn)殇秩具M(jìn)程所有的內(nèi)容都是通過(guò)網(wǎng)絡(luò)獲取的,會(huì)存在一些惡意代碼利用瀏覽器漏洞對(duì)系統(tǒng)進(jìn)行攻擊,所以運(yùn)行在渲染進(jìn)程里面的代碼是不被信任的。
這也是為什么 Chrome 會(huì)讓渲染進(jìn)程運(yùn)行在安全沙箱里,就是為了保證系統(tǒng)的安全,保證系統(tǒng)的操作權(quán)限不被竄改,以及本地保存的重要信息不被修改。
從輸入 URL 到頁(yè)面展示
現(xiàn)在我們知道了瀏覽器幾個(gè)主要進(jìn)程的職責(zé),還有在導(dǎo)航過(guò)程中需要經(jīng)歷的幾個(gè)主要的階段,下面我們就來(lái)詳細(xì)分析下這些階段,同時(shí)也就解答了開頭所說(shuō)的那道經(jīng)典的面試題。
1. 用戶輸入
當(dāng)用戶在地址欄中輸入一個(gè)查詢關(guān)鍵字時(shí),地址欄會(huì)判斷輸入的關(guān)鍵字是搜索內(nèi)容,還是請(qǐng)求的 URL。
搜素內(nèi)容:地址欄會(huì)使用瀏覽器默認(rèn)的搜索引擎,來(lái)合成新的帶搜索關(guān)鍵字的URL。 URL地址:比如輸入的事www.baidu.com,那么地址欄會(huì)根據(jù)規(guī)則,把這段內(nèi)容加上協(xié)議,合成為完整的 URL,如 https://www.baidu.com。
當(dāng)用戶輸入關(guān)鍵字并鍵入回車之后,瀏覽器當(dāng)前的頁(yè)面并沒(méi)有被立即替換成新的頁(yè)面,是為什么呢?
是因?yàn)闉g覽器還給了當(dāng)前頁(yè)面依次執(zhí)行beforeunload事件的機(jī)會(huì),beforeunload事件允許頁(yè)面在退出之前執(zhí)行一些數(shù)據(jù)清理操作,還可以詢問(wèn)用戶是否要離開當(dāng)前頁(yè)面,比如:
當(dāng)前頁(yè)面可能有未完成的表單提交等情況
因此用戶可以通過(guò)beforeunload事件來(lái)取消導(dǎo)航讓瀏覽器不再執(zhí)行任何后續(xù)工作。
當(dāng)前頁(yè)面沒(méi)有監(jiān)聽 beforeunload 事件或者同意了繼續(xù)后續(xù)流程,那么瀏覽器便進(jìn)入下圖的狀態(tài):

從圖中可以看出,此時(shí)圖中頁(yè)面顯示的依然是之前打開的頁(yè)面內(nèi)容,并沒(méi)立即替換為極客時(shí)間的頁(yè)面。因?yàn)樾枰却?strong style="font-weight: bold;line-height: 1.75em;color: rgb(74,74,74);">提交文檔階段,頁(yè)面內(nèi)容才會(huì)被替換。
這里簡(jiǎn)單說(shuō)一下,提交文檔階段都做了什么:
所謂提交文檔,就是指瀏覽器進(jìn)程將網(wǎng)絡(luò)進(jìn)程接收到的 HTML 數(shù)據(jù)提交給渲染進(jìn)程,可以理解為需要等待渲染進(jìn)程和網(wǎng)路進(jìn)程之間建立傳輸數(shù)據(jù)的”管道“,只有等文檔數(shù)據(jù)傳輸完畢之后,渲染進(jìn)程才會(huì)返回一些頁(yè)面重要的信息和狀態(tài)給瀏覽器進(jìn)程,才會(huì)更新當(dāng)前頁(yè)面。
2. URL 請(qǐng)求過(guò)程
接下來(lái),便進(jìn)入了頁(yè)面資源請(qǐng)求過(guò)程。這是,瀏覽器進(jìn)程會(huì)把URL請(qǐng)求地址發(fā)送至網(wǎng)絡(luò)進(jìn)程,當(dāng)網(wǎng)絡(luò)進(jìn)程收到URL之后,才會(huì)開始真正的URL請(qǐng)求流程:
1))首先,網(wǎng)絡(luò)進(jìn)程會(huì)查找本地緩存是否緩存了該資源。
有緩存資源:直接返回資源給瀏覽器進(jìn)程 緩存中無(wú)請(qǐng)求資源:DNS解析,以獲取域名的服務(wù)器IP地址。如果請(qǐng)求協(xié)議是 HTTPS,那么還需要建立 TLS 連接。
2) 然后,利用IP地址和服務(wù)器建議TCP連接。
3) 連接建立之后,瀏覽器端會(huì)構(gòu)建請(qǐng)求行、請(qǐng)求頭等信息,并把和該域名相關(guān)的Cookie等數(shù)據(jù)附加到請(qǐng)求頭中,然后向服務(wù)器發(fā)送構(gòu)建的請(qǐng)求信息。
4)服務(wù)器收到請(qǐng)求信息后,會(huì)根據(jù)請(qǐng)求信息生成響應(yīng)數(shù)據(jù)(包括響應(yīng)行、響應(yīng)頭和響應(yīng)體等信息),并發(fā)給網(wǎng)絡(luò)進(jìn)程。等網(wǎng)絡(luò)進(jìn)程接收了響應(yīng)行和響應(yīng)頭之后,就開始解析響應(yīng)頭的內(nèi)容了。
① 重定向
在接收到服務(wù)器返回的響應(yīng)頭后,網(wǎng)絡(luò)進(jìn)程開始解析響應(yīng)頭,如果發(fā)現(xiàn)返回的狀態(tài)碼是 301 或者 302,那么說(shuō)明服務(wù)器需要瀏覽器重定向到其他 URL。這時(shí)網(wǎng)絡(luò)進(jìn)程會(huì)從響應(yīng)頭的 Location 字段里面讀取重定向的地址,然后再發(fā)起新的 HTTP 或者 HTTPS 請(qǐng)求,一切又重頭開始了。
比如,我們?cè)诮K端里輸入以下命令:
curl -I http://time.geekbang.org/
curl -I + URL的命令是接收服務(wù)器返回的響應(yīng)頭的信息。執(zhí)行命令后,我們看到服務(wù)器返回的響應(yīng)頭信息如下:

從圖中可以看出,極客時(shí)間服務(wù)器會(huì)通過(guò)重定向的方式把所有 HTTP 請(qǐng)求轉(zhuǎn)換為 HTTPS 請(qǐng)求。也就是說(shuō)你使用 HTTP 向極客時(shí)間服務(wù)器請(qǐng)求時(shí),服務(wù)器會(huì)返回一個(gè)包含有 301 或者 302 狀態(tài)碼響應(yīng)頭,并把響應(yīng)頭的 Location 字段中填上 HTTPS 的地址,這就是告訴了瀏覽器要重新導(dǎo)航到新的地址上。
下面我們?cè)偈褂?HTTPS 協(xié)議對(duì)極客時(shí)間發(fā)起請(qǐng)求,看看服務(wù)器的響應(yīng)頭信息是什么樣子的。
curl -I https://time.geekbang.org/
我們看到服務(wù)器返回如下信息:

從圖中可以看出,服務(wù)器返回的響應(yīng)頭的狀態(tài)碼是 200,這是告訴瀏覽器一切正常,可以繼續(xù)往下處理該請(qǐng)求了。
好了,以上是重定向內(nèi)容的介紹?,F(xiàn)在你應(yīng)該理解了,在導(dǎo)航過(guò)程中,如果服務(wù)器響應(yīng)行的狀態(tài)碼包含了 301、302 一類的跳轉(zhuǎn)信息,瀏覽器會(huì)跳轉(zhuǎn)到新的地址繼續(xù)導(dǎo)航;如果響應(yīng)行是 200,那么表示瀏覽器可以繼續(xù)處理該請(qǐng)求。
② 響應(yīng)數(shù)據(jù)類型處理
在處理了跳轉(zhuǎn)信息之后,我們繼續(xù)導(dǎo)航流程的分析。URL 請(qǐng)求的數(shù)據(jù)類型,有時(shí)候是一個(gè)下載類型,有時(shí)候是正常的 HTML 頁(yè)面,那么瀏覽器是如何區(qū)分它們呢?
答案是 Content-Type。Content-Type 是 HTTP 頭中一個(gè)非常重要的字段, 它告訴瀏覽器服務(wù)器返回的響應(yīng)體數(shù)據(jù)是什么類型,然后瀏覽器會(huì)根據(jù) Content-Type 的值來(lái)決定如何顯示響應(yīng)體的內(nèi)容。
這里我們還是以極客時(shí)間為例,看看極客時(shí)間官網(wǎng)返回的 Content-Type 值是什么。在終端輸入以下命令:
curl -I https://time.geekbang.org/
返回信息如下圖:

從圖中可以看到,響應(yīng)頭中的 Content-type 字段的值是 text/html,這就是告訴瀏覽器,服務(wù)器返回的數(shù)據(jù)是 HTML 格式。
接下來(lái)我們?cè)賮?lái)利用 curl 來(lái)請(qǐng)求極客時(shí)間安裝包的地址,如下所示:
curl -I https://res001.geekbang.org/apps/geektime/android/2.3.1/official/geektime_2.3.1_20190527-2136_offical.apk
請(qǐng)求后返回的響應(yīng)頭信息如下:

從返回的響應(yīng)頭信息來(lái)看,其 Content-Type 的值是 application/octet-stream,顯示數(shù)據(jù)是字節(jié)流類型的,通常情況下,瀏覽器會(huì)按照下載類型來(lái)處理該請(qǐng)求。
需要注意的是,如果服務(wù)器配置 Content-Type 不正確,比如將 text/html 類型配置成 application/octet-stream 類型,那么瀏覽器可能會(huì)曲解文件內(nèi)容,比如會(huì)將一個(gè)本來(lái)是用來(lái)展示的頁(yè)面,變成了一個(gè)下載文件。
所以,不同 Content-Type 的后續(xù)處理流程也截然不同。如果 Content-Type 字段的值被瀏覽器判斷為下載類型,那么該請(qǐng)求會(huì)被提交給瀏覽器的下載管理器,同時(shí)該 URL 請(qǐng)求的導(dǎo)航流程就此結(jié)束。但如果是 HTML,那么瀏覽器則會(huì)繼續(xù)進(jìn)行導(dǎo)航流程。由于 Chrome 的頁(yè)面渲染是運(yùn)行在渲染進(jìn)程中的,所以接下來(lái)就需要準(zhǔn)備渲染進(jìn)程了。
3. 準(zhǔn)備渲染進(jìn)程
默認(rèn)情況下,Chrome 會(huì)為每個(gè)頁(yè)面分配一個(gè)渲染進(jìn)程,也就是說(shuō),每打開一個(gè)新頁(yè)面就會(huì)配套創(chuàng)建一個(gè)新的渲染進(jìn)程。但是,也有一些例外,在某些情況下,瀏覽器會(huì)讓多個(gè)頁(yè)面直接運(yùn)行在同一個(gè)渲染進(jìn)程中。
比如我從極客時(shí)間的首頁(yè)里面打開了另外一個(gè)頁(yè)面——算法訓(xùn)練營(yíng),我們看下圖的 Chrome 的任務(wù)管理器截圖:

從圖中可以看出,打開的這三個(gè)頁(yè)面都是運(yùn)行在同一個(gè)渲染進(jìn)程中,進(jìn)程 ID 是 23601。
那什么情況下多個(gè)頁(yè)面會(huì)同時(shí)運(yùn)行在一個(gè)渲染進(jìn)程中呢?
要解決這個(gè)問(wèn)題,我們就需要先了解下什么是同一站點(diǎn)(same-site)。具體地講,我們將“同一站點(diǎn)”定義為根域名(例如,geekbang.org)加上協(xié)議(例如,https:// 或者 http://),還包含了該根域名下的所有子域名和不同的端口,比如下面這三個(gè):
https://time.geekbang.org
https://www.geekbang.org
https://www.geekbang.org:8080
它們都是屬于同一站點(diǎn),因?yàn)樗鼈兊膮f(xié)議都是 HTTPS,而且根域名也都是 geekbang.org。
Chrome 的默認(rèn)策略是,每個(gè)標(biāo)簽對(duì)應(yīng)一個(gè)渲染進(jìn)程。但如果從一個(gè)頁(yè)面打開了另一個(gè)新頁(yè)面,而新頁(yè)面和當(dāng)前頁(yè)面屬于同一站點(diǎn)的話,那么新頁(yè)面會(huì)復(fù)用父頁(yè)面的渲染進(jìn)程。官方把這個(gè)默認(rèn)策略叫 process-per-site-instance。
那若新頁(yè)面和當(dāng)前頁(yè)面不屬于同一站點(diǎn),情況又會(huì)發(fā)生什么樣的變化呢?比如我通過(guò)極客邦頁(yè)面里的鏈接打開 InfoQ 的官網(wǎng)(https://www.infoq.cn/ ), 因?yàn)?infoq.cn 和 geekbang.org 不屬于同一站點(diǎn),所以 infoq.cn 會(huì)使用一個(gè)新的渲染進(jìn)程,你可以參考下圖:

從圖中任務(wù)管理器可以看出:由于極客邦和極客時(shí)間的標(biāo)簽頁(yè)擁有相同的協(xié)議和根域名,所以它們屬于同一站點(diǎn),并運(yùn)行在同一個(gè)渲染進(jìn)程中;而 infoq.cn 的根域名不同于 geekbang.org,也就是說(shuō) InfoQ 和極客邦不屬于同一站點(diǎn),因此它們會(huì)運(yùn)行在兩個(gè)不同的渲染進(jìn)程之中。
總結(jié)來(lái)說(shuō),打開一個(gè)新頁(yè)面采用的渲染進(jìn)程策略就是:
通常情況下,打開新的頁(yè)面都會(huì)使用單獨(dú)的渲染進(jìn)程; 如果從 A 頁(yè)面打開 B 頁(yè)面,且 A 和 B 都屬于同一站點(diǎn)的話,那么 B 頁(yè)面復(fù)用 A 頁(yè)面的渲染進(jìn)程;如果是其他情況,瀏覽器進(jìn)程則會(huì)為 B 創(chuàng)建一個(gè)新的渲染進(jìn)程。
渲染進(jìn)程準(zhǔn)備好之后,還不能立即進(jìn)入文檔解析狀態(tài),因?yàn)榇藭r(shí)的文檔數(shù)據(jù)還在網(wǎng)絡(luò)進(jìn)程中,并沒(méi)有提交給渲染進(jìn)程,所以下一步就進(jìn)入了提交文檔階段。
4. 提交文檔
所謂提交文檔,就是指瀏覽器進(jìn)程將網(wǎng)絡(luò)進(jìn)程接收到的 HTML 數(shù)據(jù)提交給渲染進(jìn)程,具體流程是這樣的:
首先當(dāng)瀏覽器進(jìn)程接收到網(wǎng)絡(luò)進(jìn)程的響應(yīng)頭數(shù)據(jù)之后,便向渲染進(jìn)程發(fā)起“提交文檔”的消息; 渲染進(jìn)程接收到“提交文檔”的消息后,會(huì)和網(wǎng)絡(luò)進(jìn)程建立傳輸數(shù)據(jù)的“管道”; 瀏覽器進(jìn)程在收到“確認(rèn)提交”的消息后,會(huì)更新瀏覽器界面狀態(tài),包括了安全狀態(tài)、地址欄的 URL、前進(jìn)后退的歷史狀態(tài),并更新 Web 頁(yè)面。
其中,當(dāng)瀏覽器進(jìn)程確認(rèn)提交之后,更新內(nèi)容如下圖所示:

這也就解釋了為什么在瀏覽器的地址欄里面輸入了一個(gè)地址后,之前的頁(yè)面沒(méi)有立馬消失,而是要加載一會(huì)兒才會(huì)更新頁(yè)面。
到這里,一個(gè)完整的導(dǎo)航流程就“走”完了,這之后就要進(jìn)入渲染階段了。
5. 渲染階段
一旦文檔被提交,渲染進(jìn)程便開始頁(yè)面解析和子資源加載了,關(guān)于這個(gè)階段的完整過(guò)程,我會(huì)在下一篇公眾號(hào)中來(lái)專門介紹。
這里你只需要先了解一旦頁(yè)面生成完成,渲染進(jìn)程會(huì)發(fā)送一個(gè)消息給瀏覽器進(jìn)程,瀏覽器接收到消息后,會(huì)停止標(biāo)簽圖標(biāo)上的加載動(dòng)畫。如下所示:

總結(jié):
服務(wù)器可以根據(jù)響應(yīng)頭來(lái)控制瀏覽器的行為,如跳轉(zhuǎn)、網(wǎng)絡(luò)數(shù)據(jù)類型(響應(yīng)頭:Content-Type)判斷。 Chrome 默認(rèn)采用每個(gè)標(biāo)簽對(duì)應(yīng)一個(gè)渲染進(jìn)程,但是如果兩個(gè)頁(yè)面屬于同一站點(diǎn),那這兩個(gè)標(biāo)簽會(huì)使用同一個(gè)渲染進(jìn)程。 瀏覽器的導(dǎo)航過(guò)程涵蓋了從用戶發(fā)起請(qǐng)求到提交文檔給渲染進(jìn)程的中間所有階段。
導(dǎo)航流程很重要,它是網(wǎng)絡(luò)加載流程和渲染流程之間的一座橋梁,如果你理解了導(dǎo)航流程,那么你就能完整串起來(lái)整個(gè)頁(yè)面顯示流程,這對(duì)于你理解瀏覽器的工作原理起到了點(diǎn)睛的作用。
最后我們思考一下開頭的面試題,用自己的語(yǔ)言來(lái)概括一下:在瀏覽器里,從輸入U(xiǎn)RL到頁(yè)面展示,這中間發(fā)生了什么?
從輸入U(xiǎn)RL到頁(yè)面展示,這中間發(fā)生了什么?
我們可以概括為三個(gè)階段,分別是用戶輸入階段,URL請(qǐng)求階段,準(zhǔn)備渲染進(jìn)程階段。
i. 用戶輸入階段
用戶輸入url并回車,瀏覽器進(jìn)程檢查url,組裝協(xié)議,構(gòu)成完整的url。
ii. URL 請(qǐng)求過(guò)程
瀏覽器進(jìn)程通過(guò)進(jìn)程間通信(IPC)把URL請(qǐng)求發(fā)送給網(wǎng)絡(luò)進(jìn)程。
網(wǎng)絡(luò)進(jìn)程接收到URL請(qǐng)求后檢查本地緩存是否緩存了該請(qǐng)求資源
如果有則將該資源返回給瀏覽器進(jìn)程 如果沒(méi)有,網(wǎng)絡(luò)進(jìn)程向web服務(wù)器發(fā)起http請(qǐng)求(網(wǎng)絡(luò)請(qǐng)求),請(qǐng)求流程如下: 進(jìn)行DNS解析,獲取服務(wù)器ip地址 利用ip地址和服務(wù)器建立tcp連接 構(gòu)建請(qǐng)求頭信息 發(fā)送請(qǐng)求頭信息 服務(wù)器響應(yīng)后,網(wǎng)絡(luò)進(jìn)程接收響應(yīng)頭和響應(yīng)信息,并解析響應(yīng)內(nèi)容
網(wǎng)絡(luò)進(jìn)程解析響應(yīng)流程:
檢查狀態(tài)碼,如果是301/302,則需要重定向,從Location自動(dòng)中讀取地址,重新進(jìn)行第3步
200 狀態(tài)碼響應(yīng)處理,檢查響應(yīng)類型Content-Type,如果是字節(jié)流類型,則將該請(qǐng)求提交給下載管理器,該導(dǎo)航流程結(jié)束,不再進(jìn)行后續(xù)的渲染
如果是html則通知瀏覽器進(jìn)程準(zhǔn)備渲染進(jìn)程準(zhǔn)備進(jìn)行渲染。
iii. 準(zhǔn)備渲染進(jìn)程
準(zhǔn)備渲染進(jìn)程:對(duì)文檔進(jìn)行頁(yè)面解析和子資源加載
5.1 HTML 通過(guò)HTML解析器轉(zhuǎn)成DOM Tree 5.2 CSS按照CSS規(guī)則和CSS解析器轉(zhuǎn)成CSSOM TREE,兩個(gè)tree結(jié)合,形成render tree(不包含HTML的具體元素和元素要畫的具體位置) 5.3 通過(guò)Layout計(jì)算出每個(gè)元素具體的寬高顏色位置,結(jié)合起來(lái),開始繪制,最后顯示在屏幕中新頁(yè)面顯示出來(lái)
看完三件事?
如果你覺得這篇內(nèi)容對(duì)你還蠻有幫助,我想邀請(qǐng)你幫我三個(gè)小忙:
點(diǎn)贊,轉(zhuǎn)發(fā),有你們的「在看」,才是我創(chuàng)造的動(dòng)力。 關(guān)注公眾號(hào) 『前端Sharing』,不定期分享原創(chuàng)知識(shí)。 同時(shí)可以期待后續(xù)文章ing??
