您好,登錄后才能下訂單哦!
這篇文章主要講解了“HTML/CSS/JS編碼規范有哪些”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“HTML/CSS/JS編碼規范有哪些”吧!
根據W3C標準,img標簽要寫alt屬性,如果沒有就寫一個空的。但是一般要寫一個有內容的,根據圖片想要表達的意思,因為alt是在圖片無法加載時顯示的文字。如下不太好的寫法:
<img src="company-logo.svg" alt="ABC Company Logo">
更好的寫法:
<img src="chime-logo.svg" alt="ABC Company">
這里就不用告訴用戶它是一個Logo了,直接告訴它是ABC Compay就好了。再如:
<img src="user-head.jpg" alt="User Portrait">
可改成:
<img src="user-head.jpg" alt="Bolynga Team">
如果圖片顯示不出來,就直接顯示用戶的名字。
有些人偷懶就直接寫個空的alt那也可以,但是在一些重要的地方還是要寫一下,畢竟它還是有利于SEO.
為什么?因為寫了也沒用,還顯得你不懂html規范,我們不是寫XHTML。常見的單標簽有img、link、input、hr、br,如下不好的寫法:
<img src="test.jpg"></img><br/> <input type="email" value=""/>
應改成:
<img src="test.jpg"><br> <input type="email" value="">
如果你用React寫jsx模板,它就要求每個標簽都要閉合,但是它始終不是原生html.
自己添加的非標準的屬性要以data-開頭,否則 w3c validator 會認為是不規范的,如下不好的寫法:
<div count="5"></div>
應改成:
<div data-count="5"></div>
如下不好的寫法:
<div> <li></li> <li></li> </div>
更常見的是td沒有寫在tr里面:
<table> <td></td> <td></td> </table>
如果你寫得不規范,有些瀏覽器會幫你矯正,但是有些可能就沒有那么幸運。因為標準并沒有說如果寫得不規范應該怎么處理,各家瀏覽器可能有自己的處理方式。
有時候可能會直接在ul里面寫一個div或者span,以為這樣沒關系:
<ol> <span>123</span> <li>a</li> <li>b</li> </ol>
這樣寫也是不規范的,不能直接在ol里面寫span,ol是一個列表,它的子元素應該都是display: list-item的,突然冒出來個span,你讓瀏覽器如何自處。所以寫得不規范就會導致在不同的瀏覽器會有不同的表現。
同樣,tr的直接子元素都應該是td,你在td里面寫tr那就亂了。
如果你用了section/aside/article/nav這種標簽的話,需要在里面寫一個h2/h3/h4之類的標題標簽,因為這四個標簽可以劃分章節,它們都是獨立的章節,需要有標題,如果UI里面根本就沒有標題呢?那你可以寫一個隱藏的標題標簽,如果出于SEO的目的,你不能直接display: none,而要用一些特殊的處理方式,如下套一個hidden-text的類:
<style>.hidden-text{position: absolute; left: -9999px; right: -9999px}</style> <section> <h2>Listing Detail</h2> </section>
使用section的好處是可以劃分章節,如下代碼:
<body> <h2>Listing Detail</h2> <section> <h2>House Infomation</h2> <section> <h2>LOCATION</h2> <p></p> </section> <section> <h2>BUILDING</h2> <p></p> </section> </section> <section> <h2>Listing Picture</h2> </section> </body>
就會被outline成這樣的大綱:
Listing Detail
House Infomation
LOCATION
BUILDING
Listing Picture
可以使用 html5 outliner 進行實驗,可以看到,我們很任性地使用了多個h2標簽,這個在html4里面是不合法的。
例如下面的寫法是不合法的:
<a href="/listing?id=123"> <div></div> </a>
a標簽是一個行內元素,行內元素里面套了一個div的標簽,這樣可能會導致a標簽無法正常點擊。再或者是span里面套了div,這種情況下需要把inline元素顯式地設置display為block,如下代碼:
<a href="/listing?id=123" style="display: block"> <div></div> </a>
這樣就正常了。
設置頁面的渲染模式為標準模式,如果忘記寫了會怎么樣?忘記寫了會變成怪異模式,怪異模式下很多東西渲染會有所不同,怪異模式下input/textarea的默認盒模型會變成border-box,文檔高度會變成可視窗口的高度,獲取window的高度時就不是期望的文檔高度。還有一個區別,父容器行高在怪異模式下將不會影響子元素,如下代碼:
<div><img src="test.jpg" style="height:100px"></div>
在標準模式下div下方會留點空白,而在怪異模式下會。這個就提醒我們在寫郵件模板時需要在頂部加上<!DOCType html>,因為在本地開發郵件模板時是寫html片段,沒有這個的話就會變成怪異模式。
由于郵件客戶端多種多樣,你不知道用戶是使用什么看的郵件,有可能是用的網頁郵箱,也有可能用的gmail/outlook/網易郵箱大師等客戶端。這些客戶端多種多樣,對html/css的支持也不一,所以我們不能使用高級的布局和排版,例如flex/float/absolute定位,使用較初級的table布局能夠達到兼容性最好的效果,并且還有伸縮的效果。
另外郵件模板里面不能寫媒體查詢,不能寫script,不能寫外聯樣式,這些都會被郵件客戶端過濾掉,樣式都得用內聯style,你可以先寫成外聯,然后再用一些工具幫你生成內聯html。
寫完后要實際測一下,可以用QQ郵箱發送,它支持發送html格式文本,發完后在不同的客戶端打開看一下,看有沒有問題,如手機的客戶端,電腦的客戶端,以及瀏覽器。
由于你不知道用戶是用手機打開還是電腦打開,所以你不能把郵件內容的寬度寫死,但是完全100%也不好,在PC大屏幕上看起來可能會太大,所以一般可以這樣寫:
<table style="border-collapse:collapse;font-family: Helvetica Neue,Helvetica,Arial;font-size:14px;width:100%;height:100%"> <tr><td valign="top"><table style="border:1px solid #ececec;border-top:none; max-width:600px;border-collapse:collapse"> <tr><td>內容1</td></tr> <tr><td>內容2</td></tr> </table></td></tr></table>
最外面的table寬度100%,里面的table有一個max-width:600px,相對于外面的table居中。這樣在PC上最大寬度就為600px,而在手機客戶端上寬度就為100%。
但是有些客戶端如比較老的outlook無法識別max-width的屬性,導致在PC上太寬。但是這個沒有辦法,因為我們不能直接把寬度寫死不然在手機上就要左右滑了,也不能寫script判斷ua之類的方法。所以無法兼容較老版本outlook.
需要套很多層的,一般有兩種情況,一種是切圖不太會,需要套很多層來維持排版,第二種是會切圖,但是把UI拆解得太細。像以下布局:
我會這么寫:
<ul> <li> <div> <img> </div> <div></div> </li> </ul>
因為它是一個列表,所以外面用ul/li作為容器,里面就兩個div,一個圖片的float: left,另外一個文字的容器,這樣就可以了。不用套很多層,但是有一些是必要的,你寫得太簡單的話,擴展性不好,例如它是一個列表那你就應該使用ul/ol,如果你要清除浮動,那你可能需要套一個clearfix的容器。
如果有一塊是整體,它整體向右排版,那么這一塊也要套一個容器,有時候為了實現一些效果,可能也要多套一個容器,例如讓外面的容器float,而里面的容器display: table-cell。但是你套這些容器應該都是有價值,如果你只是為了在功能上看起來整齊劃一,區分明顯好像沒太大的意義。
通常來說,在html里面直接寫script和style是一種不好的實踐,這樣把樣式、結構和邏輯都摻雜在一起了。但是有時候為了避免閃屏的問題,可能會直接在相應的html后面跟上調整的script,這種script看起來有點丑陋,但是很實用,是沒有辦法的辦法。
樣式不能寫在body里,寫在body里會導致渲染兩次,特別是寫得越靠后,可能會出現閃屏的情況,例如上面的已經渲染好了,突然遇到一個style標簽,導致它要重新渲染,這樣就閃了一下,不管是從碼農的追求還是用戶的體驗,在body里面寫style終究是一種下策。
同樣地script不要寫在head標簽里面,會阻礙頁面加載。
而CSS也推薦寫成style標簽直接嵌在頁面上,因為如果搞個外鏈,瀏覽器需要先做域名解析,然后再建立連接,接著才是下載,這一套下來可能已經過了0.5s/1s,甚至2~3秒。而寫在頁面的CSS雖然無法緩存,但是本身它也不會很大,再加gzip壓縮,基本上在50k以內。
如下,如果是英文的網頁,應該這么寫:
<html> <html>
第一種表示它是英文的網頁,第二種表示它是美國英語的網頁,加上這個的好處是有利于SEO和屏幕閱讀器使用者,他可以快速地知道這個網頁是什么語言的,如果是中文可以這么寫:
<html>
如下,一般charset的meta標簽要寫在head標簽后的第一個標簽:
<head> <meta charset="utf-8"> </head>
一個原因是避免網頁顯示unicode符號時亂碼,寫在前面是因為w3c有規定,語言編碼要在html文檔的前1024個字節。如果不寫的話在老的瀏覽器會有utf-7攻擊的隱患,具體可以自行查閱資料,只是現在的瀏覽器基本都去掉了對utf-7編碼的支持了。
charset的標簽寫成html5的這種比較簡潔的寫法就行了,不需要寫成html4這種長長的:
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
據我所查,就算是IE6也支持那種簡短的寫法,雖然它不是一個html5瀏覽器。
不要直接把Unicode的特殊符號直接拷到html文檔里面,要使用它對應的實體Entity,常用的如下表所示:
| 符號 | 實體編碼 |
| ? | ? |
| ¥ | ¥ |
| ? | ? |
| > | > |
| < | < |
| & | & |
特別是像?這種符號,不要從UI里面直接拷一個unicode的字符過去,如果直接拷過去會比較丑,它取的是用的字體里面的符號。
有時候可能你需要在寫一個空的img標簽,然后在JS里面動態地給它賦src,所以你可能會這么寫:
<img src="" alt>
但是這樣寫會有問題,如果你寫了一個空的src,會導致瀏覽器認為src就是當前頁面鏈接,然后會再一次請求當前頁面,就跟你寫一個a標簽的href為空類似。如果是background-image也會有類似的問題。這個時候怎么辦呢?如果你隨便寫一個不存在的url,瀏覽器會報404的錯誤。
我知道的有兩種解決方法,第一種是把src寫成about:blank,如下:
<img src="about:blank" alt>
這樣它會去加載一個空白頁面,這個沒有兼容問題,不會加載當前頁面,也不會報錯。
第二種辦法是寫一個1px的透明像素的base64,如下代碼所示:
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7">
第二種可能比較符合規范,但是第一種比較簡單,并且沒有兼容性問題。
有時候換行可能會引入空格,如下代碼:
<form> <label>Email: </label> <input type="email"> </form>
在label和input中間會有一個空格,這樣可能會導致設置lable的width和input 的width兩者的和等于form的時候會導致input換行了,有時候你檢查半天沒查出原因,最后可能發現,原來是多了一個空格,而這個空格是換行引起的。這個時候你可能會有一個問題,為什么<form>和<label>之間以及<input>和</form>之間的換行為什么沒引入空格?這是因為塊級元素開始的空白文本將會被忽略,如下Chrome源碼的說明:
// Whitespace at the start of a block just goes away. Don't even
// make a layout object for this text.
并且,塊級元素后面的空白文本結點將不會參與渲染,也就是說像這種:
<div></div> <div></div>
兩個div之間有textNode的文本節點,但是不會參與渲染。
要注意的是注釋標簽也是正常的頁面標簽,也會給它創建一個相應的節點,只是它不參與渲染。
如下使用-連接,不要使用駝峰式:
<div></div>
是否可以使用自定義標簽,像angular那樣都是用的自定義標簽,如下代碼:
<my-cotnainer></my-container>
一般不推薦使用自定義標簽,angular也有開關可以控制是否要使用自定義標簽。雖然使用自定義標簽也是合法的,只要你給他display: block,它就像一個div一樣了,但是不管是從SEO還是規范化的角度,自定義標簽還是有點另類,雖然可能你會覺得它的語義化更好。
我們知道,如果在頁面寫了兩個一模一樣的id,那么查DOM的時候只會取第一個,同理重復的屬性也會只取第一個,如下:
<input type="text" name="books">
第二個class將會被忽略,className重復了又會怎么樣?重復的也是無效的,這里主要是注意如果你直接操作原生className要注意避免className重復,如下代碼:
var input = form.books; input.className += " valid";
如果重復執行的話,className將會有重復的valid類。
例如,如果你要設置一個圖片的寬高,可能這么寫:
<img src="test.jpg" alt width="400" height="300">
這個在ios的safari上面是不支持的,可以自行實驗。
或者table也有一些可以設置:
<table border="1"></table>
但是這種能夠用CSS設置的就用CSS,但是有一個例外就是canvas的寬高需要寫在html上,如下代碼:
<canvas width="800" height="600"></canvas>
如果你用CSS設置的話它會變成拉伸,變得比較模糊。
標簽使用上不要太單調:
(1)如果內容是表格就使用table,table有自適應的優點;如果是一個列表就使用ol/ul標簽,擴展性比較好
(2)如果是輸入框就使用input,而不是寫一個p標簽,然后設置contenteditable=true,因為這個在IOS Safari上光標定位容易出現問題。如果需要做特殊效果除外
(3)如果是粗體就使用b/strong,而不是自己設置font-weight
(4)如果是表單就使用form標簽,注意form里面不能套form
(5)如果是跳鏈就使用a標簽,而不是自己寫onclick跳轉。a標簽里面不能套a標簽
(6)使用html5語義化標簽,如導航使用nav,側邊欄使用aside,頂部和尾部使用header/footer,頁面比較獨立的部分可以使用article,如用戶的評論。
(7)如果是按鈕就應該寫一個button或者<input type=”button”>,而不是寫一個a標簽設置樣式,因為使用button可以設置disabled,然后使用CSS的:disabled,還有:active等偽類使用,例如在:active的時候設置按鈕被按下去的感覺
(8)如果是標題就應該使用標題標簽h2/h3/h4,而不是自己寫一個<p class=”title”></p>,相反如果內容不是標題就不要使用標題標簽了
(9)在手機上使用select標簽,會有原生的下拉控件,手機上原生select的下拉效果體驗往往比較好,不管是IOS還是android,而使用<input type=”tel”>在手機上會彈一個電話號碼的鍵盤,<input type=”number”> <input type=”email”>都會彈相應的鍵盤
(10)如果是分隔線就使用hr標簽,而不是自己寫一個border-bottom的樣式,使用hr容易進行檢查
(11)如果是換行文本就應該使用p標簽,而不是寫br,因為p標簽可以用margin設置行間距,但是如果是長文本的話使用div,因為p標簽里面不能有p標簽,特別是當數據是后端給的,可能會帶有p標簽,所以這時容器不能使用p標簽。
只要https的網頁請求了一張http的圖片,就會導致瀏覽器地址欄左邊的小鎖沒有了,一般不要寫死,寫成根據當前域名的協議去加載,用//開頭:
<img src=”//static.chimeroi.com/hello-world.jpg”>
文件名建議用小寫字母加中橫線的方式。為什么呢?因為這樣可讀性比較強,看起來比較清爽,像鏈接也是用這樣的方式,例如stackoverflow的url:
https://stackoverflow.com/questions/25704650/disable-blue-highlight-when-touch-press-object-with-cursorpointer
或者是github的地址:
https://github.com/wangjeaf/ckstyle-node
那為什么變量名不用小寫字母加小劃線的方式,如:family_tree,而是推薦用駝峰式的familyTree?C語言就喜歡用這種方式命名變量,但是由于因為下劃線比較難敲(shift + -),所以一般用駝峰式命名變量的居多。
引入CSS文件的link可以不用帶type="text/css",如下代碼:
<link rel="stylesheet" href="test.css">
因為link里面最重要的是rel這個屬性,可以不要type,但是不能沒有rel。
JS也是同樣道理,可以不用type,如下代碼:
<script src="test.js"></script>
沒有兼容性問題。
屬性的書寫順序對于瀏覽器來說沒有區別,除了優先級覆蓋之外。但是如果順序保持一致的話,掃一眼可以很快地知道這個選擇器有什么類型的屬性影響了它,所以一般要把比較重要的屬性放前面。比較建議的順序是這樣的:
你可能會覺得我平時差不多就是這么寫的,那么說明你有一個比較好的素養。并且我覺得規則不是死,有時候可以靈活,就像你可能會用transform寫居中,然后把left/top/transform挨在一起寫了,我覺得這也是無可厚非的,因為這樣可以讓人一眼看出你要干嘛。
有些人可能喜歡用樣式的特點命名,例如:
.red-font{ color: red; } .p1{ font-size: 18px; } .p2{ font-size: 16px; }
然后你在它的html里面就會看到套了大量的p1/p2/bold-font/right-wrap之類的類名,這種是不可取的,假設你搞了個red-font,下次UI要改顏色,那你寫的這個類名就沒用了,或者是在響應式里面在右邊的排版在小屏的時候就會跑到下面去,那你取個right就沒用了。有些人先把UI整體瞄了一下,發現UI大概用了3種字號18px/16px/14px,于是寫3個類p1/p2/p3,不同的字號就套不同的類。這乍一看,好像寫得挺通用,但是當你看他的html時,你就瘋掉了,這些p1/p2/p3的類加起寫了有二三十個,密密麻麻的。我覺得如果要這樣寫的話還不如借助標題標簽如下:
.house-info h3{ font-size: 18px; } .house-info h4{ font-size: 16px; }
因為把它的字號加大了,很可能是一個標題,所以為什么不直接用標題標簽,不能僅僅擔心因為標題標簽會有默認樣式。
類的命名應當使用它所表示的邏輯意義,如signup-success-toast、request-demo、agent-portrait、 company-logo等等。
如果有些樣式你覺得真的特別通用,那可以把它當作一個類,如clearfix,或者有些動畫效果,有幾個地方都要用到,我覺得這種較為復雜并且通用的可以單獨作為一個類。但是還是趨向于使用意義命名。
有些人在寫CSS的時候使用一些hack的方法注釋,如下:
.agent-name{ float: left; _margin: 10px; //padding: 20px; }
這種方法的原理是由于//或者_開頭的CSS屬性瀏覽器不認識,于是就被忽略,分號是屬性終止符,從//到分號的內容都被瀏覽器忽略,但是這種注釋是不提倡的,要么把.css文件改成.less或者.scss文件,這樣就可以愉快地用//注釋了。
還有一些專門針對特定瀏覽器的hack,如*開頭的屬性是對IE6的hack。不管怎么樣都不要使用hack。
選擇器一般不要寫超過3個,有些人寫sass或者less喜歡套很多層,如下:
.listings-list{ ul{ li{ .bed-bath{ p{ color: #505050; } } } } }
一個容器就套一層,一層一層地套下來,最底層套了七八層,這么長的選擇器的性能比較差,因為Chrome里面是用遞歸從最后一個選擇器一直匹配到第一個,選擇器越多,匹配的時間就越長,所以時間會比較長,并且代碼的可讀性也比較差,為看到最里面的p標簽的樣式是哪個的我得一層層地往上看,看是哪里的p。代碼里面縮進了7、8層看起來也比較累。
一般只要寫兩三個比較重要的選擇器就好了,不用每個容器都寫進去,重要的目標元素套上class或者id。
最后一個選擇器的標簽的應該少用,因為如果你寫個.container div{}的話,那么頁面上所有的div第一次都匹配中,因為它是從右往左匹配的,這樣的寫的好處是html不用套很多的類,但是擴展性不好,所以不要輕易這樣用,如果要用需要仔細考慮,如果合適才使用,最起碼不能濫用。
有時候會出現自己的樣式受到其他人樣式的影響,或者自己的樣式不小心影響了別人,有可能是因為類的命名和別人一樣,還有可能是選擇器寫的范圍太廣,例如有人在他自己的頁面寫了:
* { box-sizing: border-box; }
結果導致在他個頁面的公用彈框樣式掛了。一方面不要寫*全局匹配選擇器,不管從性能還是影響范圍來說都太大了,例如在一個有3個子選擇器的選擇器:
.house-info .key-detail .location{}復制代碼
在三個容器里面,*都是適用的,并且有些屬性是會繼承的,像font-size,會導致這三個容器都有font-size,然后一層層地覆蓋。
還有一種情況是濫用了:first-child、:nth-of-type這種選擇器,使用這種選擇器的后果是擴展性不好,只要html改了,就會導致樣式不管用了,或者影響到了其它無關元素。但是并不是說這種選擇器就不能用,只要用得好還是挺方便的,例如說在所有的li里面要讓最后一個li的margin-left小一點,那么可以這么寫:
.listing-list li:last-child{ margin-left: 10px; }
這可能比你直接套一個類強。但是不管怎么樣,不能濫用,合適的時候才使用,而不是僅僅為了少寫類名。
覆蓋是一種常用的策略,也是一種不太優雅的方式,如下代碼,為了讓每個house中間的20px的間距,但是第一個house不要有間距:
.house{ margin-top: 20px; } .house:first-child{ margin-top: 0; }
其實可以改成這樣:
.house + .house{ margin-top: 20px; }
只有前面有.house的.house才能命中這個選擇器,由于第一個.house前面沒有,所以命不中,這樣看起來代碼就簡潔多了。
還有這種情況,如下代碼所示:
.request-demo input{ border: 1px solid #282828; } .request-demo input[type=submit]{ border: none; }
其實可以借助一個:not選擇器:
.request-demo input:not([type=sbumit]){ border: 1px solid #282828; }
這樣看起來代碼也優雅了很多。
有一種覆蓋是值得的,那就是響應式里面小屏的樣式覆蓋大屏,如下:
.container{ width: 1080px; margin: 0 auto; } @media (min-width: 1024px){ .container{ width: auto; margin: 0 40px; } }
大屏的樣式也可以寫成:
@media (min-width: 1025px){ .container{ width: 1080px; margin: 0 auto; } }
我一開始是就是這么寫的,為了遵循減少覆蓋原則,但是后來發現這種實踐不好,代碼容易亂,寫成覆蓋的好處在于可以在瀏覽器明顯地看到,小屏的樣式是覆蓋了哪個大屏的樣式,這個在大屏的時候又是怎么樣的。
上面提到:not可以讓代碼簡潔,還有其它一些很好用的。例如說只有兩個的時候一個占比50%,而有3個的時候一個占比33%,這個用CSS就可以實現,如下:
.listing-list li{ width: 33%; } .listing-list li:first-child:nth-last-child(2), .listing-list li:first-child:nth-last-child(2) ~ li{ width: 50%; }
當li是第一個元素并且是倒數第二個元素的時候以及和它相鄰的li被第二組選擇器命中,它的寬度是50%,也就是只有兩個li的時候才能滿足這個條件。
另外還可以借用:hover/:focus/:invalid/:disabled等偽類選擇器完成一些簡單的交互。
important用來覆蓋屬性,特別是在CSS里面用來覆蓋style里的屬性,但是important還是少用為妙。有時候你為了偷懶直接寫個!important,以為這個的優先級是最高的了,往往螳螂捕蟬,黃雀在后,很可能過不了多久又要再寫一個優先級更高的覆蓋掉,這樣就略顯尷尬了。所以能少用還是少用。如果要覆蓋還是先通過增加添加選擇器權重的方式。
"程序猿最煩兩件事,第一件事是別人要他給自己的代碼寫文檔,第二件呢?是別人的程序沒有留下文檔"。注釋也是同樣道理,當看到很多綠色的注釋代碼神經會比較放松,而當看到揉成一團還沒有注釋的代碼是比較壓抑的。CSS的注釋可包括:
/* * @description整個列表頁樣式入口文件 * @author yincheng.li */
/*詳情頁貸款計算器*/復制代碼
/*為了去除輸入框和表單點擊時的灰色背景*/ input, form{ -webkit-tap-highlight-color: rgba(255, 255, 255, 0); }
有時候你看源碼的時候你會看到一些TODO的注釋:
/* TODO(littledan): Computed properties don't work yet in nosnap. Rephrase when they do. */復制代碼
表示這些代碼還有待完善,或者有些缺陷需要以后修復。而這種TODO的注釋一般編輯器會把TODO高亮。
注意不要寫一些錯誤的誤導的注釋或者比較廢話的注釋,這種還不如不寫,如下:
/* 標題的字號要大一點 */ .listings h3{ font-size: 20px; }
不管是JS/CSS,縮進都調成4個空格,如果你用的sublime,在軟件的右下角有一個Tab Size,選擇Tab Size 4,然后再把最上面的Indent Using Spaces勾上,這樣,當你打一個tab鍵縮進的時候就會自動轉換成4個空格。如果你使用vim,新增或者編輯~/.vimrc文件新增一行:
:set tabstop=4
也會自動把縮進改成4個空格,其它編輯器自行查找設置方法。因為\t在不同的編輯器上顯示長度不一樣,而改成空格可以在不同人的電腦上格式保持一致。
多個選擇器共用一個樣式集,每個選擇器要各占一行,如下:
.landing-pop, .samll-pop-outer, .signup-success{ display: none; }
每個屬性名字冒號后面要帶個空格,~、>、+選擇器的前后也要帶一個空格:
.listings > li{ float: left; }
例如:
.list{ border: 1px solid 0px; margin: 0px; }
應改成:
.list{ border: 1px solid 0; margin: 0; }
但是有個特例,就是和時間有關的時間單位都要帶上秒s,如下兩個都是不合法的:
transition-duration: 0; transition: transform 0 linear;
如下:
color: rgb(80, 80, 80);
應改成:
color: #505050;
因為使用rgb是一個函數,它還要計算一下轉換。如果是帶有透明度的再用rgba.
如果色值的六個數字一樣,那么寫3個就好:
color: #ccc;
如下兩個意思一樣:
border: 0; border-width: 0;
而下面這兩個一樣:
border: none; border-style: none;
所以用0和none都可以去掉邊框。
你可能會說打包工具其實最后會幫我處理,但自己要保持一個良好的書寫習慣還是很重要的。
注意使用系統字體的對應的font-family名稱,如SFUIText Font這個字體,在Safari是-apple-system,而在Chrome是BlinkMacSystemFont,所以font-family可以這么寫:
font-family{ font-family: -apple-system, BlinkMacSystemFont, sans-serif; }
再如微軟雅黑,很多中文網站都用這個字體,要寫成:
font-family{ font-family: Microsoft YaHei; }
另外font-family不能在代碼任意設置,如果使用了自定義字體。如下代碼:
.title{ font-family: Lato Bold; }
因為如果你在代碼里面寫了好多個font-family,到時候要整體替換網頁的字體就很麻煩了,正確的做法應該是這樣的:
h2, strong, b{ font-family: Lato Bold; font-weight: normal; }
如果需要加粗就用標題標簽,或者b/strong標簽,并且要把font-weight調回來,因為那個字體本身就有加粗效果了,如果font-weight再是粗體的話瀏覽器會用自己的算法繼續加粗。如果是細體怎么辦,一方面一般細體用得比較少,另一方面沒有細體的標簽,可以通過套類的方式。
有些人喜歡設置z-index很大:
z-index: 99999;
以為他是老大了,不會有人再比他高了,但是螳螂捕蟬,黃雀在后,很快得再寫一個:
z-index: 999999;
通常自己頁面的業務邏輯的z-index應該保持在個位數就好了。
一般的說法是說為了提高性能,屬性要合并,但其實Chrome每個屬性都是單獨的,就算你合在一起,它也會幫你拆出來,如把margin拆成left/right/top/bottom,但是我們還是推薦寫成合的,因為它可以讓代碼看起來更簡潔,代碼量更少,如下代碼:
.container{ margin-top: 20px; margin-left: 10px; margin-right: 10px; }
可以寫成:
.container{ margin: 20px 10px 0; }
但是合在一起寫了,要注意別覆蓋了其它的設置,如上面把margin-bottom設置成了0.
再如:
.banner{ background-image: url(/test.jpg); background-position: 0 0; background-repeat: no-repeat; }
可以改成:
.banner{ background: url(test.jpg) 0 0 no-repeat; }
如下代碼:
a.btn { float: left; display: block; width: 100px; height: 30px; }
第二行的display: block其實是沒用的,因為如果你浮動了,目標元素就會具有塊級盒模型的特性,即使你display: table-cell或者inline也不管用。如果你是display: flex,那么float將會被忽略。
同樣地,absolute定位和fixed定位也有同樣的效果,會把行內元素變成塊級的。
清除浮動有多種方法,一般用clearfix大=法,雖然這個方法有缺陷,但是它比較簡單且能夠適用絕大多數的場景,一個兼容IE8及以上的clearfix的寫法:
.clearfix:after{ content: ""; display: table; clear: both; }
就不要在末尾添加一個多余元素的方法清除浮動了,雖然也可行,但是比較low.
一般來說font-family不需要添加引號,即使字體名稱帶有空格也沒關系,但是有一種情況是一定要加上引號的,就是字體名稱剛好是關鍵詞,如下字體都需要加關鍵詞:
font-family: "inherit", "serif", "sans-serif", "monospace", "fantasy", and "cursive"
background-url: url("//cdn.test.me/test.jpg");
你不加也可以,但是有一種一定要加,那就是url里面帶有特殊字符沒有轉義,如下:
background-url: url(//cdn.test.me/hello world.jpg)
上面瀏覽器會去加載//cdn.test.me/hello,然后報404。這種情況通常是圖片是用戶上傳的,圖片的名字帶有空格,后端給的url沒有對特殊字符做處理,就會有問題,所以當url是可變的時候,最好還是帶上引號:
background-url: url('//cdn.test.me/hello world.jpg');
這樣瀏覽器就能正常加載圖片了。這種情況最好的還是從源頭上避免,但我們也可以做個兼容。
這兩個都是合法的,只是統一一下比較好,不能一下子單引號,一下子雙引號的,比較普遍的推薦是html使用雙引號,css使用單引號。
(1)不要使用all屬性做動畫
使用transition做動畫的時候不要使用all所有屬性,在有一些瀏覽器上面可能會有一些問題,如下:
transition: all 2s linear;
在Safari上面可能會有一些奇怪的抖動,正確的做法是要用哪個屬性做動畫就寫哪個,如果有多個就用隔開,如下代碼所示:
transition: transform 2s linear, opacity 2s linear;
如果能用transform做動畫的,就不會使用left/top/margin等,因為transform不會造成重繪,性能要比position那些高很多,特別是在移動端的時候效果比較明顯。基本上位移的動畫都能用transform完成,不需要使用CSS2的屬性,如一個框從右到左彈出。
例如把一個框,從下到上彈出,可以用jQuery的slideUp函數,或者自己寫setInterval函數處理,但是這些沒有比用CSS來得好。使用CSS,初始狀態可以把框translate移動屏幕外,然后點擊的時候加上一個類,這個類的transform值為0,然后再用transition做動畫就好了。
英文的單詞或者數字如果當前行排不下會自動切到下一行,這樣就導致每行長短不一,有時候可能不太美觀,但是不能使用word-break: break-all把一個單詞拆成兩行,還有一種是使用:
hyphens: auto;
它會把單詞拆成用-連接的形式,看起來好像挺合理,但是由于它斷詞斷得不夠徹底,有些單詞斷不了,長短不一的現象看起來也比較明顯,有些單詞還被拆成了兩行,所以還不如不加。
因此,不要使用斷詞。
這個和上面提到的font-family設置是一樣的,不要在代碼里面手動設置font-family,如下:
.icon-up:before{ content: "\e950"; font-family: "icon-font"; }
正確的做法是給.icon-up的元素再套一個.icon的類,font-family等對圖標字體的相關設置都統一在這個類里面:
.icon{ font-family: "icon-font"; /* Better Font Rendering =========== */ -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }
因為我們可能會添加其它一些設置,有個.icon的類統一處理比較好。就不要手動一個個去設置font-family了。
由于每個瀏覽器都有自己的UA樣式,并且這些樣式還不太統一,所以需要做樣式reset,常見的reset有以下:
/* IE瀏覽器對輸入控件有自己的font-family,需要統一 */ input, textarea, button{ font-family: inherit; } /* Chrome瀏覽器會在輸入控制聚集的時候添加一個藍色的outline*/ input:focus, textarea:focus, select:focus{ outline: none; } /* 去掉textarea的可拉大小功能*/ textarea{ resize: none; } /* IOS Safari在橫屏的時候會放大字體,第二個屬性讓滑動更流暢 */ html{ -webkit-text-size-adjust: 100%; -webkit-overflow-scrolling : touch; } /* 統一標簽的margin值和p標簽的line-height*/ body, p, h2, h3, ul, ol, figure, li{ padding: 0; margin: 0; } h2, h3, p{ line-height: 150%; } /* 去掉select的默認樣式 */ select{ -webkit-appearance: none; } /* 如果有輸入內容IE會給輸入框右邊加一個大大的X */ input::-ms-clear{ display: none; width: 0; height: 0; } /* 去掉number輸入框右邊點擊上下的小三角 */ input::-webkit-inner-spin-button{ -webkit-appearance: none; } input::-webki-outer-spin-button{ -webki-appearance: none; }
不管是UI直接給的圖片還是自己從UI圖里切出來的圖片,都需要把圖片壓縮一下,建議使用 tinypng ,它可以在保持圖片質量減少較低的情況下,把圖片壓得很厲害,比直接在PS里面設置壓縮質量要強。如果是色彩比較豐富的圖片要使用jpg格式,不能使用png格式,png會大得多,如果是logo那種矢量圖片,直接使用svg格式即可。一般來說要把圖片控制在300k以內,特別是banner頭圖,圖片的大小也要控制住。
顯示一張圖片有兩種方式,可以通過設置CSS的background-image,或者是使用img標簽,究竟什么時候用哪種呢?
如果是頭圖等直接展示的圖片還是要img標簽,如果是做為背景圖就使用background,因為使用img可以寫個alt屬性增強SEO,而背景圖那種本身不需要SEO。雖然background有一個一個background-position: center center很好,但是頭圖那種還是使用img吧,自己去居中吧,不然做不了SEO。
響應式開發最不好不要雜合使用rem,文字字號要么全部使用rem,要么不要用,也不要使用transform: scale去縮小,因為被縮小的字號看起來會有點奇怪,別人都是14px,而你變成了13.231px,小了一點。響應式的原則一般是保持中間或者兩邊間距不變,然后縮小主體內容的寬度。
:before和:after可以用來畫頁面的一些視覺上的輔助性元素,如三角形、短的分隔線、短豎線等,可以減少頁面上沒有用的標簽。但是頁面上正常的文本等元素還是不要用before/after畫了。
首先absolute定位的元素渲染性能會比較高,因為它獨立出來了,計算量會少,用得好還是可以的。但是如果你頁面的主要布局是使用absolute的那肯定是不可取的,因為absolute定位的可擴展性很差,你把每個元素的位置都定死了就變不了了,可以多用float,雖然float的性能相對較差,但是不管是實用性還是兼容性都是挺好的。
有些人喜歡用inline-block,特別是剛開始學切圖的人,因為block會換行,而inline-block不會換行還具有盒模型,因此inline-block用得很順手,而float比較復雜,還要處理清除浮動之類的問題。如下布局:
應該寫li,然后讓li float,如果你讓li display:inline-block也可以達到目的。但是inline-block用得多了可能會有一些奇怪的問題,你通常要在一個inline-block的元素里面套block的元素,inline-block是行內元素,而block是塊級元素,這兩者終究有點差別。這種應該用float/flex會更自然,如果你float用順手了你會發現比inline-block好多了,并且更加專業。如果你沒怎么用過flex ,那你可以嘗試換一下使用flex,如果你沒怎么用過float,可以嘗試用一下。只有你的切圖方式多樣化了,你切起圖來才能比較靈活。
一般來說,UI給的圖片展示寬高是固定的,但是實際的圖片長寬是不固定,大部分圖片是長是比寬大,小部分圖片是寬比長大。所以需要居中裁剪展示,如下圖所示:
中間黑色的框是展示區域,圖片的短邊和窗器的邊一樣大,另一邊按圖片的原始比例拉伸,然后居中顯示。這個得借助JS,因為圖片未加載好之前,不知道是長邊比較大還是寬比較大。如下代碼:
<div> <img src="test.jpg" alt onload="resizeImg(this, '400px', '300px'"> </div>
借助一個resizeImg函數,在onload函數里面做處理。然后居中用CSS:
.img-container{ position: relative; width: 400px; height: 300px; } .img-container img{ position: absolute; left: -9999px; right: -9999px; top: -9999px; bottom: -9999px; margin: auto; }
上面代碼用了一個margin: auto做居中。
移動端的的一些圖標如X,可能會設計得比較小,所以點起來會不太好點,因此要提高可點區域范圍,可通過增加padding,如下代碼:
.icon-close{ position: abosulte; right: 0; top: 0; padding: 20px; }
這樣區域就增加了一圈,點起來就容易多了。
如果設置input的line-height,如下代碼,你可能要做垂直居中:
.request-demo input{ height: 40px; line-height: 40px; }
設置了line-height為一個很高的值,這樣會導致Safari瀏覽器的輸入光標|變得巨大,所以如果你要居中的話,使用padding吧。
因為IOS Safari切換輸入框的時候會頁面會彈閃得很厲害,因為你在切的時候它會先把鍵盤收起來,然后再彈出來,這個時間很短,給人感覺頁面彈閃了一下,但如果把body禁止滑動了就不會有這個問題,這有兩個解決辦法,第一種是把body fixed住,第二種設置body overflow: hidden,相對來說第二種比較簡單一點。IOS10完全不會閃,IOS9以下還是會閃。
有時候UI里面會有一些漸變的效果,無法復制CSS出來,這個時候可以用一個在線的工具,生成漸變的CSS: www.cssmatic.com/gradient-ge… ,但是這個需要自己手動調一個和UI一模一樣的效果,或者可以直接給UI調一個它理想的效果,它會生成兼容性很強的CSS:
background: #fff; background: -moz-linear-gradient(left, #fff 0%, #d2d2d2 43%, #d1d1d1 58%, #fefefe 100%); background: -webkit-gradient(left top, right top, color-stop(0%, #fff), color-stop(43%, #d2d2d2), color-stop(58%, #d1d1d1), color-stop(100%, #fefefe)); background: -webkit-linear-gradient(left, #fff 0%, #d2d2d2 43%, #d1d1d1 58%, #fefefe 100%); background: -o-linear-gradient(left, #fff 0%, #d2d2d2 43%, #d1d1d1 58%, #fefefe 100%); background: -ms-linear-gradient(left, #fff 0%, #d2d2d2 43%, #d1d1d1 58%, #fefefe 100%); background: linear-gradient(to right, #fff 0%, #d2d2d2 43%, #d1d1d1 58%, #fefefe 100%); filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#fff', endColorstr='#fefefe', GradientType=1 );
如下有些人為了把span撐開,設置span display: inline-block:
span.phone-numer{ display: inline-block; margin-left: 10px; }
其實行內元素可直接margin的左右,能夠把它撐開,不需要設置inline-block:
span.phone-numer{ margin-left: 10px; }
另外需要注意的是img/input/textarea/button默認就是inline-block,也不用再設置。
《代碼大全》這本書里面有一章是專門講變量命名的,這里結合這本書的建議做說明。總地來說,變量名要準確完整地描述該變量所表述的事物,具體來說:
如以下好的變量名和不好的變量名:
| 不好的變量名 | 好的變量名 |
| inp | input, priceInput |
| day1, day2, param1 | today, tomorrow |
| id | userId, orderId |
| obj | orderData, houseInfos |
| tId | removeMsgTimerId |
| handler | submitHandler, searchHandler |
左邊的變量名都不太清楚,代碼的擴展性不好,一旦代碼需要加功能的話,就容易出現obj1、obj2、obj3這種很抽象的命名方式。所以一開始就要把變量的名字起得真實有意義,不要搞一些很短很通用的名字。
當然變量名取得太長也不好,如maximumNumberOfTeamMembers.
變量名應直指問題領域,來源于現實世界,而不是計算機世界,例如取了texareaData之類的名字,應該取一個和業務相關的名字,如leaveMsg.
如up/down、begin/end、opened/closed、visible/invisible、scource/target,對仗明確可以讓人很清楚地知道兩個變量的意義和用途。
有些喜歡取temp和obj之類的變量,如果這種臨時變量在兩行代碼內就用完了,接下來的代碼就不會再用了,還是可以接受的,如交換數組的兩個元素。但是有些人取了個temp,接下來十幾行代碼都用到了這個temp,這個就讓人很困惑了。所以應該盡量少用temp類的變量,如下代碼:
var temp = 10; var leftPosition = currentPosition + temp, topPosition = currentPosition - temp;
應改成:
var adjustSpace = 10; var leftPosition = currentPosition + adjustSpace, topPosition = currentPosition - adjustSpace;
《代碼大全》這本書建議布爾變量不用以is/do之類的開頭,如:
var isMobile = true, isError = true, doUpdate = false;
可改成:
var mobile = true, error = true, updated = false;
還有其它一些常用的名稱如done/found/successs/ok/available/complete等,結合具體的語境:
var ajaxDone = true, fileFound = false, resourceUpdated = true;
另外變量名不要使用否定的名詞,如notOk,notReady,因為否定的詞取反的時候就會比較奇怪,如if(!notOk). 要使用肯定的布爾變量名。如果是參數的話可結合ES6的默認形參值。
(6****)變量名使用正確的語法
不要使用中文拼音,如shijianchuo應改成timestamp,如果是復數的話加s,或者加上List,如orderList、menuItems,而過去式的加上ed,如updated/found等,如果正在進行的加上ing,如calling.
如下聲明三個變量:
var registerForm, question, calculateResult;
以上絕對是合法JS語法,但是這三個變量的用途會讓人比較困惑,特別是中間第二個question,問題是什么。但是當你把上面的變量賦一個初始值的時候:
var registerForm = null, question = "", calculateResult = 0;
就讓人豁然開朗了,原來question是一個問題的字符串,而result是一個數字,form是一個對象。這也有利于JS解釋器提前做一些優化處理,不用等到使用的時候才知道這些變量是什么類型的。
如下代碼:
function calculatePrice(seatCount){ if (seatCount <= 0) { return ""; } else { return seatCount * 79; } }
這個代碼可能返回整型,也有可能返回字符串,就會讓人比較困惑,同時從代碼性能來說也是不高的,雖然它是合法的JS語法,一個函數的返回類型要統一。你可能會說我用上面的函數做為輸入框顯示的值,如果是負數或者0,那么輸入框就不要顯示任何東西,所以才會返回空的字符串。即使是這樣的原因也不建議這樣寫,從長遠來看這樣寫是不利的,你應該用其它的方法組織你的代碼。要養成強類型的代碼風格,這樣不容易出bug,擴展也容易。另外如果一個變量你把它當成數字使用,下面就不要再把它當成字符串使用了,因為這樣也容易讓人困惑。微軟的Typescript就是一種強類型的書寫語法,很多大型項目會使用typescript寫JS,有興趣的可以繼續了解怎么寫typescript.
undefined表示一個變量未定義,你定義了一個變量又說它未定義本身就很奇怪。這可能會造成的問題是使用上的歧義,因為我們經常使用undefined來判斷變量有沒有定義:
if (typeof window.HTMLVideoElement === "undefined")復制代碼
如果要賦值應該要賦空值,如對象賦值為null,數字賦值為0,字符串賦值為空字符,那你可能會說0也是一個正常的數字,如果賦值為0會導致我誤認為它是一個正常的數據,那怎么辦呢?如果你的數字都是非負數,那么可以把初始值置為-1,實在不行就置成NaN.
函數的返回值也不要顯式地return undefined.
一個比較流行的空格和縮進排版如下代碼所示:
//逗號后面帶個空格,) {中間帶個空格 function getSeatDiscount(seatCount, currentPrice) { //雙目運算符左右兩邊都帶個空格 var originPrice = editOrder.getSeatsPrice(seatCount); return Math.round((originPrice - currentPrice) / originPrice * 100); }
一行太長要換行,如V8的源碼里面一行最長是70個字符,超過就換行:
function ArrayUnshift(arg1) { // length == 1 //if判斷里面進行了換行,并且if (中間帶個空格 if (len > 0 && UseSparseVariant(array, len, IS_ARRAY(array), len) && !%object_is_sealed(array)) { SparseMove(array, 0, 0, len, num_arguments); } else { SimpleMove(array, 0, 0, len, num_arguments); } }
一行代碼太長了就換行是一種好的習慣,太長讓人看起來比較費勁。基本上一行不要超過100個字符,超過就要換行,不管是注釋還是代碼。
==會帶上類型轉換,這和上面一樣的,我們要用強類型的風格寫代碼,所以不要使用==,如果有類型轉換自己做類型轉換,不要讓別人去猜這里面有類型轉換,使用==會有一些比較奇怪的結果:
null == undefined //true '' == '0' //false 0 == '' //true 0 == '0' //true ' \t\r\n ' == 0 //true new String("abc") == "abc" //true new Boolean(true) == true //true true == 1 //true
對一些比較重要的常量起一個名字,例如下面的代碼:
const ONE_DATE = 3600 * 24 * 1000; var tomorrow = today + ONE_DATE;
再如下面不好的寫法:
dialogHandler.showQuestionNaire("seller", "sell", 5, true);
上面四個常量會讓人看起來比較困惑,如果可以的話給它們起個名字,如果覺得麻煩那就加上注釋。
一個原因是在全局作用域下,變量的查找時間會更長,第二個原因是污染全局作用域,有時候會造成一些意想不到的結果,如下:
var name = "hi boy"; console.log(window.name);
定義了一個變量,但是剛好不巧window.name是本來有這個屬性,這個屬性通常用來跨域傳遞數據。如果你設置了name這個變量,就把全局的window.name給覆蓋了。
ES6新增了let/const定義變量,使用let有一些好處,如:
let me = "go"; // Uncaught SyntaxError: Identifier 'me' has already been declared let me = "go";
使用babel loader打包的時候它會做靜態檢查:
Module build failed: Duplicate declaration "me"
for(let i = 0; i <= 4; i++) { tasks.push(function(){ console.log("i is " + i); }); }
使用let使得i在for循環里面每次運行的作用域都是獨立的。并且for里定義的變量在for循環外是不可見的。
babel在轉換的時候,會在for循環里面套一個function,然后把i當作函數的參數:
var _loop = function _loop(_i) { tasks.push(function () { console.log("i is " + _i); }); }; for (var _i = 0; _i <= 4; _i++) { _loop(_i); }
由于let可以避免變量重復定義,就沖著這一點,就使得它很有意義。所以推薦多用****let****定義變量。所以本規范下面的變量將使用let代替var.
而const適合于給常量起個名字,如上面提到的:
const ONE_DAY = 3600 * 24 * 1000; const adjustSpace = 10;
或者是定義其它一些不需要修改的變量,防止不小心被其它代碼修改了。
可以寫一行就不要寫三行,如下:
let seatDiscount = 100; if(seat < 5) { seatDiscount = 90; } else if(seat < 10) { seatDiscount = 80; } else { seatDiscount = 70; }
可以改成三目運算符:
let seatDiscount = seat < 5 ? 90 : seat < 10 ? 80 : 70;
代碼從8行減少到了2行。
例如以下代碼:
setTimeout(function(){ window.location.reload(true); }, 2000);
可改成:
setTimeout(() => window.location.reload(true), 2000);復制代碼
代碼從3行變成了1行。
對于一般的頁面的數據量來說,加減乘除等計算不足以造成性能瓶頸。容易造成瓶頸的是DOM操作,特別是大批量的DOM操作,只要一次有幾百上千的級別就容易造成頁面卡頓。特別是不要在一個for循環里不斷地修改DOM,如下代碼:
for(var i = 0; i < 1000; i++) { ul.appendChild(li); }
這種可以先把li拼好了,再一次性append到ul里面,如下代碼:
var fragment = document.createDocumentFragment(); for(var i = 0; i < 1000; i++) { fragment.appendChild(li); } ul.appendChild(fragment);
如果你用jq的話應該先把模板渲染好,然后再一次性append到dom里面,而不是不斷地append到dom里面。現在的瀏覽器一般也比較智能,它會做一些優化,但是我們不能老是指望瀏覽器會優化。
但是還是要注意數據量特別大的情況,你可能要使用setTimeout的方式分段處理數據,甚至使用多線程。使用setTimeout可以這樣:
function sliceWorks(data, finishedCallback) { if(!data.length) { finishedCallback(); } else { const PIECES = 100; process(data.splice(0, PIECES)); setTimeout(() => sliceWorks(data, finishedCallback), 100); } }
我們使用一個遞歸,把數據分段處理,每段100個,當數據處理完再調完成回調函數。
這個和CSS規范類似:
/* * @file listing-detail.js * @description 房源詳情頁的JS主文件,處理輪播、房貸計算器、約看房等邏輯 * @author yincheng.li * @update (yincheng.li 2017/8/19) */
/* * 和搜索界面展示有關的處理邏輯 * @namespace */ var searchWinHandler = { /* * 初始化驅動函數 * * @param {bool} realTimeSearch 是否需要進行實時搜索 * @param {HTMLFormElement} form 搜索表單DOM元素 * */ init(realTimeSearch, HTMLFormElement){ } /* * 搜索條件展示點擊X按鈕的處理函數 * * @param {object} jquery的點擊事件event * @trigger 會觸發search按鈕的點擊事件,以觸發搜索 * @returns 無返回 * * TODO 這里臨時使用了一個全局變量的flag,這種實現方式不太好 * 雖然比較方便 */ closeFilterSpan(event){ } };
上面的@auhor @return都是注釋標簽,其它常用的注釋標簽還有:
/* @class 表示一個類 @constructor 構造函數 @deprecated 被棄用 @global 全局的變量 @namespace 具有命名空間作用的object,如$.fn.remove,$.fn.append,$和fn就是一個namespace,而fn是$的子命名空間 @this 這里的this指向哪里 @throws 在這個函數里面可能會拋出什么異常 @version 當前版本 */
對一些比較重要的變量加注釋,標明它是什么用途,以及對一些核心代碼邏輯加上注釋,或者比較復雜的業務邏輯,寫了5個case,每個case分別代表什么;為了改某個bug而加入的代碼,說明下為了解決什么問題;還有某些易混的判斷,為什么if判斷條件寫了四個,為什么代碼到這個if判斷不通過就直接return了;一些常量的注釋,為什么會突然冒出來100這個數字;改動了別人的代碼,為什么要改動;等等。如:
var requestData = { listingId: listingData.listingId, page: 1, //把200改成5,點擊More的時候是重新刷新頁面的,也沒有其他地方用到, //沒必要請求那么多,嚴重影響性能 pageSize: 5//200 };
總之多寫注釋還是好的,只要不是廢話:
//定義了一個number的變量 let number = 5;
或者是和邏輯不符合的錯誤注釋。
還有一種排版的注釋,右括號的對應關系:
} //function ajax } //switch(b) } //if(a) } //searchHandler
主要是為了方便在后面加代碼,例如我要在switch(b)后面加代碼,當我看到這個注釋我就很清楚地知道需要在哪里按回車。不過一般不推薦嵌套很深的代碼,或者寫得很長,一個函數幾百行。
有些人的代碼經常會套個七八層,以jq代碼為例,如下:
var orderHandler = { bindEvent: function(){ $(".update-order").on("click", function(){ if(orderStatus === "active"){ ajax({ url: "/update-order", success: function(data){ for(let i = 0; i < data.orders.length; i++){ dom.append(); } } }); } else { ajax({ url: "/create-order", success: function(data){ } }); } }); } };
上面的代碼最深的一層縮進了八層,你可能會覺得這樣邏輯挺清晰的啊,但是這種寫法同時也有點面條式。以上代碼如果讓我寫,我會這么組織:
var orderHandler = { sendUpdateOrderReq: function(requestUrl, successCallback){ ajax({ url: requestUrl, success: successCallback; }); }, updateOrder: function(event){ let requestUrl = orderStatus === "active" ? "/update-order" : "create-order"; //更新訂單回調函數 let activeUpdateCallback = function(data){ for(var i = 0; i < data.orders.length; i++){ console.log(data.orders[i].id); } }; //創建訂單回調函數 let inactiveUpdateCallback = function(data){ }; let successCallback = { active: activeUpdateCallback, inactive: inactiveUpdateCallback }; //發請求處理訂單 searchHandler.sendUpdateOrderReq(requestUrl, successCallback[orderStatus]); }, bindEvent: function(){ $(".update-order").on("click", searchHandler.updateOrder); } };
首先把綁定的匿名函數改成有名的函數,這樣有個好處,當你想要off掉的時候隨時可off掉,然后可以減少一層縮進,接著把根據orderStatus不同的回調先用變量判斷好,而不是同時積壓到后面再一起處理。再把發送請求的函數再單獨抽出來做為一個函數,這樣可以減少兩層縮進。上面最深的縮進為4層,減少了一半。并且你會發現這樣寫代碼邏輯會更加清晰,我在bindEvent里面掃一眼就可以知道哪些DOM綁了哪些事件,然后我對如對哪個DOM的事件感興趣再跳到相應的回調函數去看,而不用拉了一兩頁才在bindEvent里面找到目標DOM。并且把updateOrder單獨做為一個獨立的函數,其它地方如果需要也可以使用,例如可能還有一個組合功能的操作可能會用到。另外把ajax再做一層抽象主要是這個東西實在是太常用,讓人一眼就知道要干嘛,把它分離到另外一個地方可以讓具體的業務代碼更加簡單,例如上面發請求,我把回調函數準備好之后,只要執行一行代碼就好了。
你縮進太多層,一行就被空格占掉了三、四十個字符,感觀上就不是很好,還會出現上面提到的,最后面要寫好多個右括號收尾的情況,并且一個函數動不動就兩、三百行。
如果你使用了jQuery。
盡量不要使用parent去獲取DOM元素,如下代碼:
var $activeRows = $this.parent().parent().children(".active");復制代碼
這樣的代碼擴展性不好,一旦DOM結構發生改變,這里的邏輯分分鐘會掛,如某天你可能會套了個div用來清除浮動,但是沒想到導致有個按鈕點不了了就坑爹了。
應該用closest,如:
var $activeRows = $this.closest(".order-list").find(".active");復制代碼
直接定位和目標元素的最近共同祖先節點,然后find一下目標元素就好了,這樣就不會出現上面的問題,只要容器的類沒有變。如果你需要處理非自己的相鄰元素,可以這么搞:
$this.closest("li").siblings("li.active").removeClass("active"); $this.addClass("active");
有時候你可以先把所有的li都置成某個類,然后再把自己改回去也是可取的,因為瀏覽器會進行優化,不會一見到DOM操作就立刻執行,會先排成一個隊列,然后再一起處理,所以實際的DOM操作對自己先加一個類然后再去掉的正負相抵操作很可能是不會執行的。
如下代碼:
$(".page ul").addClass("shown"); $(".page .page-number").text(number); $(".page .page-next").removeClass("active"); 復制代碼
上面的代碼做了三個全局查找,其實可以優化一下:
var $page = $(".page"); $page.find("ul").addClass("shown"); $page.find(".page-number").text(number); $page.find(".page-next").removeClass("active");
先做一個全局查找,后續的查DOM都縮小到page的節點只有幾十個,在幾個里面找就比在document幾百幾千個節點里面查找要快多了。jQuery的查DOM也是用的querySelectorAll,這個函數除了用在document之外,可用在其它DOM結點。
有些人喜歡在綁事件之前先off掉,這樣感覺可以確保萬無一失,但是如果你綁的事件是匿名的,你很可能會把其它JS文件綁的一起off掉了,并且這樣不容易暴露問題,有時候你的問題可能是重復綁定事件,如點一次按鈕就綁一次就導致了綁多次,所以根本原因在這里。你應該要確保事件只被綁一次,而不是確保每次寫之前都先off掉。如果你的事件容易出現綁多次的情況說明你的代碼組織有問題,這個在開發的時候應該是能夠暴露出來的。
例如說一個表單只有幾個input元素,然后你給input加了個委托到form上面,甚至有時候是body上面,由于事件冒泡導致在form上或者在頁面上的所有操作都會冒泡到form/body上,即使操作的不是目標元素,這樣jQuery就會收到在body上的事件,然后再判斷處理所有的操作的目標元素是不是你指定的那個,如果是再觸發你綁的回調函數。特別是像mousemove觸發得頻繁的事件都需要執行。所以如果元素比較少或者不需要動態增刪的那種就不要使用冒泡了,直接綁在對應的多個元素就好了。
例如獲取表單的input的和它的value:
let email = form.email.value.trim();
如果form里面有一個input[name=email]的輸入框,就可以這么用。
再如,改變一個button的狀態,下面兩個其實差不多,但是如果獲取不到dom元素的話第一個會掛:
$("#update-order")[0].disabled = true; $("#update-order").prop("disabled", true);
設置一個元素的display為block:
div.style.display = "block";復制代碼
但是絕大多數的情況下還是要使用jq的API以確保兼容性,如下獲取scrollTop:
//在Firefox永遠返回0 let _scrollTop = document.body.scrollTop(); //正確方法 let scrollTop = $(window).scrollTop();
因為在firefox里面需要使用:
document.documentElement.scrollTop
而這個在Chrome永遠返回0。再如window.innerWidth在某些低版本的安卓手機會有問題。所以當你不確定兼容性的時候,就不要使用原生API,不然你得經過小心驗證后再使用。你可以不用,但不是說不要去了解原生API,多去了解原生DOM操作還是挺有幫助的。
如下代碼,頻繁地使用了window.location這個屬性:
let webLink = window.location.protocol + window.location.hostname; if(openType === "needtoTouch"){ webLink += "/admin/lead/list/page" + window.location.search.replace(/openType=needToTouch(&?)/, "") + window.location.hash; }
可以先把它緩存一下,加快變量作用域查找:
let location = window.location; let webLink = location.protocol + location.hostname; if(openType === "needtoTouch"){ webLink += "/admin/lead/list/page" + location.search.replace(/openType=needToTouch(&?)/, "") + location.hash; }
當把location變成一個局部變量之后,它的查找時間將明顯快于全局變量。你可能會說就算再快這點時間對于用戶來說還是沒有區別的吧,但是這是做為一名程序員的追求,以及可以讓代碼更簡潔。
如下代碼,如果是非選中狀態就把顏色置灰:
$menuItem.css("color", "#ccc");復制代碼
反之顏色恢復正常:
$menuItem.css("color", "#000");
這樣的代碼有問題,如果以后顏色改了,那么你需要改兩個地方,一個是CSS里設置,另一個是JS里面設置,而JS寫的樣式特別容易被忽略,查起來也不好定位。好的做法應該是通過添加刪除類的方法:
//變成選中態 $menuItem.addClass("selected"); //變成非選中態 $menuItem.removeClass("selected");
然后再通過CSS給selected的類添加樣式。如果是button之類的控件可以結合:disabled、:checked、:valid等偽類,連類都不用添加
但是有一種是一定要用JS控制的,就是需要先計算然后動態地改變position或者transform的值,如果用CSS3的transition實現不了.
添加非空判斷可以提高代碼的穩健性,如下代碼:
//彈框時顯示other monthly charge showOtherMonthlyCharge: function(otherCharges, $dialog){ if(!otherCharges || !otherCharges.length){ return; } }
如果傳的為空就不用處理,有時候你可能要拋個異常,告訴調用者。對一些比較重要的地方可能還要添加類型檢驗。后端傳的數據要確保會有那個屬性,如果不確定也要添加非空判斷。如果調了第三方的API,添加出錯處理也很重要,因為你不能確保第三方API一定能正常工作,在一些你覺得可能會掛的地方做處理,如請求可能會超時,或者返回了undefined的異常結果,這種多使用一般能夠發現。
如下代碼:
let a = [9, 3, 5]; for(let i in a){ console.log(a[i]) }
正常情況下將會輸出數組的元素,但是很不幸的是,如果有人給數組原型添加了一個函數:
Array.prototype.add = function(){};
循環里的i將會有4個值:0, 1, 2, "add",這樣就導致你的遍歷出現問題,所以數組遍歷應該使用length屬性或者數組的forEach/map方法。
JS里面的表達式是可以不用分號結尾,例如Zepto的源碼幾乎沒看到一個分號,但是我們還是提倡要每個句子后面都要加上分號,這樣不容易出錯。
對于那些根據用戶輸入內容做跳轉,需要先把用戶內容做轉義,如下有問題的代碼:
let searchContent = form.search.value.trim(); window.location.href = `/search?key=${searchContent}`;
如果用戶輸入了一個#號如門牌號,將會導致#后面的內容當作錨點了,或者用戶可能會輸入一個空格。所以如果不確定內容的東西需要先encode一下,如下代碼:
let searchContent = encodeURIComponent(form.search.value.trim()); window.location.href = `/search?key=${searchContent}`;
這樣跳轉就沒有問題了。
點擊一個容器的時候做跳轉,有些人喜歡這么寫:
<div onclick="window.locatioin.href='/listing/detail?id={{listingId}}'"> <img> <div></div> </div>
其實這樣寫不好,不利于SEO,如果是一個跳轉應該用a標簽,如下:
<a href="window.locatioin.href='/listing/detail?id={{listingId}}'"> <img> <div></div> </a>
同時把a標簽變成塊級。就算你不用做SEO,也應當盡量使用這種方式,因為用這種方式比較自然,還可以控制是否要新開頁,如果在移動端也不用考慮click事件是否有延遲的問題。
由于Safari的隱身模式下本地存儲會被禁用,如果你嘗試往localStorage寫數據的話,會報超出使用限制的錯誤:
QuotaExceededError (DOM Exception 22): The quota has been exceeded.
而Chrome的隱身窗口不會禁用。而使用Safari的用戶可能會開隱身窗口,特別是手機上的。這樣就導致代碼拋異常了,所以為了兼容Safari,不能直接使用localStorage,要做個兼容:
Data.hasLocalStorage = true; try{ window.localStorage.trySetData = 1; }catch(e){ Data.hasLocalStorage = false; } setLocalData: function(key, value){ if(Data.hasLocalStorage){ window.localStorage[key] = value; } else{ util.setCookie("_LOCAL_DATA_" + key, value, 1000); } }, getLocalData: function(key){ if(Data.hasLocalStorage){ return window.localStorage[key]; } else{ return util.getCookie("_LOCAL_DATA_" + key); } }
上面代碼做了個兼容,如果不支持localStorage就使用cookie。要注意cookie一個域名最多只能有4kB,50個key,而本地存儲限制為5Mb.
let maxPrice = +form.maxPrice.value;復制代碼
+號相當于Number:
let maxPrice = Number(form.maxPrice.value);復制代碼
parseInt和Number有一個很大的區別是parseInt(“10px”)結果為10,而Number(“10px”)是NaN,parseInt會更加自然,其它編程語言也有類似的轉換。但是Number還是能適用很多的場景。
如果計算某個數字在第幾排:
let _row = Math.floor(index / columns); let row = parseInt(index / columns);
都可改成:
let row = index / columns >> 0;
這個用位運算的效率會明顯高于上面兩個。
如下代碼:
let mobile = !!ua.match(/iPhone|iPad|Android|iPod|Windows Phone/)
有幾個值在if判斷里面都返回false:0、false、””、undefined、null、NaN都是false,所以判斷一個數組有沒有元素可以這么寫:
if (array.length) {}
而不用寫成:
if (array.length !== 0) {}
判斷一個字符串是不是空可以寫成:
if (str) {}
但是判斷一個變量有沒有定義還是要寫成:
if (typeof foo !== “undefined”) {}
因為如果直接if變量的話,上面的幾個可能取值都將認為是沒定義。
如下代碼,在發請求之前,經常需要獲取表單的值,然后去修改和添加老數據提交:
var orderData = { id: 123, price: 500 } orderData.price = 600; orderData.discount = 15; orderData.manageFee = 100;
其實有一種更優雅的方式那就是使用Object.assign:
var setOrderData = { price: 600, discount: 15, manageFee: 100 } Object.assgin(orderData, setOrderData);
使用這個的好處是可以弄一個setOrderData的Object,寫成大括號的形式,而不用一個個去賦值,寫起來和看起來都比較累。最后再assign一下賦值給原先的Object就可以了。
調試完就把console.log之類的打印信息去掉,別想著等一下做完了再刪,等一下就忘了。另外,不要使用alert調試,console/debugger上線了都沒事,一般用戶也不會開一個控制臺,但是alert上線了就完蛋了,特別是有些人喜歡用alert(“fuck”)之類的看下代碼有沒有運行到這里,這種調試技巧還是比較初級,要是真上線了可能得卷鋪蓋走人了。這也可以通過代碼檢查工具做靜態檢查。
如下代碼:
let searchHandler = { search() { console.log(this); this.ajax(); }, ajax() { } }; $searchBtn.on("click", searchHandler.search);
當觸發searchBtn的點擊事件時,search函數里的this已經指向 searchBtn了,因為它是click的回調函數:
searchHandler.search.call(btn, event);復制代碼
所以函數運行環境就變成了btn了,因此這種單例的Object最好不要使用this,應直接使用當前命名空間的變量名:
let searchHandler = { search() { console.log(this); searchHandler.ajax(); }, ajax() { } }; $searchBtn.on("click", searchHandler.search);
這樣就沒問題了。
正則表達式可以很方便地處理字符串,通常只要一行代碼就搞定了。例如去掉全局的某一個字符,如去掉電話號碼的-連接符:
phoneNumer = phoneNumber.replace(/\-/g, “”);
或者反過來,把電話號碼改成3-3-4的形式:
phoneNumber = phoneNumber.replace(/^(\d{3})(\d{3})(\d{4})$/, “$1-$2-$3”);復制代碼
熟練掌握正則表達式是每個前端的基本技能。
當你一個函數要寫得很長的時候,例如兩、三百行,這個時候你考慮把這個大函數給拆了,拆成幾個小函數,然后讓主函數的邏輯變得清晰簡潔,而每個小函數的功能單一獨立,使用者只需要管輸入輸出,而不需要關心內部是怎么運行的。如下在地圖里面處理用戶點擊的處理函數:
handleMouseClick(latLng, ajax = true) { var path = this.path; // 這里調了一個closeToFirstPoint的函數判斷點擊位置是否接近第一個點 if(path.length >= 2 && ajax && this.closeToFirstPoint(latLng)){ // 如果是的話調closePath關閉路徑 this.closePath(ajax); return; } path.push({lat: latLng.lat(), lng: latLng.lng()}); // 調畫點的函數 this.drawPoint(latLng); // 調畫線的函數 this.drawSolidLine(); // 調畫多邊形背景的函數 this.drawPolygonMask(); this.lastMoveLine && this.lastMoveLine.setMap(null); this.$drawTip.hide(); }
上面拆成了很多個小函數,如畫點的drawPoint函數,使用這個函數只需要關心給它一個當前點的經緯度就可以了,它就幫你畫一個點。
在函數之上又可以繼續抽象,如把這個畫圖功能的模塊寫成一個DrawTool的類,這個類負責整個畫圖的功能,使用者只需要實例化一個對象,然后調一下init,傳一些參數就好了。
先抽成不同的函數,每個函數負責一小塊,相似的函數聚集在一起形成一個模塊,幾個模塊的相互調用又形成一個插件。
如果label里面有input,監聽label的事件會觸發兩次,如下代碼:
<form id="choose-fruit"> <label> <input type="radio" name="fruit" value="apple"> <span>apple</span> </label> <label> <input type="radio" name="fruit" value="pear"> <span>pear</span> </label> <form> <script> { let $form = $("#choose-fruit"); $form.find("label").on("click", function(event){ console.log(event.target); }); } </script>
當點到span的時候,click事件會觸發兩次,如果label里面沒有input的話,就只會觸發一次。這是為什么呢?因為在label容器內,點到span文字的時候會下發一次click事件給input,input事件又會冒泡到label,因此label會觸發兩次。因此如果你直接監聽label事件要注意注意觸發兩次的情況。
感謝各位的閱讀,以上就是“HTML/CSS/JS編碼規范有哪些”的內容了,經過本文的學習后,相信大家對HTML/CSS/JS編碼規范有哪些這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。