關於前端的一些東西--記住了一些,閒時再看看

這篇文章很長,但的確是一篇非常乾的乾貨,講訴了 HTML、JavaScript、CSS、jQuery使用的一些規範與建議,前端的同學可以認真閱讀此文,並比較自己平時的一些習慣,看是否有改進的地方……


HTML

咋地了, DOCTYPE?

不定義DOCTYPE是一種可以被判死刑的罪行。 以前你可能用的是下面的DOCTYPE,不過你要知道現在已經有更簡潔清晰的代碼取而代之了。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

理想的狀況是用HTML5 DOCTYPE,所有現代的瀏覽器都支持它,即使是不支持HTML5的瀏覽器,例如IE6,IE7,也會由此轉入標準模式。 參見來源

<!DOCTYPE html>

編寫合法且語義清晰的標記

用整潔、語義清晰的HTML編寫網站代碼是我們一直孜孜以求的。有時我們會發現前人配置頁面的方式限制了我們,或者有時我們編寫的是HTML格式的email模板。但永遠不要偏離HTML的規範,即使是爲了解決特定瀏覽器兼容性的bug。

所有的標題應該從<h2>開始分層級創建,文字段落應該總是放在<p>標籤裏,諸如此類。如果你編寫的HTML的語義清晰,產生的頁面會更整潔、簡練,而且易於被搜索引擎爬蟲解析。這是你能做到的最簡單的SEO修補方式。

來看看下面的段落,你覺得哪個更整潔?是這個?

<span class="sectionHeading">A Heading</span>
<br /> <br />
Lorem ipsum dolor sit amet. ...
<br /> <br />

還是這個?

<h2>A Heading</h2>
<p>
    Lorem ipsum dolor sit amet. ...
</p>

鼠標中鍵點擊的應變方式

現代web應用最令人鬱悶的可用性缺陷之一是超鏈接功能的變種。 一些看起來像是超鏈接的元素可能是通過Javascript映射的單擊功能,這就破壞了鼠標中鍵點擊(在新的tab中打開鏈接頁面)的功能。即使它們能在新的標籤頁打開,它們只帶有一個 # 的href又會把你帶回到同樣的頁面。

深刻詮釋了該問題的一個現代熱門網站的例子就是Twitter。在它的整個應用裏,鼠標中鍵點擊用戶名或頭像會得到完全不同的結果。

<!-- 舊的方式,破壞網頁語義 -->
<a href="#"></a>

<!-- 如果鼠標點擊不能產生一個頁面,那就不是超鏈接 -->
<span class="link" role="link"></span>

另一個替代方案是使用 # 引導的路徑,它會把普通的url映射爲 # 引導的鏈接,然後通過AJAX來獲取頁面片段。提供此功能的庫應該能夠在鼠標中鍵點擊的時候正常顯示頁面,或者在左鍵點擊時把該頁面內容加載到指定的區域。不過這樣也要慎重行事,有很多人都認爲 #鏈接正在破壞web應用

用Microformats格式表示聯繫人信息

Microformat是一種便於機器讀取聯繫人信息的方式。hCard類(不是vCard)用來定義元素裏包含的內容類型。這些內容會被瀏覽器提取並突出顯示。

<span class="tel">
    <span class="type">home</span>:
    <span class="value">+1.415.555.1212</span>
</span>

如果你曾經瀏覽採用此格式的網頁,你會注意到類似skype的程序可以輕鬆檢測到網頁上的哪些數字是電話號碼。在iOS設備上的Safari瀏覽器也可以做到類似的事情。

有關Microformat的更多信息請參閱http://microformats.org/wiki/hcard

圖片需要設 'Alt' 文本

<img> 標籤需要 alt 文本,以便檢查並滿足可讀性的要求。 在 alt 屬性中的文本必須能夠說明圖片顯示的內容或要達到的效果,除非該圖片不重要。

如果圖片只是一個列表中的着重號或者其他無關緊要的圖標,最好是給 alt 屬性一個空字符串,但還是留着它。這樣屏幕閱讀器會忽略它,而不是把"着重號"連讀20次。

<img src="dog.gif" alt="Fido and I at the park!" />
<!-- 很好,描述清晰 -->

<img src="bullet.gif" alt="bullet" />
<!-- 不好,顯得多餘 -->

<img src="bullet.gif" alt="" />
<!-- 好 -->

只對表格數據用table標籤

table標籤永遠只應該用在表格數據的展示上。唯一的例外是當編寫HTML格式的郵件時,這種情況下可能table是某些坑爹的郵件客戶端唯一支持的樣式了。

爲了可讀性,表格頭永遠要使用 <th> 元素。同時切記要設置cellpadding, cellspacing  border 的值爲 0 , 因爲這些樣式由CSS來控制更容易確保一致性。

<table cellpadding="0" cellspacing="0" border="0">
    <thead>
        <tr>
            <th>
                Cell Header
            </th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>
                Cell Item
            </td>
        </tr>
    </tbody>
</table>

使用 jQuery 和 jQuery UI Widgets

jQuery 和 jQuery UI 被做成儘可能在不同瀏覽器上表現出幾乎相同的外觀和功能。 jQuery UI 被設計爲符合 WAI WCAG 2.0 及 WAI ARIA, 因此採用該框架可以避免在你的站點上運行的插件或腳本的所有不確定性。


JavaScript

代碼留空和格式

任何關於代碼格式、留空和大括號位置的討論都會引起激烈辯論。對此,我想最簡單的規則就是,除非你願意把整個代碼文件重新格式化,不然還是尊重並保持已有代碼文件的格式。這意味着如果你看到一個JS文件裏的大括號沒有換行寫,那你的代碼也要繼續保持大括號不換行。如果你的代碼沒有和代碼文件裏的其他部分保持一致,那麼你的代碼就不應該通過代碼審查流程。

一致的代碼格式讓代碼更加易讀,同時也意味着代碼容易用查找/替換命令進行修改。謝天謝地,我們自己形成的編程習慣和jQuery正式推薦的方式非常相似。細微的差異也是有的,不過,那些是個人問題或者我們覺得沒法維護的一些東西。 參閱jQuery核心樣式指南

字符間距

// 不好
if(blah==="foo"){
    foo("bar");
}

// 好 :)
if (blah === "foo") {
    foo("bar");
}

大括號不換行

// 不好
if (foo)
{
    bar();
}

// 好 :)
if (foo) {
    bar();
}

總是用大括號

// 不好
if (foo)
    bar();

// 好 :)
if (foo) {
    bar();
}

字符串處理

引用字符串永遠要用雙引號。 有些人非常喜歡用C語言風格的字符串(單引號),但這種習慣會導致腳本內部的風格衝突。C語言風格的字符串處理要求空字符串和單字符包在單引號裏,而短語和單詞必須包在雙引號內。

註釋

往代碼裏玩命加註釋的需求是由各種經理、主管及其他很少接觸代碼的人們引領的。這種需求無非是僱員們考覈指標中的一個勾選欄,花在這上面的時間基本沒有帶來什麼回報。 如果那些從善如流的開發者能遵循本文檔中提出的建議,他們的代碼會變得相當易於閱讀,一目瞭然,以至於再用註釋描述這些代碼會多餘得令人尷尬。來看下面的例子。在這裏,布爾變量被作爲問題提出,而函數也有直觀的命名。

if (user.hasPermission) {
    editPage();
}

至少在這個場景中,註釋是完全沒有必要的。

註釋重要的情況

一個項目裏,永遠會有某些部分難以查閱和理解。比如一個複雜的正則表達式,或者一個對角度進行計算或在度和弧度單位之間切換的數學函數。沒有上面的註釋,初級或中級的讀者將對腳本的含義毫無頭緒。

// 校驗美國電話號碼的正則表達式,號碼格式是 (XXX) XXX-XXXX (減號、空格和括號都是可選的,可以有也可以沒有)
var phoneRegEx = /^\(?(\d{3})\)?[- ]?(\d{3})[- ]?(\d{4})$/;

總是使用 === 比較符

使用 == 比較符可以讓令人鬱悶的bug消失於無形。它允許在 JavaScript花園 中有清楚解釋的弱類型。使用嚴格的 === 比較符不會執行類型強制轉換,從而能夠嚴格地評估兩個對象之間的差別。再說一遍,更多詳細信息請參見 JavaScript花園

var zeroAsAString = "0";

if (zeroAsAString == 0) {
    // 這樣也能判斷爲true,呵呵...
}

if (zeroAsAString === 0) {
    // 判斷爲false
}

例外

在和null進行比較的時候,允許使用 == 比較符,因爲它會檢測null和undefined兩個屬性。如果你不完全理解這個原理,那我還是建議你用 === 比較符爲好。

var foo = null;

// foo 是 null, 但 bar 是 undefined ,因爲它尚未被聲明
if (foo == null && bar == null) {
    // 上面的判斷還是成立的
}

使用 .parseInt() 的時候,總是指定第二個 'radix' 參數

把字符串解析爲整數的時候,有個好習慣是指定第二個基數參數 -- 它會確定該字符串被轉換成幾進制。當字符串以 0 開頭的時候,缺省情況下會觸發 16 進製作爲基數。大部分初級和中級用戶只會用到 10 這個基數。 感謝 João Moreno 記錄的這個 勘誤

alert( parseInt("08") ); // alerts: 2

alert( parseInt("08", 10) ); // alerts: 8

避免比較 true 和 false

直接比較 true 和 false 的值是沒有必要的。有時候也許明確一下有好處,但它還是額外的代碼。

if (foo === true) {
    // 用了 === 倒是不錯,可這是多餘的
}

if (foo) {
    // 贊!
}

if (!bar) {
    // 反過來也贊
}

避免污染全局命名空間

過分依賴全局變量是我們組所有人 -- 特別是我自己 -- 特別有負罪感的一件事。關於爲啥全局變量不好的討論是相當直接的:這增加了腳本和變量衝突的概率,而且源文件和命名空間本身都會充斥着數不清的命名模糊的變量。

Douglas Crockford 堅信一個Javascript應用的代碼質量可以用其中使用的全局變量數來評價,越少越好。由於並不是什麼都可以定義成local的(不過要誠實,其實你現在考慮的那個是可以的,別偷懶),你需要想辦法整理你的變量以避免衝突,並把命名空間的膨脹減到最小。最簡單的方法就是採用單變量或者把用到這些全局變量的模塊儘可能減少。 Crockford提到YUI只用了一個全局變量,YAHOO。他在他的博文 "全局統治" 中討論了更多的細節問題。

考慮這種情況:對於小型web應用,全局變量通常用於保存應用級的設置,可以用你的項目名或者settings作爲命名去定義一個對象,這樣總的來說會更好。

// 被污染的全局命名空間
var settingA = true;
var settingB = false;
var settingC = "test";

// 用 settings 作爲對象命名
var settings = {
    settingA: true,
    settingB: false,
    settingC: "test"
}

不過,如果我們可以通過避免使用全局變量來減少衝突概率,但是把命名空間標準化成一樣的,豈不是會增加各個應用之間產生衝突的概率麼?呃,這個擔憂確實有道理。所以,建議你用自己特定的應用名作爲全局變量的命名空間,或者用和jQuery採取的 $.noConflict() 模式相同的方法重新分配你的命名空間.

var myAppName = {
    settings: {
        settingA: true
    }
}

//訪問全局變量
myAppName.settings.settingA; // true

駝峯法變量命名

JavaScript變量的駝峯法命名在大部分編程環境中都是作爲標準的。有讀者在評論中提出了唯一的例外,就是要用大寫字母加下劃線來指代常量。

var X_Position = obj.scrollLeft;

var xPosition = obj.scrollLeft; // 更好,更簡潔

SCENE_GRAVITY = 1; // 常量

循環的性能 - 緩存數組長度

循環估計是Javascript性能調優最重要的部分了。在一個循環內部節省個一兩毫秒的,說不定總體就省出好幾秒來了。這裏有一招就是把數組的長度緩存,這樣在循環裏就無需每次迭代的時候都計算一遍了。

var toLoop = new Array(1000);

for (var i = 0; i < toLoop.length; i++) {
    // 敗家玩意 - 長度會反覆算 1000 次你知道不?
}

for (var i = 0, len = toLoop.length; i < len; i++) {
    // 會過日子 - 長度只計算一次,然後緩存了
}

例外

如果你對一個數組做循環來查找並刪除某個元素,這就會改變數組長度。任何時候你只要會在循環內部增加或刪除元素來改變數組的長度,你就給自己帶來了麻煩。這種情況下,你要麼每次改變後重新設置數組長度,要麼就別緩存它了。

循環的性能 - 使用 'break;' 和 'continue;'

跳過和跳出循環的能力對於避免開銷很大的循環週期是非常有用的。

如果你是在循環內部查找,查找成功以後你會做什麼?比如1000個元素的循環執行到一半,你就找到了你要的東西。你不管三七二十一,即使知道後面的if語句不會再有符合的機會,還是讓循環繼續把剩下的500個元素迭代完麼?不!你應該跳出循環,必須的!

var bigArray = new Array(1000);

for (var i = 0, len = bigArray.length; i < len; i++) {
    if (i === 500) {
        break;
    }
    console.log(i); // 這樣只會輸出 0 - 499
}

另一個問題是跳過某個特定的迭代,然後繼續循環。雖然說類似於奇偶數這樣的條件可以通過把 i++ 替換成 i + 2 的辦法來管理,有些條件還是需要具體檢測,然後觸發跳過操作。任何能夠避免執行完整的迭代過程的東西都是很有用的。

var bigArray = new Array(1000);

for (var i = 0, len = bigArray.length; i < len; i++) {
    if (condition) {
        continue;
    }
    doCostlyStuff();
}

函數調用不要傳輸太多的參數

對於可讀性而不是其他因素來說,下面這種方式真是糟透了:

function greet(name, language, age, gender, hairColour, eyeColour) {
    alert(name);
}

下面的例子預先構建了一個對象作爲參數,或者把內聯對象傳遞過去,這樣就好多了。

function greet(user) {
    alert(user.name);
}

greet({
    name: "Bob",
    gender: "male"
});

把 'this' 映射爲 'self'

在編寫面向對象(OO)Javascript代碼的時候, 必須瞭解 this 的作用範圍. 不管你用來構建僞類的設計模式是什麼,對 this 的引用是指向一個實例的最簡單辦法。當你開始把jQuery的helper方法和你的僞類集成的時候,你就會注意到 this 的作用範圍變化。

Bob.findFriend("Barry");

Person.prototype.findFriend = function(toFind) {
    // this = Bob
    $(this.friends).each(function() {
        // this = Bob.friends[i]
        if (this.name === toFind) {
            // this = Barry
            return this;
        }
    });
}

在上面的例子裏, this 經歷了從對 Bob 的引用,變成對他的朋友 Barry 的引用的過程。 瞭解 this 的取值在一段時間發生的變化是很重要的。在原型函數內部, this 指向所在僞類的當前實例(這裏是 Bob )。而一旦我們進入 $.each() 循環, this 就會重新映射爲被解析數組的第 i 個元素。

解決辦法是把 this 的值重新映射爲 self 或者 _self。雖然 self (不帶下劃線)並不是 保留字, 但它 確實是window 對象的一個屬性。雖然我上面用到 self 的例子是從jQuery源代碼中挑的,但他們也認識到了這個錯誤,正打算 修正目前的狀況 ,也就是改用 _self。我個人還是喜歡用 self ,不爲別的,就因爲它的簡潔 -- 不過它可能會冒出一些非常令人困惑的bug。總之,用 self 有風險,使用需謹慎。

在下面的例子中,我會更好地利用在 $.each() helper 中提供的參數,同時重新映射 this 的值。

Bob.findFriend("Barry");

Person.prototype.findFriend = function(toFind) {

    // 就這一次用到了 "this"
    var _self = this;

    $(_self.friends).each(function(i,item) {
        if (item.name === toFind) {
            return item;
        }
    });

}

我能用 Boolean 嗎?

布爾變量必須能夠很容易通過命名來識別。可以用類似於 is, can 或者 has 的前綴來形成一個問句。

isEditing = true;

obj.canEdit = true;

user.hasPermission = true;  

儘量減少重新繪製和重新佈局

重新繪製和重新佈局與重新渲染DOM的過程關聯,這個過程會在特定屬性或元素被改變時發生。重新繪製是在某個元素的外觀被改變但沒有對佈局進行調整的情況下觸發的。 Nicole Sullivan 在一篇全面的 博文 中把這些改變描述爲諸如是否可見或背景色變化之類的樣式改變。重新佈局則是開銷更大的操作,由調整頁面佈局的一些改變引發。例如增加或刪除元素,改變某個元素的寬度或高度,甚至是改變瀏覽器窗口的大小。最糟糕的情況是重新佈局導致先輩、兄弟和孩子節點元素也需要重新佈局的多米諾骨牌效應。

毫無疑問,重新繪製和重新佈局應該儘量避免,但是如何做到呢?

重新佈局的例子

其實也不是說下面的代碼就很糟糕啦。不過我們先假定數組 arr 有10個元素

var myList = document.getElementById("myList");

for (var i = 0, len = arr.length; i < len; i++) {

    myList.innerHTML += "<li>" + arr[i].title + "</li>"; //重新佈局 -- 增加到元素

}

在上面的 for 循環裏,每次迭代會觸發一次重新佈局。10次迭代就是10次重新佈局。

現在考慮下面的代碼:

var constructedHTML = "";

for (var i = 0, len = arr.length; i < len; i++) {
    constructedHTML += "<li>" + arr[i].title + "</li>"; //沒有重新佈局 - 增加到字符串
}

document.getElementById("myList").innerHTML = constructedHTML; //在這裏重新佈局

在這個場景裏,需要增加的元素是在一個字符串裏構建的。循環裏邊沒有產生任何重新佈局,因爲DOM並沒有變化。只有當數組被完全循環完畢,構建的字符串被應用到某個對象的 innerHTML ,這才產生函數裏唯一的一次重新佈局。

有無數種重新佈局和重新繪製是可以避免的,希望你幸運地瞭解了那些訣竅。這方面的閱讀材料汗牛充棟,不過大部分的材料都會引用到 Nicole Sullivan的這篇 博文 ,這是一個完美的起點。除了這裏的經驗,在涉及到"web 3.0"和HTML5時代的多種技術術語的時候,還有其他重要的經驗教訓值得汲取。上面的分析直接適用於寫jQuery代碼。在搗騰 canvas 的時候這些原則也很重要,另外儘量保持幀頻在30-60的範圍內。

不要用微秒來產生唯一的ID

自打web開發早期開始,就流行一種產生唯一ID的方法。具體做法是把從1970年1月1日開始計算的微秒數加到你的靜態ID後面,如下所示:

var myID = "static" + new Date().getTime();

這本來是相當萬無一失的方法,因爲即便兩段這樣的代碼連續執行,在它們執行的間隙也會有幾毫秒。可是現在新的瀏覽器帶着新的Javascript引擎,伴隨着一直在提升的主頻。到現在,上面的代碼產生相同的毫秒數的可能性會比產生間隙的可能性更大。

這會導致傳統方法難以debug的bug。因爲你的DOM是在運行中創建的,對頁面源代碼進行傳統的測試無法確定多個重複ID的錯誤。Javascript和jQuery的錯誤處理機制會把第一個匹配的作爲ID並忽略其他的重複ID。所以它甚至都不會拋出JS錯誤!

這樣不行,唯一真正的方法是逐行設斷點和log,但是如果斷點的位置不對,你的毫秒數又不會衝突了!

好消息是有很多產生唯一ID的替代方法。學究氣一點的說法是,計算機的隨機數函數其實並不是真正隨機的,因爲它還是來源於系統時間,雖然這一點值得注意,但是隨機數衝突的可能性是微乎其微的。

var myID = "static" + Math.round(Math.random() * 10000);

我個人更偏愛人工產生GUID方法。從技術角度說,GUID是根據你的硬件創建的,不過下面的Javascript函數做得相當棒。這是我從 stack overflow 的一個帖子 裏偷來的,相當順手的一個函數。

function S4() {
   return (((1+Math.random())*0x10000)|0).toString(16).substring(1);
}
function guid() {
   return (S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4()+S4());
}

var myID = "static" + guid();

檢測特性,而不是檢測瀏覽器類型

用戶的瀏覽器是否支持地理信息?用戶的瀏覽器是否支持web works?HTML5 視頻?HTML5 音頻?答案曾經是這樣的:

if ($.browser.msie) {
    // 哦,是IE啊,那肯定不支持
}

但是世界在快速變化。最新版的IE幾乎能算是現代瀏覽器了,但它依舊給前端開發帶來痛苦。更早版本的IE基本上和它之前的版本一樣爛,這就讓偷懶的Javascript程序員習慣於檢測 if (ie) 然後執行一些微軟專用的破語法。現在IE9已經廢棄了這些專用函數,那些原來的 if (ie) 老古董就反而會壞事了。

那麼,如果能檢測每個特性而不用檢測(既不可靠又能僞裝的)user-agent,你覺得咋樣?

如果你的回答是 "那相當靠譜", 那你就說對了。

 Modernizr 吧,這是行業夢幻級大師Paul Irish參與開發的一個Javascript庫。該庫集廣泛應用、輕量級和海量文檔三大優勢於一身,實施無需動腦,實爲居家旅行、殺人滅口必備。它會產生一個 Modernizr 對象,其中包含了它所有檢測測試的結果,這樣檢測某個特性的支持與否就和下面的例子一樣簡單:

// 檢測瀏覽器是否支持canvas的老辦法
if (!!document.createElement('canvas').getContext) { ... }

// 用 Modernizr 檢測
if (Modernizr.canvas) { ... }

使用可讀的毫秒數

毫秒數的一種方便的寫法是寫成可讀的。對於初學者這很棒,但是大部分情況下其實只是一個噱頭。

// 這是3秒,30秒還是300秒啊?
var timeout = 30000;

// 增加了額外的計算開銷,但是讀和修改會更容易
var timeout = 30 * 1000;

關於jQuery代碼

像瘋狗一樣串接

jQuery最好的特性之一就是它的函數串接。你可能已經用過一點,也許把一些簡單的調用一個接一個串起來...但是你是否曾經像頭瘋狗一樣在DOM裏上躥下跳地遍歷呢?還是花點時間來熟悉一下 .end() 函數。等你從起始選擇器開始在DOM裏上躥下跳的時候,這個函數會很關鍵。

$(".quote")
    .hide()
    .find("a").text("Click here").bind("click",doStuff).end()
    .parent().removeClass().addClass("testimonial").draggable().end()
    .fadeIn("slow");

上例中,每次我們完成對某個DOM對象的操作,要反向遍歷DOM返回我們引用的原始對象的時候,就需要使用.end() 函數。然後我們就順藤摸瓜扎回原來DOM裏的位置了。

使用 data-* 屬性

你們當中那些已經寫了很長時間Javascript(原生的,不是jQuery)代碼的同學,很可能都熟悉各種屬性吧。你們想辦法設置它們,獲取它們,或者濫用 rel  title ...

別說HTML5 或者 jQuery 沒幫上忙哦。新的描述中允許在HTML元素中使用 data- 前綴來指明包含數據的屬性,jQuery會把指定的字符串轉換成正確的Javascript數據類型,這活幹的非常漂亮。我們來創建一個帶有某些數據屬性的 DIV 

<div id="test" data-is-bool="true" data-some-number="123"></div>

現在,即使我們的值被包裝在引號裏面,它們也不會被當做字符串處理:

typeof $("#test").data("isBool"); // boolean

typeof $("#test").data("someNumber"); // number

特殊的大小寫

要注意,要讓這些代碼片段正常工作,(HTML裏的屬性定義)必須使用小寫字母,這很重要。不過如果你是一位很強的前端開發者,你還是會想用駝峯法來命名你的數據變量。正如在Javascript裏很多地方出現的,前置的連接符意味着下一個字母會適用於駝峯法大寫。不過,下面的例子裏在HTML屬性定義中使用駝峯法是 不行的 ,會讓上面的Javascript代碼返回 undefined

不好使 :(

<div id="test" data-isBool="true" data-someNumber="123"></div>

好使 :)

<div id="test" data-is-bool="true" data-some-number="123"></div>

'.stop()' 停止協作和監聽

把jQuery動畫效果和鼠標事件綁定是基於web的現代用戶交互方式中的關鍵部分,可是這方面即便某些最有名的網站也做得很蹩腳。這篇文章 提供了一個實現動畫的直接例子並且演示了這些動畫放在一起在視覺上會產生多麼不和諧的效果。 好在這個問題可以利用一個函數前綴或在 $.animate 調用中加入一個參數來輕鬆解決。

在使用 $.animate 的時候, 可以在參數中加入 queue: false 來避免串接。諸如 $.fadeIn  $.slideDown 這樣的動畫快捷方式不接受 queue 設置,你必須用 $.stop 這個方法預先停止這些動畫.。 在特定的場景下,需要某個動畫直接停下,或跳轉到變換的最終狀態。推薦你先熟悉有關 clearQueue  jumpToEnd 這兩個參數的相關 文檔 ,因爲老天在上,我沒有其他辦法幫你。

$("selector").stop(true,true).fadeOut();

$("selector").animate({
    property: value
}, {
    duration: 1000,
    queue: false
}

優化你的選擇器

jQuery 很高冷。它幾乎無所不能,不過它目前還沒法給你衝咖啡,我聽說在2.0版的路線圖裏有才這個特性。你需要當心的一件事是別濫用它的 sizzleJS 選擇器引擎的能力。想避免這種問題可以有兩個策略:緩存選擇器結果以及 使用高效率的選擇器

緩存選擇器結果

是每次你要修改一點東西的時候都先進行開銷巨大的DOM查詢,還是保存一份元素的索引?選擇一目瞭然。

// before
$(".quote a").bind("click", doStuff); // DOM查詢

// now
$(".quote a").addClass("quoteLink"); // DOM查詢!!

// later
$(".quote a").fadeIn("slow"); // 又一次DOM查詢!!!

忽略串接,這樣做更好:

// before
var $quoteLinks = $(".quote a");  // 只需一次DOM查詢
$quoteLinks.bind("click", doStuff);

// now
$quoteLinks.addClass("quoteLink");

// later
$quoteLinks.fadeIn("slow");

使用高效率的選擇器

好了,jQuery/sizzleJS 可以輕鬆使用CSS3選擇器,但是真正的開銷是什麼? 在這種場景下瀏覽器有可能會使用document.querySelector(), 但是它也有可能分拆你的選擇器字符串,然後手工去查詢DOM。

// ID搜索是最快的查詢方式,然後它獲取孩子節點的列表,匹配其中class爲'quotes'的元素
$("#quoteList").children(".quotes");

// 只在預先確定的bar元素下查找'foo'class
$(".foo",bar); 

'for' 循環總是比 'each()' 循環快

不管未來幾年在瀏覽器開發領域會發生什麼,本地的 for 循環永遠會比jQuery的 $.each() 循環快。 當你思考jQuery到底是什麼(把本地JS函數包裝起來的一個庫)這種高大上問題的時候,你就會開始認識到本地原生Javascript代碼永遠會更快。用庫還是用原生,這是一個運行速度和編程速度之間的權衡。

很重要的一點是,對那些可能每秒調用數百次的性能關鍵的函數,總是要使用 for 循環。例如:

  • 鼠標移動
  • 時間間隔
  • 循環內部的循環

CSS

理解盒子模型是關鍵

"盒子模型"對於理解瀏覽器如何渲染頁面是關鍵性決定性的因素。對其複雜性的全面理解有助於奇蹟般地簡化你的工作。盒子模型描述了對HTML元素的物理維度進行計算的方式。如果一個塊元素具有固定寬度,比如說100px,那麼應該如何確定它的 padding, border 和 margin 呢?

很多網站都有深入的描述,但咱們簡單點說:在遵循標準的瀏覽器中,border和padding是被放在指定寬度之外的。最好是用圖形來解釋。比如下列代碼:

.foo {
    width: 150px;
    height: 150px;
    padding: 25px;
    border: 25px solid;
    margin: 20px;
}

你可能估計的情況(Quirks 模式)

padding 和 border 都是往裏算的,結果保持高度和寬度都是150px。

你看到的情況(遵循標準模式)

可是,實際上出來的寬度和高度都是250px。 也就是150px + (2 * 25) + (2 * 25)。

如果你覺得這個結果很奇怪,那你不是一個人(呃,你是人,只是說還有其他人也會這麼想)。 現在手頭有個修復辦法,需要引入一個CSS屬性叫 box-sizing,這個屬性對於 IE8 及以上版本 都適用。它允許你選擇計算元素維度的確切方式,這樣就能救你於危難之中。具體支持的參數因瀏覽器而異,另外需要用到瀏覽器廠商的前綴,具體細節請參閱 caniuse.com 

/* 舊方法 (178 + 20 + 2 = 200) */
.foo {
    width: 178px;
    padding: 10px;
    border: 1px;
}

/* 更好的方法 */
.foo {
    width: 200px;
    padding: 10px;
    border: 1px;
        -webkit-box-sizing: border-box;
        -moz-box-sizing: border-box;
        box-sizing: border-box;
}

雖然說你也總是可以對寬度進行心算,在各個像素數中減來減去(就像第一個方法做的那樣),但在涉及到不同的寬度單位時(比如百分比或者EM),就沒人能搞清楚到底該怎麼做了。目前,除了把元素包在父元素中,以確保寬度和 padding/margin/borders 可以全部分開之外,也沒有別的解決辦法。

知道什麼時候用float,什麼時候用position

用table進行佈局的時代過去了。現在要承認我們可以集中精力去理解float和position的工作原理。這裏需要掌握一套特別的思維模型,我相信這件事最好是通過動手練習來進行。

用float從DOM中提取元素並強制它們靠到左邊或右邊,那是相當靠譜。它們已成爲前端開發的後table佈局時代的萬金油,這可能是因爲以前瀏覽器對於 display: inline  inline-block 的支持不力,還有對position的支持中冒出的 z-index bug。可現在就真的沒有藉口了。 inline-block 已經支持得很好了,簡單的一點修正就能讓它在 IE7 裏應用。

謝天謝地,以前那些阻撓用CSS對元素進行絕對定位的爭論都消亡了。理論上,定位屬性可以讓你在頁面上以X和Y座標放置元素,這種方式簡單直接,Flash開發者們都應該很熟悉。

理解 Position

用CSS定位元素的時候,理解一個事實非常重要:定位的位置總是相對於離它最近的有定位屬性的父元素而言的。人們剛開始用CSS的時候會有一個常見的誤解,認爲 position: absolute; 是相對頁面的root元素定位的。 我覺得這種誤解來源於某些情況下,元素沒有任何父元素具備position樣式 -- 在這種情況下,他們的結論是對的。這樣向上遍歷DOM樹沒有找到任何有定位樣式的元素,就會定位到頁面的根元素上。

那麼,如果 position: absolute; 是把元素從他們所在的流中抽取出來,那你如何相對一個元素的父元素對它進行定位呢? 方法很直接。父元素需要定義 position: relative; 樣式,然後所有的孩子元素就會按上、右、下、左的順序依次擺放。利用這個知識,你會如何實現下面很直觀的佈局呢?

使用 float,你會需要把這些元素包在一個父元素中, 然後把.one float靠左,然後改動 .two  .three  float margin 。最後你應該寫出類似下面的東西:

.parent {
    /* ghetto clearfix */
    width: 310px;
    overflow: auto;
}
.one {
    width: 200px;
    height: 210px;
    float: left;
}
.two {
    width: 100px;
    height: 100px;
    float: right;
    margin-bottom: 10px;
}
.three {
    width: 100px;
    height: 100px;
    float: right;
}

正如我們前面所說,使用 position 讓我們可以用很明確的方式,按照 X 和 Y 座標把元素顯示在屏幕上。 上面用float的方式會把頁面上的長文字隔開,下面的方法則可以確保所有元素處於正常位置,無論頁面上有什麼內容。

.parent {
    position: relative;
    width: 310px;
    height: 210px;
}
.one, .two, .three {
    position: absolute;
}
.one {
    top: 0;
    left: 0;
    width: 200px;
    height: 210px;
}
.two {
    top: 0;
    right: 0;
    width: 100px;
    height: 100px;
}
.three {
    bottom: 0;
    right: 0;
    width: 100px;
    height: 100px;
}

如前文所述,有些 z-index 的問題需要考慮。雖然上面的例子可能顯得有點過分,不過一旦你開始思考定位,它會打開一個各種可能性的新世界.

留空

如果我們在單行和多行CSS參數的格式之間變來變去,CSS裏的留空也會不一樣。我不打算對這個說太細。

合適的空白

/* 不好 */
.selector {display:none;background:#ff0000;color:#000000;}

/* 好 -- 單行 */
.selector { display: none; background: #ff0000; color: #000000; }

/* 好 -- 多行 */
.selector {
    display: none;
    background: #ff0000;
    color: #000000;
}

大括號不換行

.selector {
    display: none;
    background: #ff0000;
    color: #000000;
}

子元素縮進

這個用不用就見仁見智了,我個人只會在單行定義的CSS文檔中用這種格式。

.selector { display: none; background: #ff0000; color: #000000; }
    .selector a { text-decoration: none; }
    .selector span { font-weight: bold; }

組合並縮進瀏覽器廠商前綴屬性

.selector {
    background: #FFF; border: 1px solid #000; color: #EAEAEA;
        -webkit-border-radius: 3px;
        -moz-border-radius: 3px;
        border-radius: 3px;
}

CSS 速記格式

屬性分組

把屬性分組到一起是大大減少CSS文件大小的最有效方法。這裏很重要的一點是要理解屬性是如何排序的(順時針 -- 上,右,下,左),以及如何進一步縮短它們(上和下,左和右)。

/* 逐個定義,太長了 */
padding-top: 1px;
padding-right: 2px;
padding-bottom: 1px;
padding-left: 2px;

/* 上,右,下,左,好很多 */
padding: 1px 2px 1px 2px;

/* 上和下,左和右,最優 */
padding: 1px 2px;

從 0px 到英雄

給值爲 0 的屬性分配一個單位類型是多餘的。一個元素是距離左邊 0px 還是 0 elephants 根本不重要,只要知道它是貼着左邊就行了。

/* 不好 */
padding: 0px 10px;

/* 好 */
padding: 0 10px;

註釋塊

對於在一個樣式表裏維護多個樣式區域的任務,給大段CSS加上註釋是很好的辦法。顯然這和單行CSS風格配合使用效果更佳,不過這個效果在多行CSS風格里也不是完全沒用。註釋裏用破折號、等號還是下劃線起強調作用就見仁見智了,不過下面是我喜歡的方式:

/* === HORIZONTAL NAV === */
#horizNav { width: 100%; display: block; }
#horizNav li { display: block; float: left; position: relative; }
#horizNav li a { display: block; height: 30px; text-decoration: none; }
#horizNav li ul { display: none; position: absolute; top: 30; left: 0; }

/* === HOME PAGE - CAROUSEL === */
#carousel { width: 960px; height: 150px; position: relative; }
#carousel img { display: none; }
#carousel .buttons { position: absolute; right: 10px; bottom: 10px; }

清除浮動

清除一個 <div> 過去意味着額外的DOM,因爲這會涉及到增加一個額外的清除元素。更好的辦法是給父元素設置明確的寬度('auto'並不是在所有瀏覽器和場景中有效)以及把overflow屬性設爲'auto'或者'hidden'。'hidden'顯然兼容性更好,但在某些兼容IE的版本里'auto'的效果好一些。

HTML:

<div class="parentElement">
    <div class="childElement">
        I'm floated left!
    </div>
    I'm normal text that wraps around the float
</div>

CSS:

.parentElement {
    width: 100%;
    overflow: hidden;
}
.childElement {
    float: left;
}

有本項目的貢獻者提醒我注意最新的clearfix。 micro clear-fix 被認爲相當穩定且跨瀏覽器兼容,足以列入最新的HTML5 boilerplate發佈了。 我 強烈 建議你去看看。雖然我不是瀏覽器特定CSS和 :after 這種僞元素的狂熱粉絲,不過這個micro clearfix的確更健壯。它還能避免頂層margin縮回的問題。

垂直和水平居中

水平居中元素實際上不是什麼高精尖的科技,我敢肯定你們大部分人都熟悉下面的代碼片段:

.class {
    width: 960px;
    margin: 0 auto;
}

前端開發者們使用這種代碼很長時間了,也沒搞明白爲什麼這種方式對垂直居中不起作用。從我的理解說,很重要的一點是記住父元素一般會有個 height: auto; 樣式, 也沒有垂直居中元素所需的100%高度。而應用position: absolute; 能有效地把元素轉移到定位模式,然後被設爲auto的margin會自動幫助它調整位置,達到居中效果。

.exactMiddle {
    width: 100px;
    height: 100px;
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    margin: auto;
}

這種方法的不足之處包括在 IE6 和 IE7 中缺乏支持,以及當瀏覽器被縮小到比居中對象還小時不出現滾動條。 在這個網頁 裏列出了更多的方法(現在這個是第4個),不過現在這個目前是最優方法。

在一個元素裏垂直居中文字也是很直接的。如果文字是單行的,例如一個水平導航元素,你可以設置 line-height爲該元素的物理高度。

#horizNav li {
    height: 32px;
    line-height: 32px;
}

檢測特性,而不是檢測瀏覽器類型

在前面關於Javascript特性檢測的討論中,檢測到瀏覽器是 任何版本 的 IE 然後就運用某些屬性的做法越來越成問題了。鐵人 Paul Irish 引領了使用 IE 版本檢測 方法來解決這些問題的大潮,但是 Modernizr 從那時起拯救了我們。 Modernizr 在 root <html> 元素裏放入一些class,描述某些特性是否得到支持. 然後前沿的樣式就可以很容易從這些class級聯出來或者刪除掉。

.my_elem {
   -webkit-box-shadow: 0 1px 2px rgba(0,0,0,0.25);
   -moz-box-shadow: 0 1px 2px rgba(0,0,0,0.25);
   box-shadow: 0 1px 2px rgba(0,0,0,0.25);
}

/* 如果 box shadow 不支持, 就應用 borders 屬性 */
.no-boxshadow .my_elem {
   border: 1px solid #666;
   border-bottom-width: 2px;
}

!important 不要亂用

依賴於 !important 標籤是個危險的現象。非用它不可的情況屈指可數,而且是特殊情況。這些情況大抵是需要覆蓋另外一套樣式表,而你沒法或者沒權限編輯它。另一個場景是對元素的樣式硬編碼以防止Javascript在線產生的樣式有更大優先級。而實際情況是 !important 往往被用做偷懶的快捷方式,讓某個樣式優先於其他的樣式,這樣做將來會產生很多問題。

 !important 標籤的大部分使用是可以避免的,只要更好地理解CSS選擇器優先級以及如何更準確地定位元素。選擇器越具體,被接受爲適用樣式的可能性就越大。下面來自 vanseodesign 的例子展示了具體化起作用的情況。

p { font-size: 12px; }
p.bio { font-size: 14px; }

關於樣式優先級, 他們的文章 在解釋繼承性方面比我能寫出來的文章都好,所以請給它點個贊吧。

進取性向下兼容

值得注意的是,這段是我的個人觀點,只適用於特定情況。在依賴老版本瀏覽器的大型商業項目或企業級解決方案中,進取性向下兼容的立場將不容易被接受。

進取性向下兼容的意思是如果某個特定的(老版本)瀏覽器無法渲染某個特定效果,則應直接忽略它。CSS3 按鈕就是一個好例子。諸如 border-radius box-shadow text-shadow  gradients 這些效果會在先進的瀏覽器裏顯示出來。對於版本稍微老一點的瀏覽器,可以用一個 .PNG 圖片作爲無傷大雅的補救辦法,而所有解決辦法中最優雅的應該是針對IE6提供一個PNG-Fix,或者用filter 參數來代替 gradients 和 shadows等屬性。 不過,在這種情況下,進取性向下兼容方式會讓你忽略老版本瀏覽器,而在其中展示一個平面的還過得去的對象。

簡單地說,進取性向下兼容說白了就是:如果你的瀏覽器渲染不了漸變色或盒子陰影,那是你運氣不好

雖然這不是對所有情況都理想,這種方法能確保項目按時交付,且核心產品是可用的,而不需依賴於對瀏覽器的破解辦法。


CSS3及HTML5

這個話題我想我已經說的夠多了。用 Modernizr 來檢測特定的 HTML5 和 CSS3 特性是否可用。

@font-face的使用和濫用

在你考慮嵌入一套定製的字體之前,很重要的一點是你要查看 EULA 並檢查是否允許web嵌入。 字體庫廠商自然是不願意讓設計師和開發者有能力把字體庫文件直接存放在服務器上,然後被熟練的終端用戶拷貝走。某些廠商也禁止嵌入特定的文件類型,例如 .TTF  .OTF

如果,經過慎重考慮,你相信想要的字體是可嵌入的,那就去看一下Font Squirrel的 @font-face 生成器。 它利用了 Fontspring的 防彈 @font-face 結構 並能自動生成所有需要的文件格式。

向下兼容

謝天謝地,瀏覽器對於它不支持的HTML5 和 CSS3 特性的處理已經達到了優雅的本色。加到 <input /> 標籤的新類型例如 "email", "search" 等等在瀏覽器本地不支持的情況下一般會向下兼容爲正常的 <input type="text" />。 類似的,不支持的CSS3 特性就不會出現,由高度和寬度媒體查詢控制的響應式佈局也不會被應用。

精巧的CSS3效果應該被應用爲對使用現代瀏覽器的用戶的一種獎勵。

在下面的"資源"小節裏包括了一些有助於讓HTML5和CSS3功能在一批老版本瀏覽器中保持正常的庫。


資源

下列的資源對於代碼標準化和現代web頁面的互動是很重要的。它們確保了CSS3 和 HTML5 特性在以前缺乏支持能力的一批瀏覽器中能夠使用。

本文由 Tait Brown 編寫 (@taitems),此中文版由 coderLMN 翻譯。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章