HTML/CSS/JS編碼規範

人人網FED發表於2017-08-24

最近整理了一份HTML/CSS/JS編碼規範,供大家參考。
目錄:
一、HTML編碼規範
二、CSS編碼規範
三、JS編碼規範

一、HTML編碼規範

1. img標籤要寫alt屬性

根據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.

2. 單標籤不要寫閉合標籤

為什麼?因為寫了也沒用,還顯得你不懂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.

3. 自定義屬性要以data-開頭

自己新增的非標準的屬性要以data-開頭,否則w3c validator會認為是不規範的,如下不好的寫法:

<div count="5"></div>複製程式碼

應改成:

<div data-count="5"></div>複製程式碼

4. td要在tr裡面,li要在ul/ol裡面

如下不好的寫法:

<div>
    <li></li>
    <li></li>
</div>
複製程式碼

更常見的是td沒有寫在tr裡面:

<table>
    <td></td>
    <td></td>
</table>
複製程式碼

如果你寫得不規範,有些瀏覽器會幫你矯正,但是有些可能就沒有那麼幸運。因為標準並沒有說如果寫得不規範應該怎麼處理,各家瀏覽器可能有自己的處理方式。

5. ul/ol的直接子元素只能是li

有時候可能會直接在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那就亂了。

6. section裡面要有標題標籤

如果你用了section/aside/article/nav這種標籤的話,需要在裡面寫一個h1/h2/h3之類的標題標籤,因為這四個標籤可以劃分章節,它們都是獨立的章節,需要有標題,如果UI裡面根本就沒有標題呢?那你可以寫一個隱藏的標題標籤,如果出於SEO的目的,你不能直接display: none,而要用一些特殊的處理方式,如下套一個hidden-text的類:

<style>.hidden-text{position: absolute; left: -9999px; right: -9999px}</style>
<section>
    <h1 class="hidden-text">Listing Detail</h1>
</section>
複製程式碼

7. 使用section標籤增強SEO

使用section的好處是可以劃分章節,如下程式碼:

<body>
<h1>Listing Detail</h1>
<section>
    <h1>House Infomation</h1>
    <section>
       <h1>LOCATION</h1>
       <p></p>
    </section>
    <section>
        <h1>BUILDING</h1>
        <p></p>
    </section>
</section>
<section>
    <h1>Listing Picture</h1>
</section>
</body>
複製程式碼

就會被outline成這樣的大綱:

Listing Detail

  1. House Infomation
    1. LOCATION
    2. BUILDING
  2. Listing Picture

可以使用html5 outliner進行實驗,可以看到,我們很任性地使用了多個h1標籤,這個在html4裡面是不合法的。

8. 行內元素裡面不可使用塊級元素

例如下面的寫法是不合法的:

<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>
複製程式碼

這樣就正常了。

9. 每個頁面要寫<!DOCType html>

設定頁面的渲染模式為標準模式,如果忘記寫了會怎麼樣?忘記寫了會變成怪異模式,怪異模式下很多東西渲染會有所不同,怪異模式下input/textarea的預設盒模型會變成border-box,文件高度會變成可視視窗的高度,獲取window的高度時就不是期望的文件高度。還有一個區別,父容器行高在怪異模式下將不會影響子元素,如下程式碼:

<div><img src="test.jpg" style="height:100px"></div>複製程式碼

在標準模式下div下方會留點空白,而在怪異模式下會。這個就提醒我們在寫郵件模板時需要在頂部加上<!DOCType html>,因為在本地開發郵件模板時是寫html片段,沒有這個的話就會變成怪異模式。

10. 要用table佈局寫郵件模板

由於郵件客戶端多種多樣,你不知道使用者是使用什麼看的郵件,有可能是用的網頁郵箱,也有可能用的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 align="center" 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.

11. html要保持簡潔,不要套太多層

需要套很多層的,一般有兩種情況,一種是切圖不太會,需要套很多層來維持排版,第二種是會切圖,但是把UI拆解得太細。像以下佈局:

我會這麼寫:

<ul>
    <li>
        <div class="img-container">
            <img>
        </div>
        <div class="text"></div>
    </li>
</ul>
複製程式碼

因為它是一個列表,所以外面用ul/li作為容器,裡面就兩個div,一個圖片的float: left,另外一個文字的容器,這樣就可以了。不用套很多層,但是有一些是必要的,你寫得太簡單的話,擴充套件性不好,例如它是一個列表那你就應該使用ul/ol,如果你要清除浮動,那你可能需要套一個clearfix的容器。

如果有一塊是整體,它整體向右排版,那麼這一塊也要套一個容器,有時候為了實現一些效果,可能也要多套一個容器,例如讓外面的容器float,而裡面的容器display: table-cell。但是你套這些容器應該都是有價值,如果你只是為了在功能上看起來整齊劃一,區分明顯好像沒太大的意義。

12. 特殊情況下才在html裡面寫script和style

通常來說,在html裡面直接寫script和style是一種不好的實踐,這樣把樣式、結構和邏輯都摻雜在一起了。但是有時候為了避免閃屏的問題,可能會直接在相應的html後面跟上調整的script,這種script看起來有點醜陋,但是很實用,是沒有辦法的辦法。

13. 樣式要寫在head標籤裡

樣式不能寫在body裡,寫在body裡會導致渲染兩次,特別是寫得越靠後,可能會出現閃屏的情況,例如上面的已經渲染好了,突然遇到一個style標籤,導致它要重新渲染,這樣就閃了一下,不管是從碼農的追求還是使用者的體驗,在body裡面寫style終究是一種下策。

同樣地script不要寫在head標籤裡面,會阻礙頁面載入。

而CSS也推薦寫成style標籤直接嵌在頁面上,因為如果搞個外鏈,瀏覽器需要先做域名解析,然後再建立連線,接著才是下載,這一套下來可能已經過了0.5s/1s,甚至2~3秒。而寫在頁面的CSS雖然無法快取,但是本身它也不會很大,再加gzip壓縮,基本上在50k以內。

14. html要加上lang的屬性

如下,如果是英文的網頁,應該這麼寫:

<html lang="en">
<html lang="en-US">複製程式碼

第一種表示它是英文的網頁,第二種表示它是美國英語的網頁,加上這個的好處是有利於SEO和螢幕閱讀器使用者,他可以快速地知道這個網頁是什麼語言的,如果是中文可以這麼寫:

<html lang="zh-CN">複製程式碼

15. 要在head標籤靠前位置寫上charset的meta標籤

如下,一般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瀏覽器。

16. 特殊符號使用html實體

不要直接把Unicode的特殊符號直接拷到html文件裡面,要使用它對應的實體Entity,常用的如下表所示:

符號實體編碼
©&copy;
¥&yen;
®&reg;
>&gt;
<&lt;
&&amp;

特別是像©這種符號,不要從UI裡面直接拷一個unicode的字元過去,如果直接拷過去會比較醜,它取的是用的字型裡面的符號。

17. img空src的問題

有時候可能你需要在寫一個空的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">複製程式碼

第二種可能比較符合規範,但是第一種比較簡單,並且沒有相容性問題。

18. 關於行內元素空格和換行的影響

有時候換行可能會引入空格,如下程式碼:

<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的文字節點,但是不會參與渲染。

要注意的是註釋標籤也是正常的頁面標籤,也會給它建立一個相應的節點,只是它不參與渲染。

19. 類的命名使用小寫字母加中劃線連線

如下使用-連線,不要使用駝峰式:

<div class="hello-world"></div>複製程式碼

20. 不推薦使用自定義標籤

是否可以使用自定義標籤,像angular那樣都是用的自定義標籤,如下程式碼:

<my-cotnainer></my-container>複製程式碼

一般不推薦使用自定義標籤,angular也有開關可以控制是否要使用自定義標籤。雖然使用自定義標籤也是合法的,只要你給他display: block,它就像一個div一樣了,但是不管是從SEO還是規範化的角度,自定義標籤還是有點另類,雖然可能你會覺得它的語義化更好。

21. 重複雜id和重複屬性

我們知道,如果在頁面寫了兩個一模一樣的id,那麼查DOM的時候只會取第一個,同理重複的屬性也會只取第一個,如下:

<input class="books" type="text" name="books" class="valid">複製程式碼

第二個class將會被忽略,className重複了又會怎麼樣?重複的也是無效的,這裡主要是注意如果你直接操作原生className要注意避免className重複,如下程式碼:

var input = form.books;
input.className += " valid";複製程式碼

如果重複執行的話,className將會有重複的valid類。

22. 不推薦使用屬性設定樣式

例如,如果你要設定一個圖片的寬高,可能這麼寫:

<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設定的話它會變成拉伸,變得比較模糊。

23. 使用適合的標籤

標籤使用上不要太單調:

(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)如果是標題就應該使用標題標籤h1/h2/h3,而不是自己寫一個<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標籤。

24. 不要在https的連結裡寫http的圖片

只要https的網頁請求了一張http的圖片,就會導致瀏覽器位址列左邊的小鎖沒有了,一般不要寫死,寫成根據當前域名的協議去載入,用//開頭:

<img src=”//static.chimeroi.com/hello-world.jpg”>複製程式碼

二、CSS編碼規範

1. 檔名規範

檔名建議用小寫字母加中橫線的方式。為什麼呢?因為這樣可讀性比較強,看起來比較清爽,像連結也是用這樣的方式,例如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>複製程式碼

沒有相容性問題。

2. 屬性書寫順序

屬性的書寫順序對於瀏覽器來說沒有區別,除了優先順序覆蓋之外。但是如果順序保持一致的話,掃一眼可以很快地知道這個選擇器有什麼型別的屬性影響了它,所以一般要把比較重要的屬性放前面。比較建議的順序是這樣的:

你可能會覺得我平時差不多就是這麼寫的,那麼說明你有一個比較好的素養。並且我覺得規則不是死,有時候可以靈活,就像你可能會用transform寫居中,然後把left/top/transform挨在一起寫了,我覺得這也是無可厚非的,因為這樣可以讓人一眼看出你要幹嘛。

3. 不要使用樣式特點命名

有些人可能喜歡用樣式的特點命名,例如:

.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 h2{
    font-size: 18px;
}
.house-info h3{
    font-size: 16px;
}
複製程式碼

因為把它的字號加大了,很可能是一個標題,所以為什麼不直接用標題標籤,不能僅僅擔心因為標題標籤會有預設樣式。

類的命名應當使用它所表示的邏輯意義,如signup-success-toast、request-demo、agent-portrait、 company-logo等等。

如果有些樣式你覺得真的特別通用,那可以把它當作一個類,如clearfix,或者有些動畫效果,有幾個地方都要用到,我覺得這種較為複雜並且通用的可以單獨作為一個類。但是還是趨向於使用意義命名。

4. 不要使用hack

有些人在寫CSS的時候使用一些hack的方法註釋,如下:

.agent-name{
    float: left;
    _margin: 10px;
    //padding: 20px;
}
複製程式碼

這種方法的原理是由於//或者_開頭的CSS屬性瀏覽器不認識,於是就被忽略,分號是屬性終止符,從//到分號的內容都被瀏覽器忽略,但是這種註釋是不提倡的,要麼把.css檔案改成.less或者.scss檔案,這樣就可以愉快地用//註釋了。

還有一些專門針對特定瀏覽器的hack,如*開頭的屬性是對IE6的hack。不管怎麼樣都不要使用hack。

5. 選擇器的效能

選擇器一般不要寫超過3個,有些人寫sass或者less喜歡套很多層,如下:

.listings-list{
    ul{
        li{
            .bed-bath{
                p{
                     color: #505050;
                }
            }
        }
    }
}
複製程式碼

一個容器就套一層,一層一層地套下來,最底層套了七八層,這麼長的選擇器的效能比較差,因為Chrome裡面是用遞迴從最後一個選擇器一直匹配到第一個,選擇器越多,匹配的時間就越長,所以時間會比較長,並且程式碼的可讀性也比較差,為看到最裡面的p標籤的樣式是哪個的我得一層層地往上看,看是哪裡的p。程式碼裡面縮排了7、8層看起來也比較累。

一般只要寫兩三個比較重要的選擇器就好了,不用每個容器都寫進去,重要的目標元素套上class或者id。

最後一個選擇器的標籤的應該少用,因為如果你寫個.container div{}的話,那麼頁面上所有的div第一次都匹配中,因為它是從右往左匹配的,這樣的寫的好處是html不用套很多的類,但是擴充套件性不好,所以不要輕易這樣用,如果要用需要仔細考慮,如果合適才使用,最起碼不能濫用。

6. 避免選擇器誤選

有時候會出現自己的樣式受到其他人樣式的影響,或者自己的樣式不小心影響了別人,有可能是因為類的命名和別人一樣,還有可能是選擇器寫的範圍太廣,例如有人在他自己的頁面寫了:

* {
    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;
}
複製程式碼

這可能比你直接套一個類強。但是不管怎麼樣,不能濫用,合適的時候才使用,而不是僅僅為了少寫類名。

7. 減少覆蓋

覆蓋是一種常用的策略,也是一種不太優雅的方式,如下程式碼,為了讓每個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;
    }
}
複製程式碼

我一開始是就是這麼寫的,為了遵循減少覆蓋原則,但是後來發現這種實踐不好,程式碼容易亂,寫成覆蓋的好處在於可以在瀏覽器明顯地看到,小屏的樣式是覆蓋了哪個大屏的樣式,這個在大屏的時候又是怎麼樣的。

8. 使用CSS3的選擇器完成一些高階的功能

上面提到: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等偽類選擇器完成一些簡單的互動。

9. 少用!important

important用來覆蓋屬性,特別是在CSS裡面用來覆蓋style裡的屬性,但是important還是少用為妙。有時候你為了偷懶直接寫個!important,以為這個的優先順序是最高的了,往往螳螂捕蟬,黃雀在後,很可能過不了多久又要再寫一個優先順序更高的覆蓋掉,這樣就略顯尷尬了。所以能少用還是少用。如果要覆蓋還是先通過增加新增選擇器權重的方式。

10. 多寫註釋

"程式猿最煩兩件事,第一件事是別人要他給自己的程式碼寫文件,第二件呢?是別人的程式沒有留下文件"。註釋也是同樣道理,當看到很多綠色的註釋程式碼神經會比較放鬆,而當看到揉成一團還沒有註釋的程式碼是比較壓抑的。CSS的註釋可包括:

(1)檔案頂部的註釋

/*
 * @description整個列表頁樣式入口檔案
 * @author yincheng.li
 */
複製程式碼

(2)模組的註釋

/*詳情頁貸款計算器*/複製程式碼

(3)簡單註釋

/*為了去除輸入框和表單點選時的灰色背景*/
input, 
form{
    -webkit-tap-highlight-color:  rgba(255, 255, 255, 0);
}
複製程式碼

(4)TODO的註釋

有時候你看原始碼的時候你會看到一些TODO的註釋:

/* TODO(littledan): Computed properties don't work yet in nosnap.
   Rephrase when they do.
*/複製程式碼

表示這些程式碼還有待完善,或者有些缺陷需要以後修復。而這種TODO的註釋一般編輯器會把TODO高亮。

注意不要寫一些錯誤的誤導的註釋或者比較廢話的註釋,這種還不如不寫,如下:

/* 標題的字號要大一點 */
.listings h2{
    font-size: 20px;
}
複製程式碼

11. 排版規範

不管是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;
}
複製程式碼

12. 屬性值規範

(1)如果值是0,通常都不用帶單位

例如:

.list{
    border: 1px solid 0px;
    margin: 0px;
}
複製程式碼

應改成:

.list{
    border: 1px solid 0;
    margin: 0;
}
複製程式碼

但是有個特例,就是和時間有關的時間單位都要帶上秒s,如下兩個都是不合法的:

transition-duration: 0;
transition: transform 0 linear;
複製程式碼

(2)色值用十六進位制,少用rgb

如下:

color: rgb(80, 80, 80);複製程式碼

應改成:

color: #505050;複製程式碼

因為使用rgb是一個函式,它還要計算一下轉換。如果是帶有透明度的再用rgba.

如果色值的六個數字一樣,那麼寫3個就好:

color: #ccc;複製程式碼

(3)注意border none和0的區別

如下兩個意思一樣:

border: 0;
border-width: 0;
複製程式碼

而下面這兩個一樣:

border: none;
border-style: none;
複製程式碼

所以用0和none都可以去掉邊框。

你可能會說打包工具其實最後會幫我處理,但自己要保持一個良好的書寫習慣還是很重要的。

13. font-family的設定

注意使用系統字型的對應的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,到時候要整體替換網頁的字型就很麻煩了,正確的做法應該是這樣的:

h1,
strong,
b{
    font-family: Lato Bold;
    font-weight: normal;
}
複製程式碼

如果需要加粗就用標題標籤,或者b/strong標籤,並且要把font-weight調回來,因為那個字型本身就有加粗效果了,如果font-weight再是粗體的話瀏覽器會用自己的演算法繼續加粗。如果是細體怎麼辦,一方面一般細體用得比較少,另一方面沒有細體的標籤,可以通過套類的方式。

14. 不要設定太大的z-index

有些人喜歡設定z-index很大:

z-index: 99999;複製程式碼

以為他是老大了,不會有人再比他高了,但是螳螂捕蟬,黃雀在後,很快得再寫一個:

z-index: 999999;複製程式碼

通常自己頁面的業務邏輯的z-index應該保持在個位數就好了。

15. 合併屬性

一般的說法是說為了提高效能,屬性要合併,但其實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;
}
複製程式碼

16. 注意float/absolute/fixed定位會強制設定成block

如下程式碼:

a.btn {
    float: left;
    display: block;
    width: 100px;
    height: 30px;
}
複製程式碼

第二行的display: block其實是沒用的,因為如果你浮動了,目標元素就會具有塊級盒模型的特性,即使你display: table-cell或者inline也不管用。如果你是display: flex,那麼float將會被忽略。

同樣地,absolute定位和fixed定位也有同樣的效果,會把行內元素變成塊級的。

17. 清除浮動

清除浮動有多種方法,一般用clearfix大法,雖然這個方法有缺陷,但是它比較簡單且能夠適用絕大多數的場景,一個相容IE8及以上的clearfix的寫法:

.clearfix:after{
    content: "";
    display: table;
    clear: both;
}
複製程式碼

就不要在末尾新增一個多餘元素的方法清除浮動了,雖然也可行,但是比較low.

18. 引號的使用

(1)font-family

一般來說font-family不需要新增引號,即使字型名稱帶有空格也沒關係,但是有一種情況是一定要加上引號的,就是字型名稱剛好是關鍵詞,如下字型都需要加關鍵詞:

font-family: "inherit", "serif", "sans-serif", "monospace", "fantasy", and "cursive"複製程式碼

(2)background的url

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');複製程式碼

這樣瀏覽器就能正常載入圖片了。這種情況最好的還是從源頭上避免,但我們也可以做個相容。

(3)單引號還是雙引號

這兩個都是合法的,只是統一一下比較好,不能一下子單引號,一下子雙引號的,比較普遍的推薦是html使用雙引號,css使用單引號。

19. CSS動畫規範

(1)不要使用all屬性做動畫

使用transition做動畫的時候不要使用all所有屬性,在有一些瀏覽器上面可能會有一些問題,如下:

transition: all 2s linear;複製程式碼

在Safari上面可能會有一些奇怪的抖動,正確的做法是要用哪個屬性做動畫就寫哪個,如果有多個就用隔開,如下程式碼所示:

transition: transform 2s linear,
             opacity 2s linear;複製程式碼

(2)使用transform替代position做動畫

如果能用transform做動畫的,就不會使用left/top/margin等,因為transform不會造成重繪,效能要比position那些高很多,特別是在移動端的時候效果比較明顯。基本上位移的動畫都能用transform完成,不需要使用CSS2的屬性,如一個框從右到左彈出。

(3)偏向於使用CSS動畫替代JS動畫

例如把一個框,從下到上彈出,可以用jQuery的slideUp函式,或者自己寫setInterval函式處理,但是這些沒有比用CSS來得好。使用CSS,初始狀態可以把框translate移動螢幕外,然後點選的時候加上一個類,這個類的transform值為0,然後再用transition做動畫就好了。

20. 不要斷詞

英文的單詞或者數字如果當前行排不下會自動切到下一行,這樣就導致每行長短不一,有時候可能不太美觀,但是不能使用word-break: break-all把一個單詞拆成兩行,還有一種是使用:

hyphens: auto;複製程式碼

它會把單詞拆成用-連線的形式,看起來好像挺合理,但是由於它斷詞斷得不夠徹底,有些單詞斷不了,長短不一的現象看起來也比較明顯,有些單詞還被拆成了兩行,所以還不如不加。

因此,不要使用斷詞。

21. 不要設定圖示字型font-family

這個和上面提到的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了。

22. 設定常見樣式reset

由於每個瀏覽器都有自己的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, h1, h2, ul, ol, figure, li{
    padding: 0;
    margin: 0;
}

h1, h2, 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;
}
複製程式碼

23. 圖片壓縮

不管是UI直接給的圖片還是自己從UI圖裡切出來的圖片,都需要把圖片壓縮一下,建議使用tinypng,它可以在保持圖片質量減少較低的情況下,把圖片壓得很厲害,比直接在PS裡面設定壓縮質量要強。如果是色彩比較豐富的圖片要使用jpg格式,不能使用png格式,png會大得多,如果是logo那種向量圖片,直接使用svg格式即可。一般來說要把圖片控制在300k以內,特別是banner頭圖,圖片的大小也要控制住。

24. 正確使用background和img

顯示一張圖片有兩種方式,可以通過設定CSS的background-image,或者是使用img標籤,究竟什麼時候用哪種呢?

如果是頭圖等直接展示的圖片還是要img標籤,如果是做為背景圖就使用background,因為使用img可以寫個alt屬性增強SEO,而背景圖那種本身不需要SEO。雖然background有一個一個background-position: center center很好,但是頭圖那種還是使用img吧,自己去居中吧,不然做不了SEO。

25. 響應式的規範

響應式開發最不好不要雜合使用rem,文字字號要麼全部使用rem,要麼不要用,也不要使用transform: scale去縮小,因為被縮小的字號看起來會有點奇怪,別人都是14px,而你變成了13.231px,小了一點。響應式的原則一般是保持中間或者兩邊間距不變,然後縮小主體內容的寬度。

26. 適當使用:before/:after

:before和:after可以用來畫頁面的一些視覺上的輔助性元素,如三角形、短的分隔線、短豎線等,可以減少頁面上沒有用的標籤。但是頁面上正常的文字等元素還是不要用before/after畫了。

27. 少用absolute定位

首先absolute定位的元素渲染效能會比較高,因為它獨立出來了,計算量會少,用得好還是可以的。但是如果你頁面的主要佈局是使用absolute的那肯定是不可取的,因為absolute定位的可擴充套件性很差,你把每個元素的位置都定死了就變不了了,可以多用float,雖然float的效能相對較差,但是不管是實用性還是相容性都是挺好的。

28. 少用inline-block佈局

有些人喜歡用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,可以嘗試用一下。只有你的切圖方式多樣化了,你切起圖來才能比較靈活。

29. 圖片的居中和寬高設定

一般來說,UI給的圖片展示寬高是固定的,但是實際的圖片長寬是不固定,大部分圖片是長是比寬大,小部分圖片是寬比長大。所以需要居中裁剪展示,如下圖所示:

中間黑色的框是展示區域,圖片的短邊和窗器的邊一樣大,另一邊按圖片的原始比例拉伸,然後居中顯示。這個得藉助JS,因為圖片未載入好之前,不知道是長邊比較大還是寬比較大。如下程式碼:

<div class="img-container">
    <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做居中。

30. 移動端提高可點區域範圍

移動端的的一些圖示如X,可能會設計得比較小,所以點起來會不太好點,因此要提高可點區域範圍,可通過增加padding,如下程式碼:

.icon-close{
    position: abosulte;
    right: 0;
    top: 0;
    padding: 20px;
}
複製程式碼

這樣區域就增加了一圈,點起來就容易多了。

31. 不要設定input的line-height

如果設定input的line-height,如下程式碼,你可能要做垂直居中:

.request-demo input{
    height: 40px;
    line-height: 40px;
}
複製程式碼

設定了line-height為一個很高的值,這樣會導致Safari瀏覽器的輸入游標|變得巨大,所以如果你要居中的話,使用padding吧。

32. 移動端彈框要禁止body滑動

因為IOS Safari切換輸入框的時候會頁面會彈閃得很厲害,因為你在切的時候它會先把鍵盤收起來,然後再彈出來,這個時間很短,給人感覺頁面彈閃了一下,但如果把body禁止滑動了就不會有這個問題,這有兩個解決辦法,第一種是把body fixed住,第二種設定body overflow: hidden,相對來說第二種比較簡單一點。IOS10完全不會閃,IOS9以下還是會閃。

33. 對於漸變的處理

有時候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 );複製程式碼

34. 行內元素可以直接設定margin-left/margin-right

如下有些人為了把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,也不用再設定。

三、JS編碼規範

1. 變數命名

《程式碼大全》這本書裡面有一章是專門講變數命名的,這裡結合這本書的建議做說明。總地來說,變數名要準確完整地描述該變數所表述的事物,具體來說:

(1)變數名不應以短巧為榮

如以下好的變數名和不好的變數名:

不好的變數名好的變數名
inpinput, priceInput
day1, day2, param1today, tomorrow
iduserId, orderId
objorderData, houseInfos
tIdremoveMsgTimerId
handlersubmitHandler, searchHandler

左邊的變數名都不太清楚,程式碼的擴充套件性不好,一旦程式碼需要加功能的話,就容易出現obj1、obj2、obj3這種很抽象的命名方式。所以一開始就要把變數的名字起得真實有意義,不要搞一些很短很通用的名字。

當然變數名取得太長也不好,如maximumNumberOfTeamMembers.

(2)變數名不要使用計算機術語

變數名應直指問題領域,來源於現實世界,而不是計算機世界,例如取了texareaData之類的名字,應該取一個和業務相關的名字,如leaveMsg.

(3)變數名的對仗要明確

如up/down、begin/end、opened/closed、visible/invisible、scource/target,對仗明確可以讓人很清楚地知道兩個變數的意義和用途。

(4)警惕臨時變數

有些喜歡取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;
複製程式碼

(5)bool變數

《程式碼大全》這本書建議布林變數不用以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.

2. 宣告變數時要賦值

如下宣告三個變數:

var registerForm,
     question,
     calculateResult;
複製程式碼

以上絕對是合法JS語法,但是這三個變數的用途會讓人比較困惑,特別是中間第二個question,問題是什麼。但是當你把上面的變數賦一個初始值的時候:

var registerForm = null,
     question = "",
     calculateResult = 0;
複製程式碼

就讓人豁然開朗了,原來question是一個問題的字串,而result是一個數字,form是一個物件。這也有利於JS直譯器提前做一些優化處理,不用等到使用的時候才知道這些變數是什麼型別的。

3. 函式的返回值型別要確定

如下程式碼:

function calculatePrice(seatCount){
    if (seatCount <= 0) {
        return "";
    } else {
        return seatCount * 79;
    }
}
複製程式碼

這個程式碼可能返回整型,也有可能返回字串,就會讓人比較困惑,同時從程式碼效能來說也是不高的,雖然它是合法的JS語法,一個函式的返回型別要統一。你可能會說我用上面的函式做為輸入框顯示的值,如果是負數或者0,那麼輸入框就不要顯示任何東西,所以才會返回空的字串。即使是這樣的原因也不建議這樣寫,從長遠來看這樣寫是不利的,你應該用其它的方法組織你的程式碼。要養成強型別的程式碼風格,這樣不容易出bug,擴充套件也容易。另外如果一個變數你把它當成數字使用,下面就不要再把它當成字串使用了,因為這樣也容易讓人困惑。微軟的Typescript就是一種強型別的書寫語法,很多大型專案會使用typescript寫JS,有興趣的可以繼續瞭解怎麼寫typescript.

4. 不要給變數賦值undefined

undefined表示一個變數未定義,你定義了一個變數又說它未定義本身就很奇怪。這可能會造成的問題是使用上的歧義,因為我們經常使用undefined來判斷變數有沒有定義:

if (typeof window.HTMLVideoElement === "undefined")複製程式碼

如果要賦值應該要賦空值,如物件賦值為null,數字賦值為0,字串賦值為空字元,那你可能會說0也是一個正常的數字,如果賦值為0會導致我誤認為它是一個正常的資料,那怎麼辦呢?如果你的數字都是非負數,那麼可以把初始值置為-1,實在不行就置成NaN.

函式的返回值也不要顯式地return undefined.

5. 排版規範

一個比較流行的空格和縮排排版如下程式碼所示:

//逗號後面帶個空格,) {中間帶個空格
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個字元,超過就要換行,不管是註釋還是程式碼。

6. 使用===代替==

==會帶上型別轉換,這和上面一樣的,我們要用強型別的風格寫程式碼,所以不要使用==,如果有型別轉換自己做型別轉換,不要讓別人去猜這裡面有型別轉換,使用==會有一些比較奇怪的結果:

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
複製程式碼

7. 減少魔數

對一些比較重要的常量起一個名字,例如下面的程式碼:

const ONE_DATE = 3600 * 24 * 1000;
var tomorrow = today + ONE_DATE;
複製程式碼

再如下面不好的寫法:

dialogHandler.showQuestionNaire("seller", "sell", 5, true);複製程式碼

上面四個常量會讓人看起來比較困惑,如果可以的話給它們起個名字,如果覺得麻煩那就加上註釋。

8. 不要讓程式碼暴露在全域性作用域下執行

一個原因是在全域性作用域下,變數的查詢時間會更長,第二個原因是汙染全域性作用域,有時候會造成一些意想不到的結果,如下:

var name = "hi boy";
console.log(window.name);
複製程式碼

定義了一個變數,但是剛好不巧window.name是本來有這個屬性,這個屬性通常用來跨域傳遞資料。如果你設定了name這個變數,就把全域性的window.name給覆蓋了。

9. let/var/const的使用

ES6新增了let/const定義變數,使用let有一些好處,如:

(1)避免變數重複定義

let me = "go";
// Uncaught SyntaxError: Identifier 'me' has already been declared
let me = "go"; 
複製程式碼

使用babel loader打包的時候它會做靜態檢查:

Module build failed: Duplicate declaration "me"

(2)for迴圈的變數作用域是獨立的

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;
複製程式碼

或者是定義其它一些不需要修改的變數,防止不小心被其它程式碼修改了。

10. 簡潔程式碼

(1)使用三目運算代替簡單的if-else

可以寫一行就不要寫三行,如下:

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行。

(2)使用箭頭函式取代簡單的函式

例如以下程式碼:

setTimeout(function(){
    window.location.reload(true);
}, 2000);
複製程式碼

可改成:

setTimeout(() => window.location.reload(true), 2000);複製程式碼

程式碼從3行變成了1行。

11. 注意避免執行過長時間的JS程式碼

對於一般的頁面的資料量來說,加減乘除等計算不足以造成效能瓶頸。容易造成瓶頸的是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個,當資料處理完再調完成回撥函式。

12. 多寫註釋

這個和CSS規範類似:

(1)檔案頂部的註釋,包括描述、作者、更新

/*
 * @file listing-detail.js
 * @description 房源詳情頁的JS主檔案,處理輪播、房貸計算器、約看房等邏輯
 * @author yincheng.li
 * @update (yincheng.li 2017/8/19)
 */
複製程式碼

(2)函式的註釋

/*
 * 和搜尋介面展示有關的處理邏輯
 * @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 當前版本
*/
複製程式碼

(3)變數定義和程式碼的註釋

對一些比較重要的變數加註釋,標明它是什麼用途,以及對一些核心程式碼邏輯加上註釋,或者比較複雜的業務邏輯,寫了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)後面加程式碼,當我看到這個註釋我就很清楚地知道需要在哪裡按回車。不過一般不推薦巢狀很深的程式碼,或者寫得很長,一個函式幾百行。

13. 程式碼不要巢狀太深

有些人的程式碼經常會套個七八層,以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再做一層抽象主要是這個東西實在是太常用,讓人一眼就知道要幹嘛,把它分離到另外一個地方可以讓具體的業務程式碼更加簡單,例如上面發請求,我把回撥函式準備好之後,只要執行一行程式碼就好了。

你縮排太多層,一行就被空格佔掉了三、四十個字元,感觀上就不是很好,還會出現上面提到的,最後面要寫好多個右括號收尾的情況,並且一個函式動不動就兩、三百行。

14. jQuery編碼規範

如果你使用了jQuery。

(1)使用closest代替parent

儘量不要使用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操作對自己先加一個類然後再去掉的正負相抵操作很可能是不會執行的。

(2)選擇器的效能問題

如下程式碼:

$(".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的範圍,$page的節點只有幾十個,在幾個裡面找就比在document幾百幾千個節點裡面查詢要快多了。jQuery的查DOM也是用的querySelectorAll,這個函式除了用在document之外,可用在其它DOM結點。

(3)on事件之前需要的時候才off

有些人喜歡在綁事件之前先off掉,這樣感覺可以確保萬無一失,但是如果你綁的事件是匿名的,你很可能會把其它JS檔案綁的一起off掉了,並且這樣不容易暴露問題,有時候你的問題可能是重複繫結事件,如點一次按鈕就綁一次就導致了綁多次,所以根本原因在這裡。你應該要確保事件只被綁一次,而不是確保每次寫之前都先off掉。如果你的事件容易出現綁多次的情況說明你的程式碼組織有問題,這個在開發的時候應該是能夠暴露出來的。

(4)對DOM節點較少的不要使用委託

例如說一個表單只有幾個input元素,然後你給input加了個委託到form上面,甚至有時候是body上面,由於事件冒泡導致在form上或者在頁面上的所有操作都會冒泡到form/body上,即使操作的不是目標元素,這樣jQuery就會收到在body上的事件,然後再判斷處理所有的操作的目標元素是不是你指定的那個,如果是再觸發你綁的回撥函式。特別是像mousemove觸發得頻繁的事件都需要執行。所以如果元素比較少或者不需要動態增刪的那種就不要使用冒泡了,直接綁在對應的多個元素就好了。

(5)有時候使用原生更簡單

例如獲取表單的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操作還是挺有幫助的。

15. 對於常用的屬性進行快取

如下程式碼,頻繁地使用了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變成一個區域性變數之後,它的查詢時間將明顯快於全域性變數。你可能會說就算再快這點時間對於使用者來說還是沒有區別的吧,但是這是做為一名程式設計師的追求,以及可以讓程式碼更簡潔。

16. 儘量不要在JS裡面寫CSS

如下程式碼,如果是非選中狀態就把顏色置灰:

$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實現不了.

17. 在必要的地方新增非空判斷

新增非空判斷可以提高程式碼的穩健性,如下程式碼:

//彈框時顯示other monthly charge
showOtherMonthlyCharge: function(otherCharges, $dialog){
    if(!otherCharges || !otherCharges.length){
        return;
    }   
}
複製程式碼

如果傳的為空就不用處理,有時候你可能要拋個異常,告訴呼叫者。對一些比較重要的地方可能還要新增型別檢驗。後端傳的資料要確保會有那個屬性,如果不確定也要新增非空判斷。如果調了第三方的API,新增出錯處理也很重要,因為你不能確保第三方API一定能正常工作,在一些你覺得可能會掛的地方做處理,如請求可能會超時,或者返回了undefined的異常結果,這種多使用一般能夠發現。

18. 不要用for in迴圈陣列

如下程式碼:

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方法。

19. 分號規範

JS裡面的表示式是可以不用分號結尾,例如Zepto的原始碼幾乎沒看到一個分號,但是我們還是提倡要每個句子後面都要加上分號,這樣不容易出錯。

20. 使用location跳轉需要先轉義

對於那些根據使用者輸入內容做跳轉,需要先把使用者內容做轉義,如下有問題的程式碼:

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}`;
複製程式碼

這樣跳轉就沒有問題了。

21. 點選跳轉儘量不要使用onclick跳轉

點選一個容器的時候做跳轉,有些人喜歡這麼寫:

<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事件是否有延遲的問題。

22. 不要直接使用localStorage

由於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.

23. 使用簡便的轉換

(1)把字串轉整型可以使用+號

let maxPrice = +form.maxPrice.value;複製程式碼

+號相當於Number:

let maxPrice = Number(form.maxPrice.value);複製程式碼

parseInt和Number有一個很大的區別是parseInt(“10px”)結果為10,而Number(“10px”)是NaN,parseInt會更加自然,其它程式語言也有類似的轉換。但是Number還是能適用很多的場景。

(2)把小數去掉尾數轉成整型,可以使用 >> 0

如果計算某個數字在第幾排:

let _row = Math.floor(index / columns);
let row = parseInt(index / columns);
複製程式碼

都可改成:

let row = index / columns >> 0;複製程式碼

這個用位運算的效率會明顯高於上面兩個。

(3)轉成boolean值用!!

如下程式碼:

let mobile = !!ua.match(/iPhone|iPad|Android|iPod|Windows Phone/)複製程式碼

24. 注意返回false的變數

有幾個值在if判斷裡面都返回false:0、false、””、undefined、null、NaN都是false,所以判斷一個陣列有沒有元素可以這麼寫:

if (array.length) {}複製程式碼

而不用寫成:

if (array.length !== 0) {}複製程式碼

判斷一個字串是不是空可以寫成:

if (str) {}複製程式碼

但是判斷一個變數有沒有定義還是要寫成:

if (typeof foo !== “undefined”) {}複製程式碼

因為如果直接if變數的話,上面的幾個可能取值都將認為是沒定義。

25. 使用Object.assgin簡化資料賦值

如下程式碼,在發請求之前,經常需要獲取表單的值,然後去修改和新增老資料提交:

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就可以了。

26. 除錯完去掉無關的console

除錯完就把console.log之類的列印資訊去掉,別想著等一下做完了再刪,等一下就忘了。另外,不要使用alert除錯,console/debugger上線了都沒事,一般使用者也不會開一個控制檯,但是alert上線了就完蛋了,特別是有些人喜歡用alert(“fuck”)之類的看下程式碼有沒有執行到這裡,這種除錯技巧還是比較初級,要是真上線了可能得捲鋪蓋走人了。這也可以通過程式碼檢查工具做靜態檢查。

27. 注意this的指向

如下程式碼:

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);
複製程式碼

這樣就沒問題了。

28. 使用正規表示式做字串處理

正規表示式可以很方便地處理字串,通常只要一行程式碼就搞定了。例如去掉全域性的某一個字元,如去掉電話號碼的-連線符:

phoneNumer = phoneNumber.replace(/\-/g, “”);複製程式碼

或者反過來,把電話號碼改成3-3-4的形式:

phoneNumber = phoneNumber.replace(/^(\d{3})(\d{3})(\d{4})$/, “$1-$2-$3”);複製程式碼

熟練掌握正規表示式是每個前端的基本技能。

29. 保持複用模組的觀念

當你一個函式要寫得很長的時候,例如兩、三百行,這個時候你考慮把這個大函式給拆了,拆成幾個小函式,然後讓主函式的邏輯變得清晰簡潔,而每個小函式的功能單一獨立,使用者只需要管輸入輸出,而不需要關心內部是怎麼執行的。如下在地圖裡面處理使用者點選的處理函式:

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,傳一些引數就好了。

先抽成不同的函式,每個函式負責一小塊,相似的函式聚集在一起形成一個模組,幾個模組的相互呼叫又形成一個外掛。

30. 注意label事件會觸發兩次

如果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事件要注意注意觸發兩次的情況。


相關文章