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 国产精品女上位好爽在线短片,亚洲一区二区三区免费看,一级毛片在线播放

          整合營(yíng)銷服務(wù)商

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

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

          CSS里的整潔架構(gòu)

          CSS里的整潔架構(gòu)

          歷數(shù)技術(shù)進(jìn)步的代價(jià)時(shí),弗洛伊德遵循的路線使人感到壓抑。他同意塔姆斯的評(píng)論:我們的發(fā)明只不過(guò)是手段的改進(jìn),目的卻未見改善。

          ——尼爾波斯曼《技術(shù)壟斷》

          雖然開發(fā)工具早已經(jīng)從 preprocessor 進(jìn)化到了 styled component 甚至是 functional css,但在我看來(lái)新的工具并沒(méi)有讓我們的樣式代碼寫的更好,只是更快——也可能會(huì)讓代碼壞的更快。工具的繁榮并沒(méi)有讓那些導(dǎo)致代碼難以維護(hù)的根本問(wèn)題煙消云散,而是更易讓我們對(duì)其視而不見。這篇文章旨在回答一個(gè)問(wèn)題:為什么樣式代碼難以寫對(duì),它的陷阱究竟在哪里?

          如果一本正經(jīng)的聊架構(gòu),套路多半是按照某些重要的特征依次展開講解。但這些所謂的重要特征其實(shí)在編程領(lǐng)域中是放之四海而皆準(zhǔn)的,例如“擴(kuò)展性”、“可復(fù)用”、“可維護(hù)性”等等,按這種思路聊,空談大于應(yīng)用。所以我們不如通過(guò)解決某個(gè)具體的樣式問(wèn)題,來(lái)審視樣式代碼應(yīng)該如何編寫和組織

          下圖是一個(gè)非常簡(jiǎn)單的 popup 組件,我們會(huì)以它的樣式開發(fā)過(guò)程串起整篇的內(nèi)容。

          我們首先以一種簡(jiǎn)單粗暴的方式來(lái)實(shí)現(xiàn)它,直覺(jué)上看,實(shí)現(xiàn)這個(gè) popup 只需要三個(gè)元素即可:div 是最外面的容器,h1 用于包裹 "Success" 文案,button 用來(lái)實(shí)現(xiàn)按鈕

          <div class="popup">
           <div>Success</div>
           <button>OK</button>
          </div>

          我不會(huì)完整的寫出它的完整樣式,只大概列出其中一些關(guān)鍵屬性

          .popup {
           display: flex;
           justify-content: space-around;
          
           padding: 20px;
           width: 200px;
           height: 200px;
          
           div {
             margin: 10px;
             font-size: 24px;
          }
          
           button {
             background: orange;
             font-size: 16px;
             margin: 10px;
          }
          }

          第一版實(shí)現(xiàn)即完成了。目前看來(lái)并沒(méi)有什么不妥。

          問(wèn)題不在于實(shí)現(xiàn)而是在于維護(hù)。接下來(lái)我就以一些常見的實(shí)際需求變更來(lái)看看上面的代碼存在怎樣的問(wèn)題。


          對(duì) DOM 元素的依賴

          假設(shè)現(xiàn)在需要在“Success”下方新增一個(gè)元素用于展示成功的具體信息

          想當(dāng)然的我們需要新增一個(gè) div 標(biāo)簽。但如果這樣的話上面樣式中的 .popup div 樣式就會(huì)同時(shí)對(duì)這兩個(gè) div 產(chǎn)生同樣的效果,這并不是我們希望的,很明顯這兩個(gè)元素的樣式是不同的。OK,如果你堅(jiān)持使用標(biāo)簽作為選擇器的話,你可以使用偽類選擇器 nth-child 來(lái)區(qū)分樣式:

          .popup {
           div:nth-child(1) {
             margin: 10px;
             font-size: 24px;
          }
          
           div:nth-child(2) {
             margin: 5px;
             font-size: 16px;
          }

          但如果某一天你認(rèn)為"Success"應(yīng)該使用 h1 而非 div 封裝更為恰當(dāng)?shù)脑挘敲葱薷牡某杀緞t是:

          • 將 div 改為 h1,
          • 將 div:nth-child(1) 樣式改為 h1 所屬,
          • 將 div:nth-child(2) 還原為 div 樣式

          但如果你一開始就能給 button 和 div 一個(gè)確切的 class 名稱,那么當(dāng)你修改 DOM 元素時(shí)也僅僅需要修改 DOM 元素,而無(wú)需修改樣式文件了

          上面舉得這個(gè)例子是水平拓展的情況,也就是說(shuō)我在某一元素的同一級(jí)新增一個(gè)元素。縱向拓展也會(huì)出現(xiàn)同樣的問(wèn)題,你可以完全想象的出類似于這樣的選擇器:

          .popup div > div > h1 > span {
          
          }
          
          .popup {
           div {
             div {
               span {}
            }
          }
          }

          無(wú)論是上面代碼中的哪一種情況,樣式是否生效都極度依賴于 DOM 結(jié)構(gòu)。在一連串的 DOM 標(biāo)簽的層級(jí)關(guān)系中,哪怕只有一個(gè)元素出現(xiàn)了問(wèn)題(可能是元素標(biāo)簽類型發(fā)生了修改,還有可能是在它之上新增了一個(gè)元素)都會(huì)導(dǎo)致樣式大面積失效。同時(shí)這樣的做法也會(huì)讓你復(fù)用樣式難上加難,如果你希望復(fù)用 .popup div > div > h1 > 的樣式,你不得不將 DOM 結(jié)構(gòu)也拷貝到想要復(fù)用的地方。

          所以這里我們至少能得出一個(gè)結(jié)論:CSS 不應(yīng)該過(guò)分的依賴 HTML 結(jié)構(gòu)

          而之所以加上“過(guò)分”二字,是因?yàn)闃邮酵耆珶o(wú)法脫離結(jié)構(gòu)獨(dú)立存在,例如 .popup .title .icon 這樣的的依賴關(guān)系背后就暗示了 HTML 結(jié)構(gòu)的大致輪廓。

          所以我們可以繼續(xù)將上面的原則稍作更正:CSS 應(yīng)該擁有對(duì) HTML 的最小知識(shí)。理想情況下一個(gè) .button 樣式無(wú)論應(yīng)用在任何元素上看上去都應(yīng)該像同一個(gè)立體的可點(diǎn)擊按鈕。


          父元素依賴

          上一節(jié)中我們開發(fā)完畢的組件通常會(huì)在頁(yè)面上被多處引用,但總存在個(gè)別場(chǎng)景需要你對(duì)組件稍作修改才得以適配。假設(shè)有一個(gè)需求是希望把這個(gè) popup 應(yīng)用在他們的移動(dòng)端網(wǎng)站上,但為了適配動(dòng)設(shè)備,某些元素的有關(guān)尺寸例如長(zhǎng)寬內(nèi)外邊距等都要縮小,你會(huì)怎么實(shí)現(xiàn)?

          我見過(guò)的 90% 的解決方案都是以添加父元素的依賴進(jìn)行實(shí)現(xiàn),也就是判斷該組件是否在某個(gè)特定的 class 下,如果是的話則修改樣式:

          body.mobile {
           .popup {
             padding: 10px;
             width: 100px;
             height: 100px;
          }
          }

          但如果此時(shí)你需要給平板設(shè)備添加一個(gè)新的樣式,我猜你可能會(huì)再添加一個(gè) body.tablet { .popup {} } 代碼。又如果移動(dòng)端網(wǎng)站有兩處需要使用 popup ,那么你的代碼很最終會(huì)變成這樣:

          body.mobile {
           .sidebar {
             .popup
          }
          
           .content {
             .popup
          }
          }

          這樣的代碼依然是難以復(fù)用的。如果某位開發(fā)者看到了移動(dòng)端網(wǎng)站 popup 打開的樣式很喜歡,然后想移植到另一處,那么單純引入 popup 組件是不夠的,他還需要找到真正的生效的代碼,將樣式和 DOM 層級(jí)都復(fù)制粘貼過(guò)去。

          在一個(gè)組件自身已經(jīng)擁有樣式的情況下,過(guò)分的依賴父組件間接的調(diào)整樣式,是一種 case by case 的編碼行為,本質(zhì)上這架空了 popup 自帶樣式。假設(shè) popup 自帶 box-shadow 的樣式屬性,但在有的用例里,box-shadow 可能會(huì)被加重,而在有的用例里,box-shadow 又可能會(huì)消失,那么它自帶的 box-shadow 根部本就沒(méi)有意義了,因?yàn)樗肋h(yuǎn)不會(huì)生效。

          架空違背了“最小驚訝原則”,給后續(xù)的維護(hù)者帶來(lái)了“驚喜”。如果此時(shí) popup 的設(shè)計(jì)稿發(fā)生了修改,陰影需要減少,則修改它自身的樣式是不會(huì)生效的,或者說(shuō)無(wú)法在每一處生效。而至于還有哪些地方無(wú)法生效,為什么它們無(wú)法生效,維護(hù)者并不知道,他同樣需要 case by case 的去查看代碼。這么做無(wú)疑增加了修改代碼的成本.

          解決這個(gè)問(wèn)題并不像解決 DOM 依賴問(wèn)題那么簡(jiǎn)單,需要我們多管齊下。


          樣式角色的分離

          想提高代碼的可維護(hù)性,分離關(guān)注點(diǎn)永遠(yuǎn)是屢試不爽的手段。縱觀現(xiàn)有的各類組織樣式的方法論,比如 SMASS 或者是 ITCSS,對(duì)樣式進(jìn)行適當(dāng)?shù)慕巧珓澐质撬鼈兊暮诵乃枷胫弧?/span>

          我們以一個(gè)完整的 popup 樣式為例:

          .popup {
           width: 100px;
           height: 30px;
          
           background: blue;
           color: white;
           border: 1px solid gary;
          
           display: flex;
           justify-content: center;
          }

          在這一組樣式中,我們看到

          • 有與布局相關(guān)的 width, height
          • 與視覺(jué)樣式相關(guān)的 background, color
          • 自身的布局樣式 flex
          • 其他樣式比如 border

          根據(jù)這些特點(diǎn)和常見的規(guī)范,可以考慮從下面幾個(gè)維度對(duì)樣式進(jìn)行分離:

          • 布局(Layout)和尺寸(size): 一個(gè)組件在不同的父組件下?lián)碛胁煌某叽缡窃僬2贿^(guò)的事情。與其定義一個(gè)被架空隨時(shí)會(huì)被覆蓋的尺寸,不如將布局的工作交由專職的組件處理。反過(guò)來(lái)說(shuō),該組件自生并不擁有尺寸,例如它可以選擇總是以 100% 的寬和高充滿包裹它的容器。

          從表面上看,這種行為只是將樣式(尺寸)從一個(gè)組件轉(zhuǎn)移到另一個(gè)組件(容器)上,但卻從根本上解決了我們上面提到的父元素依賴的困惱。任何想使用 popup 的其他組件,不用再設(shè)法關(guān)心 popup 組件的尺寸是如何實(shí)現(xiàn)的,它只需要關(guān)自己。

          進(jìn)一步從深層次上說(shuō),它消滅了依賴。你可能沒(méi)有注意到,flex 布局的樣式配置遵循的就是這種模式:當(dāng)你想讓你孩子元素按照某種規(guī)則布局的話,你只需要修改父元素和 flex 布局樣式屬性即可,完全不用再在孩子元素的樣式上做出修改。

          我個(gè)人認(rèn)為另一個(gè)反模式的例子是 text-overflow: ellipsis 屬性,單一的該樣式屬性是不足以自動(dòng)省略容器內(nèi)的文字,容器還需要滿足 1) 寬度必須是 px 像素為單位 2) 元素必須擁有 overflow:hidden 和 --tt-darkmode-color: #A3A3A3;">而至于布局功能元素是與父元素為同一元素,還是獨(dú)立元素,我傾向于后者,畢竟幾個(gè) markup 代碼并不會(huì)給我們添加多少負(fù)擔(dān),但清晰的職責(zé)劃分卻能給我們將來(lái)的維護(hù)帶來(lái)不少便利。

          在這個(gè)前提下任何給 popup 添加的布局樣式實(shí)際上都意味這你新增了隱性依賴,因?yàn)槟銓?shí)際上是在暗示:它在這個(gè)父容器下的這個(gè) margin 值看上去剛好。

          • 修飾類(Modifier): SOLID 原則中的 open-closed 告訴我們要對(duì)修改關(guān)閉,對(duì)拓展開發(fā),這對(duì)樣式代碼也同樣成立。

          通常我們不會(huì)只需要單一樣式的按鈕,可能還需要帶有紅底白字的錯(cuò)誤樣式的按鈕,還需要黃底白字的警告樣式按鈕。這種用例常見的解決方案不是新建 N 個(gè)不同的按鈕樣式,比如 primary-button, error-button(這樣務(wù)必會(huì)出現(xiàn)很多公共的 button 代碼),而是在一個(gè) button 樣式的基礎(chǔ)上,通過(guò)提供樣式的“修飾”類來(lái)達(dá)到最終的目的。例如基礎(chǔ)款的按鈕 class 名稱為 button, 如果你想讓它變得帶有警告樣式的話,只需要同時(shí)使用 error 的 class 名稱即可。

          <div className="button error"></div>

          從本質(zhì)上說(shuō)這也是一種關(guān)注點(diǎn)的分離,只不過(guò)從這個(gè)角度上看它關(guān)心的是“變”與“不變”。我們將“變量”統(tǒng)統(tǒng)轉(zhuǎn)移到“修飾”類中。

          但這種方案在實(shí)現(xiàn)時(shí)會(huì)遇到不少問(wèn)題,首先是修飾類的設(shè)計(jì),例如當(dāng)我在定義例如 error, primary, warning 的修飾類時(shí),究竟哪些樣式屬性是我可以覆蓋的哪些是不可以,這必須有事前約定。否則某人在寫 error 樣式時(shí),可能會(huì)無(wú)腦的覆蓋原 button 上的樣式直到看上去滿意為止。它依賴于抽象能力,但糟糕的抽象比不抽象還要難以維護(hù)。

          • 模塊化:借著組件模塊化這股東風(fēng),樣式模塊化似乎是水到渠成的事情。但如果眼光放長(zhǎng)遠(yuǎn)一些,模塊化并不僅限于將樣式趕到某個(gè)角落封裝起來(lái)集中管理。從上面的例子也不難看到,借用樣式中父元素依賴的特性可以輕松打破這種封裝。

          組件并非是封裝樣式的唯一單位,在一個(gè)網(wǎng)站中,還可能存在諸如 base、reset 這種全局或者說(shuō)切面性質(zhì)的樣式屬性。我理想的模塊化樣式應(yīng)該能夠輕松達(dá)到以下的目的:

          • 控制樣式影響的方向性:例如全局樣式能夠影響組件,但組件不能夠影響全局;
          • 樣式模塊間的隔離和污染:雖然 A 組件是 B 組件的子元素,但 B 組件的樣式不會(huì)影響 A 的樣式。

          詮釋這兩點(diǎn)最好的例子是在進(jìn)行響應(yīng)式開發(fā)時(shí),業(yè)內(nèi)通用的對(duì)字體大小適配的解決方案。例如下面這個(gè)組件的 html 結(jié)構(gòu):

          <div class="ancestor">
           <div class="parent">
            parent
             <div class="child">
              hello
             </div>    
           </div>
          </div>

          在樣式中我們會(huì)設(shè)定:

          • ancestor 組件字體相對(duì)于根元素 html 變化,所以使用 rem 單位;
          • parent 和 child 的字體單位需要相對(duì)于該組件(也就是ancestor)的基準(zhǔn)字體進(jìn)行變化,所以使用 em 單位。
          .ancestor {
           font-size: 1rem;
          }
          .parent {
           font-size: 1.5em;
          }
          .child {
           font-size: 2em;
          }

          這樣當(dāng)我們需要根據(jù)設(shè)備調(diào)整字體大小時(shí),只需要調(diào)整根元素 html 字體大小,那么頁(yè)面上其他元素就會(huì)自我調(diào)節(jié)了。而如果我們只想調(diào)整局部樣式時(shí),我們只需要調(diào)整 .ancestor 的字體大小即可,不會(huì)影響到其他元素。

          你閱讀到這里不難看出來(lái),樣式難寫對(duì)的問(wèn)題在于它太容易影響別的組件,也太容易受別的組件所影響了。絕大部分人遇到的問(wèn)題是:

          • 我以為我修改的是 A 組件的樣式,但無(wú)形中卻影響到了 B 組件;
          • 組件 A 同時(shí)受好幾組樣式的影響,無(wú)論單獨(dú)修改誰(shuí)都無(wú)法達(dá)到最終的效果。

          解決這個(gè)問(wèn)題的辦法早就有了,那就是樣式的隔離。比如在 Angular 中,它是靠給元素添加隨機(jī)屬性并且給樣式附帶上屬性選擇器來(lái)實(shí)現(xiàn)的,例如你同時(shí)創(chuàng)建了 page-title 組件和 section-title 組件,它們都擁有 h1 元素的樣式,但是在編譯之后你看到的 css 分別是:

          h1[_ngcontent-kkb-c18] {
             background: yellow;
          }
          
          h1[_ngcontent-kkb-c19] {
             background: blue;
          }

          這樣所有的 h1 元素樣式都不會(huì)被互相影響。


          實(shí)現(xiàn)里的問(wèn)題

          Pre-Processer

          無(wú)論你主觀上多么想避免以上的所有問(wèn)題,給樣式一個(gè)好的整潔架構(gòu)。在實(shí)現(xiàn)的過(guò)程中,我們依然會(huì)不小心掉入工具的陷阱中。

          再一次回到我們上面提到的 popup 樣式:

          .popup {
           width: 100px;
           height: 30px;
          
           background: blue;
           color: white;
          }

          假如你發(fā)現(xiàn) { background: blue; color: white; } 作為常見樣式出現(xiàn)頻繁,希望對(duì)它進(jìn)行復(fù)用,在使用 Sass 編程的前提下很明顯此時(shí)你有兩個(gè)選擇:@mixin 或者 @extend。

          如果采用 mixin,代碼如下:

          @mixin common {  
           background: blue;
           color: white;
          }
          
          .popup {  
           @include common;  
          }
          而如果采用 extend:
          .common {  
           background: blue;
           color: white;
          }
          
          .popup {  
           @extend .common;  
          }

          第一個(gè)問(wèn)題是,無(wú)論你選擇哪種模式,你都很難說(shuō)開發(fā)者是有意在依賴抽象還是在依賴實(shí)現(xiàn)。我們可以把 @mixin common 和 .common 解讀為對(duì)一種抽象的封裝,但很有可能后續(xù)的消費(fèi)者只是想復(fù)用 background 和 color 而已。一旦如此,common 模塊就變得難以修改,因?yàn)閷?duì)任意一個(gè)屬性的修改都會(huì)影響到未知的若干個(gè)模塊。

          在 SASS 中雖然我們可以給類名添加參數(shù),把它當(dāng)作參數(shù)相互傳遞,但它與我們實(shí)際編程中的變量和函數(shù)并不相同:JavaScript 中的函數(shù)我們往往只關(guān)心它的輸入與輸出,只是定義函數(shù)并不會(huì)對(duì)程序的結(jié)果造成影響。而當(dāng)你在定義樣式類的那個(gè)時(shí)刻就已經(jīng)可能對(duì)頁(yè)面產(chǎn)生了影響,并且其中的每一條屬性都會(huì)產(chǎn)生影響。

          如果你聽說(shuō)過(guò)“組合優(yōu)于繼承”,我相信會(huì)對(duì)這一點(diǎn)有更深刻的體驗(yàn)。你可以回想繼承體系中存在的副作用,例如繼承打破了對(duì)超類的封裝,子類不能減少超類的接口等等,在 SASS 的這類復(fù)用關(guān)系中都能找到相似的影子。
          extend 相比 mixin 更危險(xiǎn)的地方在于,它破壞了我們一如既往組織模塊的方式。

          例如目前已有一個(gè) page 頁(yè)面,其中擁有一組 page-title 的樣式:

          .page {
           .page-title {
               .icon {
                   width: 10px;
              }
          
               .label {
                   width: 100px;
              }
          }    
          }

          現(xiàn)在 card-title 想通過(guò) extend 來(lái)復(fù)用它:

          .card-title {
             @extend .page-title;
          }

          那么編譯之后的結(jié)果看上去會(huì)非常奇怪:

          .page .page-title .icon, .page .card-title .icon {
           width: 10px;
          }
          .page .page-title .label, .page .card-title .label {
           width: 100px;
          }

          哪怕你沒(méi)有聽說(shuō)過(guò) BEM,你的編程經(jīng)驗(yàn)也應(yīng)該會(huì)告訴你 page 和 card 的樣式應(yīng)屬于不同的模塊。但事實(shí)上編譯后的結(jié)果更像是優(yōu)先考慮復(fù)用,從橫切面強(qiáng)行把二者耦合在一起。

          而如果你嘗試將公共的 title 樣式抽象為 mixin,再在 page-title 和 card-title 中進(jìn)行復(fù)用:

          @mixin title {
             .icon {
                 width: 10px;
            }
          
             .label {
                 width: 100px;
            }
          }
          
          .page {
             .page-title {
                 @include title        
            }
          }
          
          .card-title {
             @include title
          }

          編譯的結(jié)果如下:

          .page .page-title .icon {
           width: 10px;
          }
          .page .page-title .label {
           width: 100px;
          }
          
          .card-title .icon {
           width: 10px;
          }
          .card-title .label {
           width: 100px;
          }

          很明顯 page 和 card 的樣式更涇渭分明。

          An Necessary Evil

          如果你問(wèn)我我是否會(huì)遵守上面自己寫的每一條原則,我的答案是否定的。在實(shí)際開發(fā)中我傾向用便捷性換取可維護(hù)性。

          在編程領(lǐng)域里面唯一不變的就是變化本身,無(wú)論在敲鍵盤之前你面向?qū)ο笤O(shè)計(jì)的多么準(zhǔn)確,對(duì)組件拆分的多么恰當(dāng),任何業(yè)務(wù)上的變化都有可能讓你所有的設(shè)計(jì)推倒重來(lái)。所以為了保證代碼能夠精確反饋業(yè)務(wù)知識(shí)的合理性,我們需要時(shí)常對(duì)代碼設(shè)計(jì)重新設(shè)計(jì)。

          你可以想象整個(gè)過(guò)程需要重新審視架構(gòu),從頭閱讀理解代碼,修改完畢后驗(yàn)證。執(zhí)行這一系列步驟需要不小的成本,還不包括其中的試錯(cuò),以及因?yàn)橹貥?gòu)而浪費(fèi)的添加新功能的機(jī)會(huì)。更重要的是成本擺在那里,但收益卻并不明顯。

          如果你的樣式代碼是基于 design system 之上的,那么你的改動(dòng)成本會(huì)更高。因?yàn)槟愀豢赡芤詡€(gè)人的視角隨心所欲的改動(dòng)代碼了,而是要自上而下的用整個(gè)產(chǎn)品的設(shè)計(jì)語(yǔ)言來(lái)衡量修改的合理性。

          另一個(gè)更實(shí)際的問(wèn)題是,代碼從來(lái)不是依靠個(gè)人來(lái)維護(hù)。當(dāng)這一套理論在團(tuán)隊(duì)內(nèi)并沒(méi)有達(dá)成共識(shí),或者是大家只在理論層面了解過(guò)而實(shí)操時(shí)并不在意時(shí),少數(shù)人的精心付出終究會(huì)化為泡影。代碼在理想狀態(tài)下應(yīng)該最大成度上摒棄“人”這個(gè)因素成為流水線上工業(yè)化的產(chǎn)品。所以當(dāng)我發(fā)現(xiàn)某個(gè)框架只有要求人們閱讀完數(shù)十頁(yè)最佳實(shí)踐有關(guān)的文檔才能寫出符合官方標(biāo)準(zhǔn)的好代碼時(shí),那么現(xiàn)實(shí)工作中好代碼出現(xiàn)的概率基本為0——在規(guī)范輸出代碼上,一則有效的 eslint 規(guī)則比十頁(yè)文檔都要強(qiáng)。而在本篇中敘述的各種原則屬于后者。

          然而 css 代碼被寫的亂七八糟又會(huì)怎樣呢?產(chǎn)品壞了是肯定的,但相比其他 bug 有意思的事情是:

          • 相比腳本而言發(fā)現(xiàn)樣式問(wèn)題的概率高,所見即所得;
          • 帶來(lái)的破壞相比腳本功能小,問(wèn)題下產(chǎn)品依然可用;
          • 修復(fù)問(wèn)題成本低,甚至不需要完整閱讀源碼即可有針對(duì)性的快速修復(fù)。

          基于上面的三點(diǎn),同時(shí)考慮到當(dāng)下技術(shù)棧繁雜學(xué)習(xí)成本高,腳本開發(fā)工作量大,交付壓力重,樣式架構(gòu)的正確性想當(dāng)然是被犧牲掉的那一個(gè)。

          最后重申我不鼓勵(lì)這樣的行為,這只是屈服于現(xiàn)實(shí)壓力下其中的一種可能性而已。如果你所在的項(xiàng)目資源充足,以及大家有決心把事情做對(duì),那也未嘗不可。

          Functional CSS

          在我看來(lái)還有一類實(shí)踐是游離于以上體系之外的,比如 tailwind 和 tachyons 。之所以將它們稱之為“函數(shù)式”樣式,是因?yàn)樵谶@些框架不提供組件化、語(yǔ)義化的樣式,比如 .card, .btn,而提供的是“工具類(utility class)”,比如 .overflow-auto,.box-content,它們 類似于函數(shù)式編程中沒(méi)有副作用的純函數(shù)。當(dāng)你需要給你元素添加樣式時(shí),只需要給這個(gè)元素添加對(duì)應(yīng)的 class 名稱即可:

          <div class="overflow-auto box-content float-left"></div>

          之所以說(shuō)這種實(shí)踐游離于以上體系之外,是因?yàn)樗蚱屏宋疑厦嫠f(shuō)的前提:樣式和 DOM 結(jié)構(gòu)之間存在依賴關(guān)系。在這種編程模式下,因?yàn)椴辉俅嬖凇凹?jí)聯(lián)”關(guān)系,所以每個(gè)元素的樣式都是獨(dú)立的,互不影響。

          如此看來(lái)這種模式簡(jiǎn)直就是天堂,本文里提及的所有問(wèn)題都可以避免了:父元素依賴、角色耦合、預(yù)處理器里糾結(jié)的復(fù)用。

          但仔細(xì)想想,這種方式是不是很 inline style 類似?用 inline style 也能解決我們所說(shuō)的上述所有問(wèn)題。我們是不是又回到了起點(diǎn)?

          除了上面的問(wèn)題外,我不再給出進(jìn)一步推薦或者反對(duì)意見的原因在于,一方面這種實(shí)踐存在很大的爭(zhēng)議。另一方面我缺乏使用這類框架的經(jīng)驗(yàn)。這里經(jīng)驗(yàn)的判斷標(biāo)準(zhǔn)不是“是否用過(guò)”,而是“是否長(zhǎng)期投入到多人協(xié)作的大型項(xiàng)目中”——“長(zhǎng)期”、“多人”、“大型”這幾個(gè)關(guān)鍵詞很重要。因?yàn)槲覀冊(cè)谧黾夹g(shù)選型的時(shí)候,更多要考慮和現(xiàn)有項(xiàng)目的契合度、團(tuán)隊(duì)的適應(yīng)成本,以及評(píng)估長(zhǎng)遠(yuǎn)來(lái)看它能給我們帶來(lái)巨大的好處是否能抵消替換它的成本。這些經(jīng)驗(yàn)是我缺乏的。



          文/Thoughtworks 李光毅

          更多精彩洞見,請(qǐng)關(guān)注公眾號(hào)Thoughtworks洞見

          家好,我是皮皮。

          前言

          對(duì)于前端來(lái)說(shuō),HTML 都是最基礎(chǔ)的內(nèi)容。

          今天,我們來(lái)了解一下 HTML 和網(wǎng)頁(yè)有什么關(guān)系,以及與 DOM 有什么不同。通過(guò)本講內(nèi)容,你將掌握瀏覽器是怎么處理 HTML 內(nèi)容的,以及在這個(gè)過(guò)程中我們可以進(jìn)行怎樣的處理來(lái)提升網(wǎng)頁(yè)的性能,從而提升用戶的體驗(yàn)。


          一、瀏覽器頁(yè)面加載過(guò)程

          不知你是否有過(guò)這樣的體驗(yàn):當(dāng)打開某個(gè)瀏覽器的時(shí)候,發(fā)現(xiàn)一直在轉(zhuǎn)圈,或者等了好長(zhǎng)時(shí)間才打開頁(yè)面……

          此時(shí)的你,會(huì)選擇關(guān)掉頁(yè)面還是耐心等待呢?

          這一現(xiàn)象,除了網(wǎng)絡(luò)不穩(wěn)定、網(wǎng)速過(guò)慢等原因,大多數(shù)都是由于頁(yè)面設(shè)計(jì)不合理導(dǎo)致加載時(shí)間過(guò)長(zhǎng)導(dǎo)致的。

          我們都知道,頁(yè)面是用 HTML/CSS/JavaScript 來(lái)編寫的。

          • HTML 的職責(zé)在于告知瀏覽器如何組織頁(yè)面,以及搭建頁(yè)面的基本結(jié)構(gòu);
          • CSS 用來(lái)裝飾 HTML,讓我們的頁(yè)面更好看;
          • JavaScript 則可以豐富頁(yè)面功能,使靜態(tài)頁(yè)面動(dòng)起來(lái)。

          HTML由一系列的元素組成,通常稱為HTML元素。HTML 元素通常被用來(lái)定義一個(gè)網(wǎng)頁(yè)結(jié)構(gòu),基本上所有網(wǎng)頁(yè)都是這樣的 HTML 結(jié)構(gòu):

          <html>
              <head></head>
              <body></body>
          </html>

          其中:

          • html元素是頁(yè)面的根元素,它描述完整的網(wǎng)頁(yè);
          • head元素包含了我們想包含在 HTML 頁(yè)面中,但不希望顯示在網(wǎng)頁(yè)里的內(nèi)容;
          • body元素包含了我們?cè)L問(wèn)頁(yè)面時(shí)所有顯示在頁(yè)面上的內(nèi)容,是用戶最終能看到的內(nèi)容;


          HTML 中的元素特別多,其中還包括可用于 Web Components 的自定義元素。

          前面我們提到頁(yè)面 HTML 結(jié)構(gòu)不合理可能會(huì)導(dǎo)致頁(yè)面響應(yīng)慢,這個(gè)過(guò)程很多時(shí)候體現(xiàn)在<script><style>元素的設(shè)計(jì)上,它們會(huì)影響頁(yè)面加載過(guò)程中對(duì) Javascript 和 CSS 代碼的處理。

          因此,如果想要提升頁(yè)面的加載速度,就需要了解瀏覽器頁(yè)面的加載過(guò)程是怎樣的,從根本上來(lái)解決問(wèn)題。

          瀏覽器在加載頁(yè)面的時(shí)候會(huì)用到 GUI 渲染線程和 JavaScript 引擎線程(更詳細(xì)的瀏覽器加載和渲染機(jī)制將在第 7 講中介紹)。其中,GUI 渲染線程負(fù)責(zé)渲染瀏覽器界面 HTML 元素,JavaScript 引擎線程主要負(fù)責(zé)處理 JavaScript 腳本程序。

          由于 JavaScript 在執(zhí)行過(guò)程中還可能會(huì)改動(dòng)界面結(jié)構(gòu)和樣式,因此它們之間被設(shè)計(jì)為互斥的關(guān)系。也就是說(shuō),當(dāng) JavaScript 引擎執(zhí)行時(shí),GUI 線程會(huì)被掛起。

          以網(wǎng)易云課堂官網(wǎng)為例,我們來(lái)看看網(wǎng)頁(yè)加載流程。

          (1)當(dāng)我們打開官網(wǎng)的時(shí)候,瀏覽器會(huì)從服務(wù)器中獲取到 HTML 內(nèi)容。

          (2)瀏覽器獲取到 HTML 內(nèi)容后,就開始從上到下解析 HTML 的元素。

          (3)<head>元素內(nèi)容會(huì)先被解析,此時(shí)瀏覽器還沒(méi)開始渲染頁(yè)面。

          我們看到<head>元素里有用于描述頁(yè)面元數(shù)據(jù)的<meta>元素,還有一些<link>元素涉及外部資源(如圖片、CSS 樣式等),此時(shí)瀏覽器會(huì)去獲取這些外部資源。除此之外,我們還能看到<head>元素中還包含著不少的<script>元素,這些<script>元素通過(guò)src屬性指向外部資源。

          (4)當(dāng)瀏覽器解析到這里時(shí)(步驟 3),會(huì)暫停解析并下載 JavaScript 腳本。

          (5)當(dāng) JavaScript 腳本下載完成后,瀏覽器的控制權(quán)轉(zhuǎn)交給 JavaScript 引擎。當(dāng)腳本執(zhí)行完成后,控制權(quán)會(huì)交回給渲染引擎,渲染引擎繼續(xù)往下解析 HTML 頁(yè)面。

          (6)此時(shí)<body>元素內(nèi)容開始被解析,瀏覽器開始渲染頁(yè)面。

          在這個(gè)過(guò)程中,我們看到<head>中放置的<script>元素會(huì)阻塞頁(yè)面的渲染過(guò)程:把 JavaScript 放在<head>里,意味著必須把所有 JavaScript 代碼都下載、解析和解釋完成后,才能開始渲染頁(yè)面。

          到這里,我們就明白了:如果外部腳本加載時(shí)間很長(zhǎng)(比如一直無(wú)法完成下載),就會(huì)造成網(wǎng)頁(yè)長(zhǎng)時(shí)間失去響應(yīng),瀏覽器就會(huì)呈現(xiàn)“假死”狀態(tài),用戶體驗(yàn)會(huì)變得很糟糕。

          因此,對(duì)于對(duì)性能要求較高、需要快速將內(nèi)容呈現(xiàn)給用戶的網(wǎng)頁(yè),常常會(huì)將 JavaScript 腳本放在<body>的最后面。這樣可以避免資源阻塞,頁(yè)面得以迅速展示。我們還可以使用defer/async/preload等屬性來(lái)標(biāo)記<script>標(biāo)簽,來(lái)控制 JavaScript 的加載順序。

          百度首頁(yè)

          三、DOM 解析

          對(duì)于百度這樣的搜索引擎來(lái)說(shuō),必須要在最短的時(shí)間內(nèi)提供到可用的服務(wù)給用戶,其中就包括搜索框的顯示及可交互,除此之外的內(nèi)容優(yōu)先級(jí)會(huì)相對(duì)較低。

          瀏覽器在渲染頁(yè)面的過(guò)程需要解析 HTML、CSS 以得到 DOM 樹和 CSS 規(guī)則樹,它們結(jié)合后才生成最終的渲染樹并渲染。因此,我們還常常將 CSS 放在<head>里,可用來(lái)避免瀏覽器渲染的重復(fù)計(jì)算。


          二、HTML 與 DOM 有什么不同

          我們知道<p>是 HTML 元素,但又常常將<p>這樣一個(gè)元素稱為 DOM 節(jié)點(diǎn),那么 HTML 和 DOM 到底有什么不一樣呢?

          根據(jù) MDN 官方描述:文檔對(duì)象模型(DOM)是 HTML 和 XML 文檔的編程接口。

          也就是說(shuō),DOM 是用來(lái)操作和描述 HTML 文檔的接口。如果說(shuō)瀏覽器用 HTML 來(lái)描述網(wǎng)頁(yè)的結(jié)構(gòu)并渲染,那么使用 DOM 則可以獲取網(wǎng)頁(yè)的結(jié)構(gòu)并進(jìn)行操作。一般來(lái)說(shuō),我們使用 JavaScript 來(lái)操作 DOM 接口,從而實(shí)現(xiàn)頁(yè)面的動(dòng)態(tài)變化,以及用戶的交互操作。

          在開發(fā)過(guò)程中,常常用對(duì)象的方式來(lái)描述某一類事物,用特定的結(jié)構(gòu)集合來(lái)描述某些事物的集合。DOM 也一樣,它將 HTML 文檔解析成一個(gè)由 DOM 節(jié)點(diǎn)以及包含屬性和方法的相關(guān)對(duì)象組成的結(jié)構(gòu)集合。


          三、DOM 解析

          我們常見的 HTML 元素,在瀏覽器中會(huì)被解析成節(jié)點(diǎn)。比如下面這樣的 HTML 內(nèi)容:

          <html>
              <head>
                  <title>標(biāo)題</title>
              </head>
              <body>
                  <a href='xx.com'>我的超鏈接</a>
                  <h1>頁(yè)面第一標(biāo)題</h1>
              </body>
          </html>

          打開控制臺(tái) Elements 面板,可以看到這樣的 HTML 結(jié)構(gòu),如下圖所示:

          在瀏覽器中,上面的 HTML 會(huì)被解析成這樣的 DOM 樹,如下圖所示:


          我們都知道,對(duì)于樹狀結(jié)構(gòu)來(lái)說(shuō),常常使用parent/child/sibling等方式來(lái)描述各個(gè)節(jié)點(diǎn)之間的關(guān)系,對(duì)于 DOM 樹也不例外。

          舉個(gè)例子,我們常常會(huì)對(duì)頁(yè)面功能進(jìn)行抽象,并封裝成組件。但不管怎么進(jìn)行整理,頁(yè)面最終依然是基于 DOM 的樹狀結(jié)構(gòu),因此組件也是呈樹狀結(jié)構(gòu),組件間的關(guān)系也同樣可以使用parent/child/sibling這樣的方式來(lái)描述。同時(shí),現(xiàn)在大多數(shù)應(yīng)用程序同樣以root為根節(jié)點(diǎn)展開,我們進(jìn)行狀態(tài)管理、數(shù)據(jù)管理也常常會(huì)呈現(xiàn)出樹狀結(jié)構(gòu)。


          四、事件委托

          我們知道,瀏覽器中各個(gè)元素從頁(yè)面中接收事件的順序包括事件捕獲階段、目標(biāo)階段、事件冒泡階段。其中,基于事件冒泡機(jī)制,我們可以實(shí)現(xiàn)將子元素的事件委托給父級(jí)元素來(lái)進(jìn)行處理,這便是事件委托。

          如果我們?cè)诿總€(gè)元素上都進(jìn)行監(jiān)聽的話,則需要綁定三個(gè)事件;(假設(shè)頁(yè)面上有a,b,c三個(gè)兄弟節(jié)點(diǎn))

          function clickEventFunction(e) {
            console.log(e.target===this); // logs `true`
            // 這里可以用 this 獲取當(dāng)前元素
          }
          // 元素a,b,c綁定
          element2.addEventListener("click", clickEventFunction, false);
          element5.addEventListener("click", clickEventFunction, false);
          element8.addEventListener("click", clickEventFunction, false);

          使用事件委托,可以通過(guò)將事件添加到它們的父節(jié)點(diǎn),而將事件委托給父節(jié)點(diǎn)來(lái)觸發(fā)處理函數(shù):

          function clickEventFunction(event) {
            console.log(e.target===this); // logs `false`
            // 獲取被點(diǎn)擊的元素
            const eventTarget=event.target;
            // 檢查源元素`event.target`是否符合預(yù)期
            // 此處控制廣告面板的展示內(nèi)容
          }
          // 元素1綁定
          element1.addEventListener("click", clickEventFunction, false);

          這樣能解決什么問(wèn)題呢?

          • 綁定子元素會(huì)綁定很多次的事件,而綁定父元素只需要一次綁定。
          • 將事件委托給父節(jié)點(diǎn),這樣我們對(duì)子元素的增加和刪除、移動(dòng)等,都不需要重新進(jìn)行事件綁定。

          常見的使用方式主要是上述這種列表結(jié)構(gòu),每個(gè)選項(xiàng)都可以進(jìn)行編輯、刪除、添加標(biāo)簽等功能,而把事件委托給父元素,不管我們新增、刪除、更新選項(xiàng),都不需要手動(dòng)去綁定和移除事件。

          如果在列表數(shù)量?jī)?nèi)容較大的時(shí)候,對(duì)成千上萬(wàn)節(jié)點(diǎn)進(jìn)行事件監(jiān)聽,也是不小的性能消耗。使用事件委托的方式,我們可以大量減少瀏覽器對(duì)元素的監(jiān)聽,也是在前端性能優(yōu)化中比較簡(jiǎn)單和基礎(chǔ)的一個(gè)做法。

          注意:

          1. 如果我們直接在document.body上進(jìn)行事件委托,可能會(huì)帶來(lái)額外的問(wèn)題;
          2. 由于瀏覽器在進(jìn)行頁(yè)面渲染的時(shí)候會(huì)有合成的步驟,合成的過(guò)程會(huì)先將頁(yè)面分成不同的合成層,而用戶與瀏覽器進(jìn)行交互的時(shí)候需要接收事件。此時(shí),瀏覽器會(huì)將頁(yè)面上具有事件處理程序的區(qū)域進(jìn)行標(biāo)記,被標(biāo)記的區(qū)域會(huì)與主線程進(jìn)行通信。
          3. 如果我們document.body上被綁定了事件,這時(shí)候整個(gè)頁(yè)面都會(huì)被標(biāo)記;
          4. 即使我們的頁(yè)面不關(guān)心某些部分的用戶交互,合成器線程也必須與主線程進(jìn)行通信,并在每次事件發(fā)生時(shí)進(jìn)行等待。這種情況,我們可以使用passive: true選項(xiàng)來(lái)解決


          五、總結(jié)

          我們了解了 HTML 的作用,以及它是如何影響瀏覽器中頁(yè)面的加載過(guò)程的,同時(shí)還介紹了使用 DOM 接口來(lái)控制 HTML 的展示和功能邏輯。我們了解了DOM解析事件委托等相關(guān)概念。

          用 CSS 最困難的部分之一是處理CSS的權(quán)重值,它可以決定到底哪條規(guī)則會(huì)最終被應(yīng)用,尤其是如果你想在 Bootstrap 這樣的框架中覆蓋其已有樣式,更加顯得麻煩。不過(guò)隨著 CSS 層的引入,這一切都發(fā)生了變化。 這個(gè)新功能允許您創(chuàng)建自己的自定義 CSS 層,這是有史以來(lái)第一次確定所有 CSS 代碼權(quán)重的層次結(jié)構(gòu)。 在本文中,我將剖析這對(duì)您意味著什么,它是如何工作的,以及您今天如何開始使用它。

          什么是層(Layers)

          創(chuàng)建您自己的自定義圖層是 CSS 的新功能,但圖層從一開始就存在于 CSS 中。 CSS 中有 3 個(gè)不同的層來(lái)管理所有樣式的工作方式。

          瀏覽器(也稱為用戶代理)樣式 - user agent style
          用戶樣式 - User Styles
          作者樣式 - Author Styles

          瀏覽器樣式是應(yīng)用于瀏覽器的默認(rèn)樣式。這就是為什么 Chrome 和 Safari 中的按鈕看起來(lái)不同的原因。在瀏覽器層中找到的樣式在瀏覽器之間是不同的,并且給每個(gè)瀏覽器一個(gè)獨(dú)特的外觀。

          下一層是用戶樣式,這并不是您真正需要擔(dān)心的事情。這些通常是用戶可以編寫并注入瀏覽器的自定義樣式,但瀏覽器不再真正支持這些樣式。用戶可能會(huì)更改一些瀏覽器設(shè)置,這些設(shè)置會(huì)向該圖層添加樣式,但在大多數(shù)情況下,可以完全忽略該層。

          最后,我們來(lái)到作者層。這是您最熟悉的層,因?yàn)槟帉懙拿恳欢?CSS 代碼都屬于這一層。

          這些層分開的原因是因?yàn)樗梢院苋菀椎馗采w瀏覽器樣式和用戶樣式中定義的代碼,因?yàn)閷佣x了自己的層次結(jié)構(gòu),完全忽略了權(quán)重的影響。

          這 3 個(gè) CSS 層是有序的(瀏覽器樣式、用戶樣式、然后是作者樣式),后面層中的每個(gè)樣式都將覆蓋前一層的任何樣式。這意味著即使瀏覽器樣式定義了一個(gè)超級(jí)特定的選擇器,例如#button.btn.super-specific,并且您的作者樣式定義了一個(gè)超級(jí)通用的選擇器,例如按鈕,您的作者樣式仍然會(huì)覆蓋瀏覽器樣式。

          這實(shí)際上已經(jīng)是您可能一直在使用而沒(méi)有意識(shí)到的東西。

          * {
            box-sizing: border-box;
          }

          上面的選擇器沒(méi)有權(quán)重,因?yàn)?* 符號(hào)對(duì)權(quán)重沒(méi)有貢獻(xiàn)。 這意味著例如使用 p 作為選擇器的 p 標(biāo)簽的瀏覽器樣式在技術(shù)上比 * 選擇器更具體,權(quán)重更高。 但是,這一切并不重要,因?yàn)樽髡邩邮轿挥诒葹g覽器樣式層晚的層中,因此您的代碼將始終覆蓋瀏覽器樣式。

          理解這一點(diǎn)至關(guān)重要,因?yàn)槭褂眠@個(gè)新的圖層 API,您可以在作者圖層中創(chuàng)建自己的圖層,從而更輕松地處理特定性。

          如何創(chuàng)建你自己的層

          下面來(lái)看個(gè)例子:

          很明顯,這是我們正常理解的CSS, ID設(shè)置的顏色權(quán)重更高,所以按鈕顯示為紅色。讓我們使用@layer給它們加上兩個(gè)層,看看是什么效果:

          按鈕變成藍(lán)色。為什么會(huì)這樣?

          我們給兩條CSS分別建立了base和utilities層,很明顯,后面創(chuàng)建的層的樣式覆蓋了前面層的樣式,盡管前面層的樣式有更高的權(quán)重。這就是層的默認(rèn)工作原理。當(dāng)然層的順序是可以指定的,

          @layer utilities, base;

          @layer utilities, base;

          您需要做的就是編寫@layer 關(guān)鍵字,后跟以逗號(hào)分隔的層列表。 這將按從左到右的順序定義所有層,其中列出的第一層到最后一層的權(quán)重是依次增加的。 然后,您可以稍后使用普通的@layer 語(yǔ)法向每個(gè)層添加代碼,而不必?fù)?dān)心定義層的順序,因?yàn)樗鼈兌荚谶@一行中定義。 需要注意的是,這行代碼必須在定義任何層之前出現(xiàn),所以我通常將它作為我的 CSS 文件中的第一行。如上圖,通過(guò)指定層的順序,我們讓base層應(yīng)用在utilities層之后,所以按鈕又顯示為紅色。

          導(dǎo)入層

          上面這兩種方式都是導(dǎo)入bootstrap框架的CSS,并且把他們放在framework層中,這樣你如果想要覆蓋它已有的樣式,只需要新建一個(gè)自己的層,放置在framework層后面就行。像下面這樣。

          匿名層

          匿名層不常用,但它寫在后面可以覆蓋其他層的樣式,像下面可以把按鈕設(shè)為橙色。

          不在層里的樣式

          不在層里的樣式會(huì)有更高的權(quán)重,下面這個(gè)列表會(huì)讓你看得更清楚覆蓋是怎么發(fā)生的

          層還可以重疊設(shè)置,不過(guò)很少用。具體的用法可以查閱相關(guān)文檔。

          瀏覽器支持

          自從IE死了以后,所有主流瀏覽器都已支持這一特性。大家請(qǐng)放心使用。


          主站蜘蛛池模板: 波多野结衣一区二区三区高清av| 天堂va在线高清一区| 97av麻豆蜜桃一区二区| 国产SUV精品一区二区88L | 一区二区三区在线视频播放| 亚洲伦理一区二区| 亚洲AⅤ视频一区二区三区| 在线成人一区二区| 亚洲视频一区网站| 国产另类TS人妖一区二区| 99国产精品一区二区| 亚洲AV无码一区二区三区国产| 日韩一区二区三区电影在线观看| 国产精品香蕉在线一区| 日韩精品在线一区二区| 国产高清视频一区三区| 久久久久一区二区三区| 一区二区三区无码视频免费福利 | 国产福利电影一区二区三区久久老子无码午夜伦不 | 精品一区二区久久久久久久网精| 搡老熟女老女人一区二区| 色偷偷一区二区无码视频| 亚洲一区二区免费视频| 国产无线乱码一区二三区| 精品国产日产一区二区三区| 亚洲乱码国产一区三区| 久久国产精品最新一区| 91精品一区二区三区在线观看| 色窝窝无码一区二区三区| 日日摸夜夜添一区| 国产一区二区三区播放心情潘金莲| 亚洲av无码一区二区三区天堂| 国产精品无码亚洲一区二区三区 | 亚洲国产精品一区二区久| 中字幕一区二区三区乱码| 女同一区二区在线观看| 日韩动漫av在线播放一区| 少妇人妻偷人精品一区二区| 无码人妻精品一区二区三区在线 | 亚洲色无码一区二区三区| 骚片AV蜜桃精品一区|