摘要:瀏覽器渲染進程瀏覽器內核進程,內部是多線程的默認每個頁面一個進程,互不影響。事件觸發線程歸屬于瀏覽器而不是引擎,用來控制事件循環可以理解成引擎自己都忙不過來,需要瀏覽器另開線程協助。
線程和進程
進程和線程的概念可以這樣理解:
進程是一個工廠,工廠有它的獨立資源--工廠之間相互獨立--線程是工廠中的工人,多個工人協作完成任務--工廠內有一個或多個工人--工人之間共享空間
工廠有多個工人,就相當于一個進程可以有多個線程,而且線程共享進程的空間。
進程是cpu資源分配的最小單位(是能擁有資源和獨立運行的最小單位,系統會給它分配內存)
線程是cpu調試的最小單位(線程是建立在進程的基礎上的一次程序運行單位,一個進程中可以有多個線程。核心還是屬于一個進程。)
瀏覽器是多進程的,每打開一個tab頁,就相當于創建了一個獨立的瀏覽器進程。
瀏覽器包含的進程:
Browser進程:瀏覽器的主進程(負責協調,主控),只有一個,作用有:
負責瀏覽器的界面顯示,與用戶交互,如前進,后退等
負責各個頁面的管理,創建和銷毀其它進程
將Rendered進程得到的內存中的Bitmap,繪制到用戶界面上
網絡資源的管理,下載
第三方插件進程:每種類型的插件對應一個進程,僅當使用該插件時才創建。
GPU進程:最多一個,用于3D繪制等。
瀏覽器渲染進程(瀏覽器內核)(Render進程,內部是多線程的):默認每個Tab頁面一個進程,互不影響。主要作用為:
頁面渲染,腳本執行,事件處理等
在瀏覽器中打開一個網頁相當于新起了一個進程(進程內有自己的多線程)
瀏覽器多進程的優勢避免單個page crash影響整個瀏覽器
避免第三方插件crash影響整個瀏覽器
多進程充分利用多核優勢
方便使用沙盒模型隔離插件等進程,提高瀏覽器穩定性
簡單理解就是:如果瀏覽器是單進程的,某個Tab頁崩潰了,就影響了整個瀏覽器,體驗就會很差。同理如果是單進程的,插件崩潰了也會影響整個瀏覽器;
當然,內存等資源消耗也會更大,像空間換時間一樣。
對于普通的前端操作來說,最重要的渲染進程:頁面的渲染,js的執行,事件的循環等都在這個進程內執行;
瀏覽器是多進程的,瀏覽器的渲染進程是多線程的;
GUI渲染線程負責渲染瀏覽器界面,解析HTML,CSS,構建DOM樹和RenderObject樹,布局和繪制等。
當界面需要重繪或由于某種操作引發回流時,該線程就會執行。
注意,GUI渲染線程與JS引擎線程是互斥的,當JS引擎執行時GUI線程會被掛起(相當于凍結了),GUI更新會被保存在一個隊列中等到JS引擎空閑時立即被執行。
JS引擎線程也稱為JS內核,負責處理JavaScript腳本程序。(例如V8引擎)。
JS引擎線程負責解析JavaScript腳本,運行代碼。
JS引擎一直等待著任務隊列中任務的到來,然后加以處理,一個Tab頁(render進程)中無論什么時候都只有一個JS線程在運行JS程序。
同樣注意,GUI渲染線程與JS引擎線程是互斥的,所以如果JS執行的時間過長,這樣就會造成頁面的渲染不連貫,導致頁面渲染加載阻塞。
事件觸發線程歸屬于瀏覽器而不是JS引擎,用來控制事件循環(可以理解成JS引擎自己都忙不過來,需要瀏覽器另開線程協助)。
當JS引擎執行代碼塊如setTimeout時(也可來自瀏覽器內核的其它線程,如鼠標點擊,AJAX異步請求等),會將對應任務添加到事件線程中。
當對應的事件符合觸發條件被觸發時,該線程會把事件添加到待處理隊列的隊尾,等待JS引擎的處理。
注意,由于JS的單線程關系,所以這些待處理隊列中的事件都得排隊等待JS引擎處理(當JS引擎空閑時才會去執行)。
定時觸發器線程傳說中的setTimeout和setInterval所在的線程
瀏覽器定時計數器并不是由JavaScript引擎計數的,(因為JavaScript引擎是單線程的,如果處于阻塞線程狀態就會影響計時的準確)
因此通過多帶帶線程來計時并觸發定時(計時完畢后,添加到事件隊列中,等待JS引擎空閑后執行)
注意,W3C在HTML標準中規定,規定要求setTimeout中低于4ms的時間間隔算為4ms。
異步http請求線程在XMLHttpRequest在連接后是通過瀏覽器新型一個線程請求
將檢測到狀態變更時,如果設置有回調函數,異步線程就產生狀態變更事件,將這個回調再放入事件隊列中,再由JavaScript引擎執行
總結下來,渲染進程如下:
Browser主進程和瀏覽器內核(渲染進程)的通信過程打開一個瀏覽器,可以看到:任務管理器出現了2個進程(一個主進程,一個是打開Tab頁的渲染進程);
Browser主進程收到用戶請求,首先需要獲取頁面內容(如通過網絡下載資源),隨后將該任務通過RendererHost接口傳遞給Render渲染進程
Render渲染進程的Renderer接口收到消息,簡單解釋后,交給渲染線程GUI,然后開始渲染
GUI渲染線程接收請求,加載網頁并渲染網頁,這其中可能需要Browser主進程獲取資源和需要GPU進程來幫助渲染
當然可能會有JS線程操作DOM(這可能會造成回流并重繪)
最后Render渲染進程將結果傳遞給Browser主進程
Browser主進程接收到結果并將結果繪制出來
瀏覽器內核(渲染進程)中線程之間的關系GUI渲染線程與JS引擎線程互斥
由于JavaScript是可操作DOM的,如果在修改這些元素屬性同時渲染界面(即JS線程和GUI線程同時運行),那么渲染線程前后獲得的元素數據就可能不一致了。
因此,為了防止渲染出現不可預期的結果,瀏覽器就設置了互斥的關系,當JS引擎執行時GUI線程會被掛起。GUI更新則會被保存在一個隊列中等到JS引擎線程空閑時立即被執行。
JS阻塞頁面加載
從上述的互斥關系,可以推導出,JS如果執行時間過長就會阻塞頁面。
譬如,假設JS引擎正在進行巨量的計算,此時就算GUI有更新,也會被保存在隊列中,要等到JS引擎空閑后執行。然后由于巨量計算,所以JS引擎可能很久很久才能空閑,肯定就會感覺很卡。
所以,要盡量避免JS執行時間過長,這樣就會造成頁面的渲染不連貫,導致頁面渲染加載阻塞的感覺。
css加載是否會阻塞dom樹渲染
這里說的是頭部引入css的情況
首先,我們都知道:css是由多帶帶的下載線程異步下載的。
然后還有幾個現象:
css加載不會阻塞DOM樹解析(異步加載時dom照常構建)
但會阻塞render樹渲染(渲染時需要等css加載完畢,因為render樹需要css信息)
這可能也是瀏覽器的一種優化機制
因為你加載css的時候,可能會修改下面DOM節點的樣式,如果css加載不阻塞render樹渲染的話,那么當css加載完之后,render樹可能又得重新重繪或者回流了,這就造成了一些沒有必要的損耗
所以干脆把DOM樹的結構先解析完,把可以做的工作做完,然后等css加載完之后,在根據最終的樣式來渲染render樹,這種做法確實對性能好一點。
WebWorker,JS的多線程?
前文中有提到JS引擎是單線程的,而且JS執行時間過長會阻塞頁面,那么JS就真的對cpu密集型計算無能為力么?
所以,后來HTML5中支持了WebWorker。
這樣理解下:
創建Worker時,JS引擎向瀏覽器申請開一個子線程(子線程是瀏覽器開的,完全受主線程控制,而且不能操作DOM)
JS引擎線程與worker線程間通過特定的方式通信(postMessage API,需要通過序列化對象來與線程交互特定的數據)
所以,如果有非常耗時的工作,請多帶帶開一個Worker線程,這樣里面不管如何翻天覆地都不會影響JS引擎主線程,只待計算出結果后,將結果通信給主線程即可,perfect!
而且注意下,JS引擎是單線程的,這一點的本質仍然未改變,Worker可以理解是瀏覽器給JS引擎開的外掛,專門用來解決那些大量計算問題。
WebWorker與SharedWorker
既然都到了這里,就再提一下SharedWorker(避免后續將這兩個概念搞混)
WebWorker只屬于某個頁面,不會和其他頁面的Render進程(瀏覽器內核進程)共享
所以Chrome在Render進程中(每一個Tab頁就是一個render進程)創建一個新的線程來運行Worker中的JavaScript程序。
SharedWorker是瀏覽器所有頁面共享的,不能采用與Worker同樣的方式實現,因為它不隸屬于某個Render進程,可以為多個Render進程共享使用
所以Chrome瀏覽器為SharedWorker多帶帶創建一個進程來運行JavaScript程序,在瀏覽器中每個相同的JavaScript只存在一個SharedWorker進程,不管它被創建多少次。
看到這里,應該就很容易明白了,本質上就是進程和線程的區別。SharedWorker由獨立的進程管理,WebWorker只是屬于render進程下的一個線程
總結瀏覽器渲染流程瀏覽器輸入url,瀏覽器主進程接管,開一個下載線程,然后進行http請求(略去DNS查詢,IP尋址等等操作),然后等待響應,獲取內容,隨后將內容通過RendererHost接口轉交給Render進程--瀏覽器渲染流程開始
瀏覽器內核拿到內容后,渲染大概可以劃分為:
解析html建立dom要
解析css構建render樹(將css代碼解析成樹形的數據結構,然后結合dom合并成render樹)
布局render樹(Layout/reflow),負責各元素尺寸,位置的計算
繪制render樹(paint),繪制頁面像素信息
瀏覽器會將各層的信息發送給GPU,GPU會將各層合成(composite),顯示在屏幕上
渲染完畢后就是load事件了,之后就是自己的JS邏輯處理了,略去了詳細步驟。
load事件與DOMContentLoaded事件的先后
上面提到,渲染完畢后會觸發load事件,那么你能分清楚load事件與DOMContentLoaded事件的先后么?
很簡單,知道它們的定義就可以了:
當 DOMContentLoaded 事件觸發時,僅當DOM加載完成,不包括樣式表,圖片。
(譬如如果有async加載的腳本就不一定完成)
當 onload 事件觸發時,頁面上所有的DOM,樣式表,腳本,圖片都已經加載完成了。(渲染完畢了)
所以,順序是:DOMContentLoaded -> load
普通圖層和復合圖層渲染步驟就提到了composite概念;瀏覽器渲染的圖層一般包含兩大類:普通圖層以及復合圖層。
普通文檔流內可以理解為一個復合圖層(這里默認復合層,里面不管添加多少元素,其實都是在同個復合圖層中)
absolute布局(fixed也一樣),雖然可以脫離文檔流,但它仍然屬于默認復合層
可以通過硬件加速的方式,聲明一個新的復合圖層,它會多帶帶分配資源(當然也會脫離普通文檔流,這樣一來,不管這個復合圖層中怎么變化,也不會影響默認復合層里的回流重繪)
可以簡單理解下:GPU中,各個復合圖層是多帶帶繪制的,所以互不影響,這也是為什么某些場景硬件加速效果一級棒
如何變成復合圖層(硬件加速)
將元素變成一個復合圖層,就是傳說中的硬件加速技術
最常用的方式:translate3d,translatez
opacity屬性/過渡動畫(需要動畫執行的過程中才會創建合成層,動畫沒有開始或結束后元素還會回到之前的狀態)
will-chang屬性(這個比較偏僻),一般配合opacity與translate使用(而且經測試,除了上述可以引發硬件加速的屬性外,其它屬性并不會變成復合層),作用是提前告訴瀏覽器要變化,這樣瀏覽器會開始做一些優化工作(這個最好用完后就釋放)
等元素
其它,譬如以前的flash插件
absolute和硬件加速的區別
可以看到,absolute雖然可以脫離普通文檔流,但是無法脫離默認復合層。
所以,就算absolute中信息改變時不會改變普通文檔流中render樹,但是,瀏覽器最終繪制時,是整個復合層繪制的,所以absolute中信息的改變,仍然會影響整個復合層的繪制。(瀏覽器會重繪它,如果復合層中內容多,absolute帶來的繪制信息變化過大,資源消耗是非常嚴重的)
而硬件加速直接就是在另一個復合層了(另起爐灶),所以它的信息改變不會影響默認復合層(當然了,內部肯定會影響屬于自己的復合層),僅僅是引發最后的合成(輸出視圖)
復合圖層的作用
一般一個元素開啟硬件加速后會變成復合圖層,可以獨立于普通文檔流中,改動后可以避免整個頁面重繪,提升性能。
但是盡量不要大量使用復合圖層,否則由于資源消耗過度,頁面反而會變的更卡。
硬件加速時請使用index
使用硬件加速時,盡可能的使用index,防止瀏覽器默認給后續的元素創建復合層渲染
具體的原理是:
webkit CSS3中,如果這個元素添加了硬件加速,并且index層級比較低,那么在這個元素的后面其它元素(層級比這個元素高的,或者相同的,并且relective或absolute屬性相同的),會默認變為復合層渲染,如果處理不當會極大的影響性能
簡單點理解,可以認為是一個隱式合成的概念:如果a是一個復合層,而且b在a上面,那么b也會被隱式轉為一個復合圖層,這點需要特別注意
從Event Loop談JS的運行機制到此時,已經是屬于瀏覽器頁面初次渲染完畢后的事情,JS引擎的一些運行機制分析。主要是結合Event Loop來談JS代碼是如何執行的。
我們已經知道了JS引擎是單線程的,知道了JS引擎線程,事件觸發線程,定時觸發器線程。
然后還需要知道:
JS分為同步任務和異步任務
同步任務都在主線程上執行,形成一個執行棧
主線程之外,事件觸發線程管理著一個任務隊列,只要異步任務有了運行結果,就在任務隊列之中放置一個事件
一旦執行棧中的所有同步任務執行完畢(此時JS引擎空閑),系統就會讀取任務隊列,將可運行的異步任務添加到可執行棧,開始執行。
看到這里,應該就可以理解了:為什么有時候setTimeOut推入的事件不能準時執行?因為可能在它推入到事件列表時,主線程還不空閑,正在執行其它代碼,所以就必須等待,自然有誤差。
主線程在運行時會產生執行棧,棧中的代碼調用某些api時,它們會在事件隊列中添加各種事件(當滿足觸發條件后,如ajax請求完畢)。而當棧中的代碼執行完畢,就會去讀取事件隊列中的事件,去執行那些回調,如此循環。
定時器上面事件循環機制的核心是:JS引擎線程和事件觸發線程
調用setTimeout后,是由定時器線程控制等到特定時間后添加到事件隊列的,因為JS引擎是單線程的,如果處于阻塞線程狀態就會影響計時準確,因此很有必要另開一個線程用來計時。
當使用setTimout或setInterval時,需要定時器線程計時,計時完成后就會將特定的事件推入事件隊列中。
如:
setTimeout(()=>console.log("hello!),1000) //等1000毫秒計時完畢后(由定時器線程計時),將回調函數推入事件隊列中,等待主線程執行 setTimeout(()=>{ console.log("hello") },0) console.log("begin")
這段代碼的效果是最快的時間內將回調函數推入事件隊列中,等待主線程執行。
注意:
執行結果是:先begin,后hello
雖然代碼的本意是0毫秒就推入事件隊列,但是W3C在HTML標準中規定,規定要求setTimeout中低于4ms的時間間隔算為4ms
就算不等待4ms,就算假設0毫秒就推入事件隊列,也會先執行begin(因為只能可執行棧內空了后才會主動讀取事件隊列)
setInterval
用setTimeout模擬定期計時和直接用setInterval是有區別的:
每次setTimeout計時到后就會去執行,然后執行一段時間后才會繼續setTimeout,中間就多了誤差
而setInterval則是每次都精確的隔一段時間推入一個事件(但是,事件的實際執行時間不一定就準確,還有可能是這個事件還沒執行完畢,下一個事件就來了)
而且setInterval有一些比較致命的問題:
累積效應,如果setInterval代碼在setInterval再次添加到隊列之前還沒有完成執行,就會導致定時器代碼連續運行好幾次,而之間沒有間隔,就算正常間隔執行,多個setInterval的代碼執行時間可能會比預期?。ㄒ驗榇a執行需要一定時間)
比如你ios的webview,或者safari等瀏覽器中都有一人特點,在滾動的時候是不執行JS的,如果使用了setInterval,會發現在滾動結束后會執行多次由于滾動不執行JS積攢回調,如果回調執行時間過長,就會非常容易造成卡頓問題和一些不可知的錯誤(setInterval自帶的優化,如果當前事件隊列中有setInterval的回調,不會重復添加回調)
而且把瀏覽器最小化顯示等操作時,setInterval并不是不執行程序,它會把setInterval的回調函數放在隊列中,等瀏覽器窗口再次打開時,一瞬間全部執行
所以,至于這么問題,一般認為的最佳方案是:用setTimeout模擬setInterval或者特殊場合直接用requestAnimationFrame
Promise時代的microtask與macrotask在es6盛行的現在,可以看下這題:
console.log("script start"); setTimeout(()=>{ console.log("setTimeout") },0); Promise.resolve() .then(()=>console.log("promise1")) .then(()=>console.log("promise2")) console.log("script end") //執行結果: script start script end promise1 promise2 setTimeout
因為promise有一個新的概念microtask.或者可以說JS中分為兩種任務:macrotask和microtask;
理解如下:
macrotask(又叫宏任務),主代碼塊,setTimeout,setInterval等(可以看到,事件隊列中的每一個事件都是一個macrotask)
可以理解是每次執行的代碼就是一個宏任務(包括每次從事件隊列中獲取一個事件回調并放到執行棧中執行)
第一個macrotask會從頭到尾將這個任務執行完畢,不會執行其它
瀏覽器為了能夠使得JS內部macrotask與DOM任務能夠有序的執行,會在一個macrotask執行結束后,在下一個macrotask執行開始前,對頁面進行重新渲染(task->渲染->task->...)
microtask(又叫微任務),Promise,process.nextTick等。
可以理解是在當前macrotask執行結束后立即執行的任務
也就是說在當前macrotask任務后,下一個macrotask之前,在渲染之前
所以它的響應速度相比setTimeout(setTimeout是macrotask)會更快因為無需等待渲染
也就是說,在某一個macrotask執行完成后,就會將在它執行期間產生的所有microtask都執行完畢(在渲染前)
注意:在Node環境下,process.nextTick的優先級高于promise.也就是:在宏任務結束后會先執行微任務隊列中的nextTick部分,然后才會執行微任務中的promise部分。
另外,setImmediate則是規定:在下一次Event Loop(宏任務)時觸發(所以它是屬于優先級較高的宏任務),(Node.js文檔中稱,setImmediate指定的回調函數,總是排在setTimeout前面),所以setImmediate如果嵌套的話,是需要經過多個Loop才能完成的,而不會像process.nextTick一樣沒完沒了。
可以理解:
macrotask中的事件都是放在一個事件隊列中的,而這個隊列由事件觸發線程維護.
microtask中的所有微任務都是添加到微任務隊列中,等待當前macrotask執行完后執行,而這個隊列由JS引擎線程維護。
所以:
執行一個宏任務(棧中沒有就從事件隊列中獲取)
執行過程中如果遇到微任務,就將它添加到微任務的任務隊列中
宏任務執行完畢后,立即執行當前微任務隊列中的所有微任務(依次執行)
當前宏任務執行完畢,開始檢查渲染,然后GUI線程接管渲染
渲染完畢后,JS線程繼續接管,開始下一個宏任務(從事件隊列中獲?。?/p>
new Promise里的函數是直接執行的算做主程序里,而且.then后面的才會放到微任務中。
另外,請注意下Promise的polyfill與官方版本的區別:
官方版本中,是標準的microtask形式
polyfill,一般都是通過setTimeout模擬的,所以是macrotask形式
請特別注意這兩點區別
注意,有一些瀏覽器執行結果不一樣(因為它們可能把microtask當成macrotask來執行了),但是為了簡單,這里不描述一些不標準的瀏覽器下的場景(但記住,有些瀏覽器可能并不標準)
Mutation Observer可以用來實現microtask(它屬于microtask,優先級小于Promise,一般是Promise不支持時才會這樣做)
它是HTML5中的新特性,作用是:監聽一個DOM變動,當DOM對象樹發生任何變動時,Mutation Observer會得到通知
像以前的Vue源碼中就是利用它來模擬nextTick的,具體原理是,創建一個TextNode并監聽內容變化,然后要nextTick的時候去改一下這個節點的文本內容,如下:(Vue的源碼,未修改)
var counter=1 var observer=newMutationObserver(nextTickHandler) var textNode=document.createTextNode(String(counter)) observer.observe(textNode,{characterData:true}) timerFunc=()=>{ counter=(counter+1)%2 textNode.data=String(counter) }
不過,現在的Vue(2.5+)的nextTick實現移除了Mutation Observer的方式(據說是兼容性原因),取而代之的是使用MessageChannel(當然,默認情況仍然是Promise,不支持才兼容的)。
MessageChannel屬于宏任務,優先級是:setImmediate->MessageChannel->setTimeout,所以Vue(2.5+)內部的nextTick與2.4及之前的實現是不一樣的,需要注意下。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/52089.html
摘要:模塊和將下面的渲染機制,安全機制,插件機制等等隱藏起來,提供一個接口層。進行網頁的渲染進程,可能有多個。最后進程將結果由線程傳遞給進程最后,進程接收到結果并將結果繪制出來。 這是之前在簡書上面的處女作,也搬過來了,以后就一直在 segmentfault 上面寫文章了,webkit技術內幕-朱永盛是我大四買的書,很舊的一本書了,當時只看了一點點,一直沒繼續看完它,現在才看完,,,說來慚愧...
摘要:前端頁面渲染機制筆記瀏覽器基礎結構用戶界面用戶所看到及與之交互的功能組件,如地址欄返回前進按鈕瀏覽器引擎用戶界面和呈現引擎之間傳遞指令渲染引擎呈現引擎負責解析用戶請求的內容網絡負責處理網絡相關的事物后端負責繪制提示框等瀏覽器組件,底層使用 前端頁面渲染機制-筆記 瀏覽器基礎結構 1.用戶界面(user interface):用戶所看到及與之交互的功能組件,如地址欄、返回、前進按鈕 2...
摘要:前端頁面渲染機制筆記瀏覽器基礎結構用戶界面用戶所看到及與之交互的功能組件,如地址欄返回前進按鈕瀏覽器引擎用戶界面和呈現引擎之間傳遞指令渲染引擎呈現引擎負責解析用戶請求的內容網絡負責處理網絡相關的事物后端負責繪制提示框等瀏覽器組件,底層使用 前端頁面渲染機制-筆記 瀏覽器基礎結構 1.用戶界面(user interface):用戶所看到及與之交互的功能組件,如地址欄、返回、前進按鈕 2...
摘要:事件循環機制首先區分進程和線程進程是資源分配的最小單位系統會給它分配內存不同的進程之間是可以同學的,如管道命名管道消息隊列一個進程里有單個或多個線程瀏覽器是多進程的,因為系統給它的進程分配了資源內存打開會有一個主進程,每打開一個頁就有一個獨 JS JavaScript事件循環機制 首先區分進程和線程 進程是cpu資源分配的最小單位(系統會給它分配內存) 不同的進程之間是可以同學的,如...
摘要:如果看完本文后,還對進程線程傻傻分不清,不清楚瀏覽器多進程瀏覽器內核多線程單線程運行機制的區別。因此準備梳理這塊知識點,結合已有的認知,基于網上的大量參考資料,從瀏覽器多進程到單線程,將引擎的運行機制系統的梳理一遍。 前言 見解有限,如有描述不當之處,請幫忙及時指出,如有錯誤,會及時修正。 ----------超長文+多圖預警,需要花費不少時間。---------- 如果看完本文后,還...
摘要:渲染機制瀏覽器渲染機制什么是及作用告訴瀏覽器文件是什么文檔類型,瀏覽器根據它來判斷用什么引擎來解析渲染文件。觸發改動改動例當添加時,最好一次添加,避免多次。 渲染機制 瀏覽器 1. 渲染機制 什么是 DOCTYPE 及作用 DTD 告訴瀏覽器文件是什么文檔類型,瀏覽器根據它來判斷用什么引擎來解析渲染文件。DOCTYPE 用來聲明文檔類型和 DTD 規范。 瀏覽器是怎么渲染過程show...
閱讀 1342·2019-08-30 15:55
閱讀 1644·2019-08-26 10:21
閱讀 3436·2019-08-23 18:28
閱讀 3374·2019-08-23 15:38
閱讀 743·2019-08-23 15:24
閱讀 2134·2019-08-23 13:59
閱讀 773·2019-08-23 11:31
閱讀 2869·2019-08-23 10:53