摘要:當(dāng)遍歷一個(gè)集合時(shí),首要優(yōu)化原則是把集合存儲(chǔ)在局部變量中,并把緩存在循環(huán)外部,然后使用局部變量訪問(wèn)這些需要多次訪問(wèn)的元素。
友情提醒:這篇我都覺(jué)得有點(diǎn)長(zhǎng)...可能會(huì)占用你10+分鐘,沒(méi)有這么多時(shí)間的你可以直接去文末看小姐結(jié)。
瀏覽器中的DOM用腳本進(jìn)行DOM操作的代價(jià)是很昂貴的,它是富web應(yīng)用中最常見(jiàn)的性能瓶頸。主要有以下三種問(wèn)題:
訪問(wèn)和修改DOM元素
修改DOM元素的樣式導(dǎo)致repaint和reflow
通過(guò)DOM事件處理與用戶進(jìn)行交互
DOM是(Document Object Model)一個(gè)與語(yǔ)言無(wú)關(guān)的、用來(lái)操作XML和HTML文檔的應(yīng)用程序接口(Application Program Interface)。 盡管DOM與語(yǔ)言無(wú)關(guān),但是在瀏覽器中的接口卻是用JavaScript來(lái)實(shí)現(xiàn)的。
一個(gè)前端小知識(shí)瀏覽器通常會(huì)把js和DOM分開(kāi)來(lái)分別獨(dú)立實(shí)現(xiàn)。
舉個(gè)栗子冷知識(shí),在IE中,js的實(shí)現(xiàn)名為JScript,位于jscript.dll文件中;DOM的實(shí)現(xiàn)則存在另一個(gè)庫(kù)中,名為mshtml.dll(Trident)。
Chrome中的DOM實(shí)現(xiàn)為webkit中的webCore,但js引擎是Google自己研發(fā)的V8。
Firefox中的js引擎是SpiderMonkey,渲染引擎(DOM)則是Gecko。
前面的小知識(shí)中說(shuō)過(guò),瀏覽器把實(shí)現(xiàn)頁(yè)面渲染的部分和解析js的部分分開(kāi)來(lái)實(shí)現(xiàn),既然是分開(kāi)的,一旦兩者需要產(chǎn)生連接,就要付出代價(jià)。
兩個(gè)例子:
小明和小紅是兩個(gè)不同學(xué)校的學(xué)生,兩個(gè)人家里經(jīng)濟(jì)條件都不太好,買不起手機(jī)(好尷尬的設(shè)定Orz...),所以只能通過(guò)寫信來(lái)互相交流,這樣的過(guò)程肯定比他倆面對(duì)面交談時(shí)所需要花費(fèi)的代價(jià)大(額外的事件、寫信的成本等)。
官方例子:把DOM和js(ECMAScript)各自想象為一座島嶼,它們之間用收費(fèi)橋進(jìn)行連接。ECMAScript每次訪問(wèn)DOM,都要途徑這座橋,并交納“過(guò)橋費(fèi)”。訪問(wèn)DOM的次數(shù)越多,費(fèi)用也就越高。
因此,推薦的做法是:盡可能的減少過(guò)橋的次數(shù),努力待在ECMAScript島上。
DOM的訪問(wèn)與修改前面說(shuō)到訪問(wèn)DOM需要交納“過(guò)橋費(fèi)”,而修改DOM元素則代價(jià)更為昂貴,因?yàn)樗鼤?huì)導(dǎo)致瀏覽器重新計(jì)算頁(yè)面的幾何變化。
來(lái)看一段代碼:
function innerHTMLLoop(){ for (var count = 0; count < 15000; count++){ document.getElementById("text").innerHTML += "dom"; } }
這段代碼,每次循環(huán)會(huì)訪問(wèn)兩次特定的元素:第一次讀取這個(gè)元素的innerHTML屬性,第二次重寫它。
看清楚了這一點(diǎn),不難得到一個(gè)效率更高的版本:
function innerHTMLLoop2(){ var content = ""; for (var count = 0; count < 15000; count++){ content += "dom"; } document.getElementById("text").innerHTML += content; }
用一個(gè)局部變量包層每次更新后的內(nèi)容,等待循環(huán)結(jié)束后,一次性的寫入頁(yè)面(盡可能的把更多的工作交給js的部分來(lái)做)。
根據(jù)統(tǒng)計(jì),在所有的瀏覽器中,修改后的版本都運(yùn)行的更快(優(yōu)化幅度最明顯的是IE8,使用后者比使用前者快273倍)。
HTML元素集合是包含了DOM節(jié)點(diǎn)引用的類數(shù)組對(duì)象。
可以用以下方法或?qū)傩缘玫揭粋€(gè)HTML元素集合:
document.getElementsByName()
document.getElementsByTagName()
document.getElementsByClassName()
document.images 頁(yè)面中所有img元素
document.links 頁(yè)面中所有a元素
document.forms 頁(yè)面中所有表單元素
document.forms[0].elements 頁(yè)面中第一個(gè)表單的所有字段
HTML元素集合處于一種“實(shí)時(shí)的狀態(tài)”,這意味著當(dāng)?shù)讓游臋n對(duì)象更新時(shí),它也會(huì)自動(dòng)更新,也就是說(shuō),HTML元素集合與底層的文檔對(duì)象之間保持的連接。正因如此,每當(dāng)你想從HTML元素集合中獲取一些信息時(shí),都會(huì)產(chǎn)生一次查詢操作,這正是低效之源。
昂貴的集合//這是一個(gè)死循環(huán) //不管你信不信,反正我是信了 var alldivs = document.getElementsByTagName("div"); for (var i = 0; i < alldivs.length; i++){ document.body.appendChild(document.createElement("div")); }
乍一看,這段代碼只是單純的把頁(yè)面中的div數(shù)量翻倍:遍歷所有的div,每次創(chuàng)建一個(gè)新的div并創(chuàng)建到添加到body中。
但事實(shí)上,這是一個(gè)死循環(huán):因?yàn)檠h(huán)的退出條件alldivs.length在每一次循環(huán)結(jié)束后都會(huì)增加,因?yàn)檫@個(gè)HTML元素集合反映的是底層文檔元素的實(shí)時(shí)狀態(tài)。
接下來(lái),我們通過(guò)這段代碼,對(duì)一個(gè)HTML元素集合做一些處理:
function toArray(coll){ for (var i = 0, a = [], len = coll.lengthl i < len; i++){ a[i] = coll[i]; } return a; } //將一個(gè)HTML元素集合拷貝到一個(gè)數(shù)組中 var coll = document.getElementsByTagName("div"); var arr = toArray(coll);
現(xiàn)在比較以下兩個(gè)函數(shù):
function loopCollection(){ for (var count = 0; count < coll.length; count++){ //processing... } } function loopCopiedArray(){ for (var count = 0; count < arr.length; count++){ //processing... } }
在IE6中,后者比前者快114倍;IE7中119倍;IE8中79倍...
所以,在相同的內(nèi)容和數(shù)量下,遍歷一個(gè)數(shù)組的速度明顯快于遍歷一個(gè)HTML元素集合。
由于在每一次迭代循環(huán)中,讀取元素集合的length屬性會(huì)引發(fā)集合進(jìn)行更新,這在所有的瀏覽器中都有明顯的性能問(wèn)題,所以你也可以這么干:
function loopCacheLengthCollection(){ var coll = document.getElementsByTagName("div"), len = coll.length; for (var count = 0; count < len; count++){ //processing... } }
這個(gè)函數(shù)和上面的loopCopiedArray()一樣快。
訪問(wèn)集合元素時(shí)使用局部變量一般來(lái)說(shuō),對(duì)于任何類型的DOM訪問(wèn),當(dāng)同一個(gè)DOM屬性或者方法需要被多次訪問(wèn)時(shí),最好使用一個(gè)局部變量緩存此成員。當(dāng)遍歷一個(gè)集合時(shí),首要優(yōu)化原則是把集合存儲(chǔ)在局部變量中,并把length緩存在循環(huán)外部,然后使用局部變量訪問(wèn)這些需要多次訪問(wèn)的元素。
一個(gè)栗子,在循環(huán)之中訪問(wèn)每個(gè)元素的三個(gè)屬性。
function collectionGlobal(){ var coll = document.getElementsByTagName("div"), len = coll.length, name = ""; for (var count = 0; count < len; count++){ name = document.getElementsByTagName("div")[count].nodeName; name = document.getElementsByTagName("div")[count].nodeType; name = document.getElementsByTagName("div")[count].tagName; //我的天不會(huì)有人真的這么寫吧... } return name; }
上面這段代碼,大家不要當(dāng)真...正常人肯定是寫不出來(lái)的...這里是為了對(duì)比一下,所以把這種最慢的情況寫給大家看。
接下來(lái),是一個(gè)稍微優(yōu)化了的版本:
function collectionLocal(){ var coll = document.getElementsByTagName("div"), len = coll.length, name = ""; for (var count = 0; count < length; count++){ name = coll[count].nodeName; name = coll[count].nodeType; name = coll[count].tagName; } return name; }
這次就看起來(lái)正常很多了,最后是這次優(yōu)化之旅的最終版本:
function collectionNodesLocal(){ var coll = document.getElementsByTagName("div"), len = coll.length, name = "", ele = null; for (var count = 0; count < len; count++){ ele = coll[count]; name = ele.nodeName; name = ele.nodeType; name = ele.tagName; } return name; }遍歷DOM 在DOM中爬行
通常你需要從某一個(gè)DOM元素開(kāi)始,操作周圍的元素,或者遞歸查找所有的子節(jié)點(diǎn)。
考慮下面兩個(gè)等價(jià)的栗子:
//1 function testNextSibling(){ var el = document.getElementById("mydiv"), ch = el.firstChild, name = ""; do { name = ch.nodeName; } while (ch = ch.nextSibling); return name; } //2 function testChildNodes(){ var el = document.getElementById("mydiv"), ch = el.childNodes, len = ch.length, //childNodes是一個(gè)元素集合,因此在循環(huán)中主席緩存length屬性以避免迭代更新 name = ""; for (var count = 0; count < len; count++){ name = ch[count].nodeName; } return name; }
在不同瀏覽器中,兩種方法的運(yùn)行時(shí)間幾乎相等。但在老版本的IE瀏覽器中,nextSibling的性能比childNodes更好一些。
元素節(jié)點(diǎn)我們知道,DOM節(jié)點(diǎn)有以下五種分類:
整個(gè)文檔是一個(gè)文檔節(jié)點(diǎn)
每個(gè)HTML元素是元素節(jié)點(diǎn)
HTML元素內(nèi)的文本是文本節(jié)點(diǎn)
每個(gè)HTML屬性是屬性節(jié)點(diǎn)
注釋是注釋節(jié)點(diǎn)
諸如childNodes、firstChild、nextSibling這些DOM屬性是不區(qū)分元素節(jié)點(diǎn)和其他類型的節(jié)點(diǎn)的,但往往我們只需要訪問(wèn)元素節(jié)點(diǎn),此時(shí)需要做一些過(guò)濾的工作。事實(shí)上,這些類型檢查的過(guò)程都是不必要的DOM操作。
許多現(xiàn)代瀏覽器提供的API只返回元素節(jié)點(diǎn),如果可用的話推薦直接只用這些API,因?yàn)樗鼈兊膱?zhí)行效率比自己在js中過(guò)濾的效率要高。
現(xiàn)代瀏覽器提供的API(被替換的API)
children(childNodes)
childElementCount (childNodes.length)
firstElementChild (firstChild)
lastElementChild (lastChild)
nextElementSibling (nextSibling)
previousElementSibling (previousSibling)
使用這些新的API,可以直接獲取到元素節(jié)點(diǎn),也正是因此,其速度也更快。
選擇器API有時(shí)候?yàn)榱说玫叫枰脑亓斜?,開(kāi)發(fā)人員不得不組合調(diào)用getElementById、getElementsByTagName,并遍歷返回的節(jié)點(diǎn),但這種繁密的過(guò)程效率低下。
最新的瀏覽器提供了一個(gè)傳遞參數(shù)為CSS選擇器的名為querySelectorAll()的原生DOM方法。這種方式自然比使用js和DOM來(lái)遍歷查找元素要快的多。
比如,
var elements = document.querySelectorAll("#menu a");
這一段代碼,返回的是一個(gè)NodeList————包含著匹配節(jié)點(diǎn)的類數(shù)組對(duì)象。與之前不同的是,這個(gè)方法不會(huì)返回HTML元素集合,因此返回的節(jié)點(diǎn)不會(huì)對(duì)應(yīng)實(shí)時(shí)的文檔結(jié)構(gòu),也避免了之前由于HTML集合引起的性能(潛在邏輯)問(wèn)題。
如果不使用querySelectorAll(),我們需要這樣寫:
var elements = document.getElementById("menu").getElementsByTagName("a");
不僅寫起來(lái)更麻煩了,更要注意的是,此時(shí)的elements是一個(gè)HTML元素集合,所以還需要把它c(diǎn)opy到數(shù)組中,才能得到一個(gè)與前者相似的靜態(tài)列表。
還有一個(gè)querySelector()方法,用來(lái)獲取第一個(gè)匹配的節(jié)點(diǎn)。
瀏覽器用來(lái)顯示頁(yè)面的所有“組件”,有:HTML標(biāo)簽、js、css、圖片——之后會(huì)解析并生成兩個(gè)內(nèi)部的數(shù)據(jù)結(jié)構(gòu):
DOM樹(shù)(表示頁(yè)面結(jié)構(gòu))
渲染樹(shù)(表示DOM節(jié)點(diǎn)應(yīng)該如何表示)
DOM樹(shù)中的每一個(gè)需要顯示的節(jié)點(diǎn)在渲染樹(shù)中至少存在一個(gè)對(duì)應(yīng)的節(jié)點(diǎn)。
渲染樹(shù)中的節(jié)點(diǎn)被稱為“幀(frames)”或“盒(boxes)”,符合css盒模型的定義,理解頁(yè)面元素為一個(gè)具有padding、margin、borders和position的盒子。
一旦渲染樹(shù)構(gòu)建完成,瀏覽器就開(kāi)始顯示頁(yè)面元素,這個(gè)過(guò)程稱為繪制(paint)。
當(dāng)DOM的變化影響了元素的幾何屬性(寬、高)——比如改變改變了邊框的寬度或者給一個(gè)段落增加一些文字導(dǎo)致其行數(shù)的增加——瀏覽器就需要重新計(jì)算元素的幾何屬性,同樣,頁(yè)面中其他元素的幾何屬性和位置也會(huì)因此受到影響。
瀏覽器會(huì)使渲染樹(shù)中收到影響的部分消失,重新構(gòu)建渲染樹(shù),這個(gè)過(guò)程稱為“重排(reflow)”。重排完成之后,瀏覽器會(huì)重新將受到影響的部分繪制到瀏覽器中,這個(gè)過(guò)程稱之為“重繪(repaint)”。
如果改變的不是元素的幾何屬性,如:改變?cè)氐谋尘邦伾?,不?huì)發(fā)生重排,只會(huì)發(fā)生一次重繪,因?yàn)樵氐牟季植](méi)有改變。
不管是重繪還是重排,都是代價(jià)昂貴的操作,它們會(huì)導(dǎo)致web應(yīng)用程序的UI反應(yīng)遲鈍,應(yīng)當(dāng)盡可能的減少這類過(guò)程的發(fā)生。
添加或刪除可見(jiàn)的DOM元素
元素位置的改變
元素尺寸的改變(padding、margin、border、height、width)
內(nèi)容改變(文本改變或圖片尺寸改變)
頁(yè)面渲染器初始化
瀏覽器窗口尺寸改變
滾動(dòng)條的出現(xiàn)(會(huì)觸發(fā)整個(gè)頁(yè)面的重排)
最小化重繪和重排 改變樣式一個(gè)栗子:
var el = document.getElementById("mydiv"); el.style.borderLeft = "1px"; el.style.borderRight = "2px"; el.style.padding = "5px";
示例中,元素的三個(gè)樣式被改變,而且每一個(gè)都會(huì)影響元素的幾何結(jié)構(gòu)。在最糟糕的情況下,這段代碼會(huì)觸發(fā)三次重排(大部分現(xiàn)代瀏覽器為此做了優(yōu)化,只會(huì)觸發(fā)一次重排)。從另一個(gè)角度看,這段代碼四次訪問(wèn)DOM,可以被優(yōu)化。
var el = document.getElementById("mydiv"); //思路:合并所有改變?nèi)缓笠淮涡蕴幚? //method_1:使用cssText屬性 el.style.cssText = "border-left: 1px; border-right: 2px; padding: 5px"; //method_2:修改類名 el.className = "anotherClass";批量修改DOM
當(dāng)你需要對(duì)DOM元素進(jìn)行一系列操作的時(shí)候,不妨按照如下步驟:
使元素脫離文檔流
對(duì)其應(yīng)用多重改變
把元素帶回文檔中
上面的這一套組合拳中,第一步和第三部分別會(huì)觸發(fā)一次重排。但是如果你忽略了這兩個(gè)步驟,那么在第二步所產(chǎn)生的任何修改都會(huì)觸發(fā)一次重排。
在此安利三種可以使DOM元素脫離文檔流的方法:
隱藏元素
使用文檔片段(document fragment)在當(dāng)前DOM之外構(gòu)建一個(gè)子樹(shù),再把它拷貝回文檔
將原始元素拷貝到一個(gè)脫離文檔的節(jié)點(diǎn)中,修改副本,完成后再替換原始元素
讓動(dòng)畫元素脫離文檔流一般情況下,重排只影響渲染樹(shù)中的一小部分,但也可能影響很大的一部分,甚至是整個(gè)渲染樹(shù)。
瀏覽器所需的重排次數(shù)越少,應(yīng)用程序的響應(yīng)速度也就越快。
想象這樣一種情況,頁(yè)面的底部有一個(gè)動(dòng)畫,會(huì)推移頁(yè)面整個(gè)余下的部分,這將是一次代價(jià)昂貴的大規(guī)模重排!用戶也勢(shì)必會(huì)感覺(jué)到頁(yè)面一卡一卡的。
因此,使用以下步驟可以避免頁(yè)面中的大部分重排:
使用絕對(duì)定位讓頁(yè)面上的動(dòng)畫元素脫離文檔流
動(dòng)畫展示階段
動(dòng)畫結(jié)束時(shí),將元素恢復(fù)定位。
IE的:hover從IE7開(kāi)始,IE允許在任何元素上使用:hover這個(gè)css選擇器。
然而,如果你有大量元素使用了:hover,你會(huì)發(fā)現(xiàn),賊喇慢!
這一個(gè)優(yōu)化手段也是在前端求職面試中的高頻題目。
當(dāng)頁(yè)面中有大量的元素,并且這些元素都需要綁定事件處理器。
每綁定一個(gè)事件處理器都是有代價(jià)的,要么加重了頁(yè)面負(fù)擔(dān),要么增加了運(yùn)行期的執(zhí)行時(shí)間。再者,事件綁定會(huì)占用處理時(shí)間,而且瀏覽器需要跟蹤每個(gè)事件處理器,這也會(huì)占用更多的內(nèi)存。還有一種情況就是,當(dāng)這些工作結(jié)束時(shí),這些事件處理器中的絕大多數(shù)都是不再需要的(并不是100%的按鈕或鏈接都會(huì)被用戶點(diǎn)擊),因此有很多工作是沒(méi)有必要的。
事件委托的原理很簡(jiǎn)單——事件逐層冒泡并能被父級(jí)元素捕獲。
使用事件委托,只需要給外層元素綁定一個(gè)處理器,就可以處理在其子元素上觸發(fā)的所有事件。
有以下幾點(diǎn)需要注意:
訪問(wèn)事件對(duì)象,判斷事件源
按需取消文檔樹(shù)中的冒泡
按需阻止默認(rèn)動(dòng)作
小結(jié)訪問(wèn)和操作DOM需要穿越連接ECMAScript和DOM兩個(gè)島嶼之間的橋梁,為了盡可能的減少“過(guò)橋費(fèi)”,有以下幾點(diǎn)需要注意:
最小化DOM訪問(wèn)次數(shù)
對(duì)于需要多次訪問(wèn)的DOM節(jié)點(diǎn),使用局部變量存儲(chǔ)其引用
如果要操作一個(gè)HTML元素集合,建議把它拷貝到一個(gè)數(shù)組中
使用速度更快的API:比如querySelectorAll
留意重排和重繪的次數(shù)
事件委托
分享前端和一些有趣的東西
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/81420.html
摘要:語(yǔ)句會(huì)使得局部變量位于作用域第二層,會(huì)使性能下降,所以應(yīng)避免使用。使用事件委托來(lái)減少事件處理器的數(shù)量。把最可能出現(xiàn)的條件放在首位。在進(jìn)行優(yōu)化時(shí),要弄清楚性能瓶頸,然后對(duì)癥優(yōu)化。新看到一篇很棒的文章前端性能優(yōu)化備忘錄如有不對(duì),歡迎指正。 春節(jié)在家,把《高性能的JavaScript》刷了一遍,受益匪淺。本著每看完一本書都要做讀書筆記的習(xí)慣,將書中的知識(shí)點(diǎn)總結(jié)一下。 由于不同瀏覽器使用的Ja...
摘要:或者說(shuō)一直以來(lái)我是缺乏開(kāi)發(fā)高性能網(wǎng)頁(yè)的意識(shí)的,但是想做一個(gè)好的前端開(kāi)發(fā)者,是需要在當(dāng)自己編寫的程序慢慢復(fù)雜以后還能繼續(xù)保持網(wǎng)頁(yè)的高性能的。 不知道有多少人和我一樣,在以前的開(kāi)發(fā)過(guò)程中很少在乎自己編寫的網(wǎng)頁(yè)的性能?;蛘哒f(shuō)一直以來(lái)我是缺乏開(kāi)發(fā)高性能網(wǎng)頁(yè)的意識(shí)的,但是想做一個(gè)好的前端開(kāi)發(fā)者,是需要在當(dāng)自己編寫的程序慢慢復(fù)雜以后還能繼續(xù)保持網(wǎng)頁(yè)的高性能的。這需要我們對(duì)JavaScript語(yǔ)句,...
摘要:性能訪問(wèn)字面量和局部變量的速度是最快的,訪問(wèn)數(shù)組和對(duì)象成員相對(duì)較慢變量標(biāo)識(shí)符解析過(guò)程搜索執(zhí)行環(huán)境的作用域鏈,查找同名標(biāo)識(shí)符。建議將全局變量存儲(chǔ)到局部變量,加快讀寫速度。優(yōu)化建議將常用的跨作用域變量存儲(chǔ)到局部變量,然后直接訪問(wèn)局部變量。 缺陷 這本書是2010年出版的,這本書談性能是有時(shí)效性的,現(xiàn)在馬上就2018年了,這幾年前端發(fā)展的速度是飛快的,書里面還有一些內(nèi)容考慮IE6、7、8的東...
摘要:最近在全力整理高性能的文檔,并重新學(xué)習(xí)一遍,放在這里方便大家查看并找到自己需要的知識(shí)點(diǎn)。 最近在全力整理《高性能JavaScript》的文檔,并重新學(xué)習(xí)一遍,放在這里方便大家查看并找到自己需要的知識(shí)點(diǎn)。 前端開(kāi)發(fā)文檔 高性能JavaScript 第1章:加載和執(zhí)行 腳本位置 阻止腳本 無(wú)阻塞的腳本 延遲的腳本 動(dòng)態(tài)腳本元素 XMLHTTPRequest腳本注入 推薦的無(wú)阻塞模式...
摘要:事件冒泡一個(gè)簡(jiǎn)單,但是坑了我無(wú)數(shù)回的知識(shí)點(diǎn)與的交互通過(guò)事件來(lái)實(shí)現(xiàn)。而瀏覽器的事件流是一個(gè)非常重要的概念。不去討論那些古老的瀏覽器有事件捕獲與事件冒泡的爭(zhēng)議,只需要知道在中規(guī)定的事件流包括了三個(gè)部分,事件捕獲階段處于目標(biāo)階段事件冒泡階段。 打算封裝一個(gè)彈窗組件,做的時(shí)候忘記了考慮事件冒泡的因素,結(jié)果被坑得不要不要的。為了解決自己的問(wèn)題,去查閱了不少資料,把事件流相關(guān)的知識(shí)都給總結(jié)一下。 ...
閱讀 1469·2021-11-24 10:24
閱讀 4554·2021-11-22 15:29
閱讀 1214·2019-08-30 15:53
閱讀 2913·2019-08-30 10:54
閱讀 2103·2019-08-29 17:26
閱讀 1443·2019-08-29 17:08
閱讀 720·2019-08-28 17:55
閱讀 1734·2019-08-26 14:01