偶然發(fā)現(xiàn)我很少寫jquery相關(guān)的文章。作為一名前端從業(yè)人員,jquery可以說一門必備的技能,能夠熟練使用jquery是每一個前端開發(fā)者應(yīng)該掌握的。不過這篇文章我并不打算寫成從零開始的教程式文章,僅僅闡述一些使用jquery需要注意的一些內(nèi)容。
前幾日有幸讀到一篇文章,講得就是使用jquery的一些周邊知識,包括一些相關(guān)的最佳實(shí)踐,感覺受益匪淺。原文地址在這里,這篇文章應(yīng)該寫了有一段時間了,不過可喜的是,作者并沒有棄之不理,反而在最近有修改過相關(guān)內(nèi)容。所以我決定索性翻譯此篇文章以作備忘(本文的翻譯并非完全按照原文照字照句翻譯的,在某些位置做了內(nèi)容合并以及應(yīng)用了一些活躍的修飾手法,不過不影響原文的含義)。
譯文開始。
在使用jquery時,加載jquery會有一些需要注意的小技巧,下面聽我一一道來。
使用cdn總會有一些益處,而且又不要錢,不用白不用。
不過,萬一你用的cdn在關(guān)鍵時候掉鏈子,那就不好玩了。所以我們還需要整點(diǎn)小技巧來防著這一手。
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js" rel="external nofollow" ></script>
<script>
window.jQuery || document.write('<script src="js/jquery-2.1.1.min.js" type="text/javascript"><\/script>')
</script>
這里稍微解釋一下。上面代碼中第一個<script>
標(biāo)簽的作用比較顯目,就是加載cdn提供的jquery資源。而第二個<script>
標(biāo)簽中的代碼就是為了防止cdn掛了而作出的特別處理。其中的javascript代碼使用了短語判斷,若瀏覽器環(huán)境已經(jīng)存在window.jQuery
變量則什么也不做;否則動態(tài)的插入一個<script>
標(biāo)簽來加載自己服務(wù)器上的jquery資源。
所以,看起來很普通的jq文件加載過程,也是有不少門道的。
我們接著往下看。
出現(xiàn)了一個不太常見的詞,裸協(xié)議。啥意思呢?很簡單就是說去掉url中的http:
或https:
,就想前面的代碼塊中一樣。關(guān)于裸協(xié)議的更多解釋可以參閱這里。
譯者注:經(jīng)過譯者多方資料的查閱,發(fā)現(xiàn)這一點(diǎn)不再是一個推薦的做法。如果你的頁面使用的是https協(xié)議(需要SSL),那么你在html中引用外部cdn資源時,最好還是帶上
https://
協(xié)議。
盡可能的將你的js代碼和jquery引用代碼放在頁面的底部。為什么應(yīng)該這么做呢?簡單來說,如果js代碼塊在html的上部的話,就會阻塞頁面主體內(nèi)容的加載。一般我們都是將js代碼塊,包括js代碼的引用代碼都是放在</body>
標(biāo)簽之前。
關(guān)于這一點(diǎn),原文給出了一篇參閱文章。此外,開源屆還有一個非常流行的項(xiàng)目,html5-boilerplate是一個非常專業(yè)的前端起手模板,可以多參考參考。
眾所周知,jquery有很多的版本,特別地,jquery1.x.x版本和jquery2.x.x版本存在著非常大的差異。那么我們實(shí)際在生產(chǎn)環(huán)境中針對如此多的不同版本究竟要如何抉擇呢?下面是幾個推薦的考慮要素,
1.9.2
。如果你的項(xiàng)目中除了jquery之外,還使用了諸如Prototype,MooTools,Zepto等等,此時你應(yīng)該需要謹(jǐn)慎一點(diǎn),因?yàn)檫@些類庫也使用$
作為他們的暴露接口。此時我們可以直接使用jQuery
。并且在存在多個類庫接口沖突隱患時,我們應(yīng)該調(diào)用類庫提供的$.noConflict()
來避免沖突。
因?yàn)閖query1.9.x之后的版本中,取消了$browser
轉(zhuǎn)而提供一個用于特性檢測的變量$.support
,而且jquery官方的言下之意是,這個變量是jquery內(nèi)部使用的,開發(fā)者們最好不要使用,同時推薦開發(fā)者們使用Modernizr來進(jìn)行特性探測。
譯者注:本博客之前也有一篇討論瀏覽器特性探測的文章,若有興趣,請移步這里。
在使用jquery變量時,我們會有一些約定成俗的不成文規(guī)定。
往往我們會為了效率或者訪問考慮,臨時緩存一些jquery變量,針對這些變量我們應(yīng)該加上一個$
前綴以示區(qū)別。
var $myDiv = $('#myDiv');
$myDiv.click(function() {
// TODO
});
通常我們會通過jquery選擇出一個dom元素,然后對dom元素進(jìn)行各式各樣的操作。這里我們一般建議先將相關(guān)dom元素的jquery對象進(jìn)行緩存,以便后續(xù)的各種操作,同時也有一個提升效率的因素在里面。
在前端界,駝峰式命名法基本上得到了所有社區(qū)的認(rèn)可。當(dāng)你需要閱讀別人代碼時,駝峰式的變量命名會讓你感覺更加親切。
一般來說,使用jquery進(jìn)行前端開發(fā)的思路都差不多。第一步選擇一個dom元素,第二步對dom元素進(jìn)行各式各樣的操作,包括事件監(jiān)聽,改變dom展示等等。
jquery的核心基本上可以說就是其選擇器。
在可能的情況下,盡量使用ID選擇器。因?yàn)樗旄踩?。(因?yàn)槠鋬?nèi)部就是封裝的原生的document.getElementById()
,不會經(jīng)過sizzle查找算法)
有的人在使用class選擇器時,喜歡帶上元素類型。其實(shí)這種做法若非必要,反而會降低選擇器的效率。
var $products = $('div.products'); // 慢
var $products = $('.products'); // 快
find()
在ID容器下查找子元素時,推薦使用find()
方法,因?yàn)檫@樣更快。其背后的機(jī)制跟前面的盡量使用ID選擇器基本一樣。因?yàn)镮D選擇器不會經(jīng)過sizzle的查找算法。
var ele = $('#products div.id'); // 慢
var ele = $('#products').find('div.id') // 快
在使用jquery進(jìn)行多級dom元素查找時,應(yīng)當(dāng)遵循這樣一個規(guī)則,即左簡右繁。啥意思呢?讓我們來看個例子。
$('div.data .gonzalez'); // 較慢
$('.data td.gonzalez'); // 較快
左簡右繁的含義,簡單來說,盡量左側(cè)的選擇器簡單,而越是靠右側(cè)的選擇器越要詳細(xì)。
再舉個具體點(diǎn)的例子。
<div class="wrapper">
<div class="left">
<h2>...</h2>
<div class="content">
<p></p>
</div>
</div>
<div class="main"></div>
<div class="right"></div>
</div>
現(xiàn)在我若想選擇.content
下的p
標(biāo)簽元素,會有許多方法。這里我們可以這么來,
var $p = $('.wrapper .left.content.p');
它會比下面這種方式要更加效率,
var $p = $('.wrapper.left.content p');
至于這是為什么?我這里不打算過多的闡述,不過可以指出的是,這跟css的解析優(yōu)先級(css會優(yōu)先解析從右側(cè)開始解析)以及jquery的sizzle引擎有關(guān)系。讀者朋友們?nèi)粲信d趣可以參閱原文給出的這篇文章或者自行查閱相關(guān)資料。
有時候我們在寫選擇器時,會不自覺的嵌套很多層。其實(shí)一葉障目,跳出來后,你可能會發(fā)現(xiàn)有時候會變得很簡單。
$(".data table.attendees td.gonzalez");
$(".data td.gonzalez"); // 省略中間多余的選擇器,提升效率
原文中還給出了簡陋的性能測試來說明這一點(diǎn)。
$('.class'); // bad case. 因?yàn)樾枰兞空麄€dom元素來查找.class元素
$('.class', '#class-container'); // good case. 因?yàn)樗粫闅v整個dom元素,僅僅在父級容器中進(jìn)行查找
因?yàn)槟:x擇器往往會帶來較多的匹配消耗,從而較低了選擇器效率。
$('div.container > *'); // BAD
$('div.container').children(); // BETTER
大多數(shù)時候,省略的選擇器其實(shí)就是*
選擇器。
$('div.someclass :radio'); // BAD
$('div.someclass input:radio'); // GOOD
經(jīng)常會看到一些jquery新手在使用id選擇器會犯一個毛病,就是混搭id選擇器和其他選擇器。其實(shí)這完全沒有必要。因?yàn)閕d選擇器已經(jīng)表示了唯一性。
$('#outer #inner'); // BAD
$('div#inner'); // BAD
$('.outer-container #inner'); // BAD
$('#inner'); // GOOD, only calls document.getElementById()
謹(jǐn)記一條,在對一個dom元素進(jìn)行大量繁雜操作之前,最好將其脫離原先的dom文檔,操作完畢之后再重新append上去。
至于為何要這么做。最核心的原因是防止在進(jìn)行操作dom元素時導(dǎo)致dom樹的頻繁更新,甚至導(dǎo)致頁面的重繪和重排,從而降低了頁面效率。更多可參考這里。
var $myList = $("#list-container > ul").detach();
// ... 針對$myList的一系列操作
$myList.appendTo("#list-container");
使用jquery時,經(jīng)常會遇到需要使用手動拼接dom元素字符串的情況。這里,應(yīng)該謹(jǐn)記一條,就是應(yīng)該在所有處理操作完畢之后再append到dom樹中,而不是處理一次就append一次。更多可參考這里。
// BAD
var $myList = $("#list");
for(var i = 0; i < 10000; i++){
$myList.append("<li>"+i+"</li>");
}
// GOOD
var $myList = $("#list");
var list = "";
for(var i = 0; i < 10000; i++){
list += "<li>"+i+"</li>";
}
$myList.html(list);
// EVEN FASTER
// [].join()方法會將所有的數(shù)組元素鏈接成一個字符串,而且速度不俗。
var array = [];
for(var i = 0; i < 10000; i++){
array[i] = "<li>"+i+"</li>";
}
$myList.html(array.join(''));
原文中還給出一個性能比較。
有時候我們通過jquery選擇器拿到的jquery對象是個空數(shù)組([]
),可能是那一塊內(nèi)容還未生成或者其他什么原因。此時我們不應(yīng)該對一個空的jquery對象進(jìn)行操作。詳情可參考這里。
// BAD: This runs three functions before it realizes there's nothing in the selection
// jquery需要運(yùn)行3個函數(shù),才會知道選擇器什么也沒拿到。
$("#nosuchthing").slideUp();
// GOOD
var $mySelection = $("#nosuchthing");
if ($mySelection.length) {
$mySelection.slideUp();
}
一個頁面只寫一個文檔ready事件的處理程序。這樣代碼既清晰好調(diào)試,又容易跟蹤代碼的進(jìn)程。
匿名函數(shù)往往會造成一些不便之處,比如不利于調(diào)試、維護(hù)、測試、復(fù)用等等。更多內(nèi)容可參考這里。
$("#myLink").on("click", function(){...}); // BAD
// GOOD
function myLinkClickHandler(){...}
$("#myLink").on("click", myLinkClickHandler);
同時,處理文檔ready的事件回調(diào)函數(shù)也盡量別用匿名函數(shù)。
$(function(){ ... }); // BAD: You can never reuse or write a test for this function.
// GOOD
$(initPage); // or $(document).ready(initPage);
function initPage(){
// Page load event where you can initialize values and call other initializers.
}
除此之外,處理文檔ready的事件回調(diào)函數(shù)應(yīng)該使用外部文件引入的方式,而且頁面中嵌入初始化調(diào)用即可。
<script src="my-document-ready.js"></script>
<script>
true// Any global variable set-up that might be needed.
true$(document).ready(initPage); // or $(initPage);
</script>
譯者注:關(guān)于這一塊內(nèi)容,譯者是不太贊同原作者的觀點(diǎn)的。
html允許直接在html標(biāo)簽的屬性中添加js相關(guān)的邏輯處理代碼。這雖然帶來了方便,但是更多的是給調(diào)試帶來了不便。我們應(yīng)該總是走jquery先選擇后綁定這個路子。
<a id="myLink" href="#" onclick="myEventHandler();">my link</a> <!-- BAD -->
$("#myLink").on("click", myEventHandler); // GOOD
如果可以,我們可以設(shè)計(jì)一套專門用于區(qū)分事件的命名空間。這樣利于維護(hù)且不會對其他事件造成影響。
$("#myLink").on("click.mySpecialClick", myEventHandler); // GOOD
// Later on, it's easier to unbind just your click event
$("#myLink").unbind("click.mySpecialClick");
當(dāng)你需要給相似的元素添加同一個事件處理時,你就需要用到j(luò)query的事件代理了。所謂事件代理,說簡單點(diǎn)就是給系列元素的父元素添加事件監(jiān)聽,而且事件真正觸發(fā)在其子元素上。
$("#list a").on("click", myClickHandler); // 不好。因?yàn)橄喈?dāng)于添加了多次相同的事件
$("#list").on("click", "a", myClickHandler); // 不錯。使用事件代理,在父元素上添加事件,在子元素上觸發(fā)
jquery內(nèi)置了ajax方法,提供XMLHTTPRequest服務(wù),并且屏蔽了不同瀏覽器對XMLHTTPRequest的差異支持。
$.ajax()
有的人喜歡使用$.get()
,$.getJson()
等方法,不過jquery內(nèi)部任然是將其轉(zhuǎn)換成$.ajax()
譯者注:關(guān)于這一點(diǎn),譯者是不太贊同原作者的觀點(diǎn)的。
在https站上不要使用http請求,最好是請求時別指定請求協(xié)議。(將http或者h(yuǎn)ttps從你的url中移除)
不要在請求url中附帶參數(shù)。$.ajax()
提供有專門參數(shù)用于傳遞參數(shù)。后者更加可讀。
// Less readable...
$.ajax({
url: "something.php?param1=test1¶m2=test2",
....
});
// More readable...
$.ajax({
url: "something.php",
data: { param1: test1, param2: test2 }
});
發(fā)起ajax請求時,最好都指定請求返回的數(shù)據(jù)類型(dataType)。
使用事件代理,以便ajax動態(tài)加載出來的內(nèi)容仍然能夠觸發(fā)之前的事件綁定。更多內(nèi)容可參考這里。
$("#parent-container").on("click", "a", delegatedClickHandlerForAjax);
使用Promise模式來處理ajax不同情況的返回。點(diǎn)我查看更多的示例。
$.ajax({ ... }).then(successHandler, failureHandler);
// OR
var jqxhr = $.ajax({ ... });
jqxhr.done(successHandler);
jqxhr.fail(failureHandler);
下面是一個通用的使用jquery發(fā)送ajax請求的模板。更多的說明可參考這里。
var jqxhr = $.ajax({
url: url,
type: "GET", // 默認(rèn)是GET,不過使用其他http動詞
cache: true, // 默認(rèn)是true,不過當(dāng)dataType為script和jsonp時為false
data: {}, // 請求參數(shù)
dataType: "json", // 最好指明請求返回的數(shù)據(jù)類型,默認(rèn)為json
jsonp: "callback", // 指定回調(diào)處理JSONP類型的請求
statusCode: { // 如果你想處理其他的錯誤,可以在這里指明各錯誤碼對應(yīng)的回調(diào)函數(shù)
404: handler404,
500: handler500
}
});
jqxhr.done(successHandler);
jqxhr.fail(failureHandler);
下面是兩點(diǎn)關(guān)于jquery動畫的通用建議。
jquery支持鏈?zhǔn)秸Z法。很多時候我們可以使用鏈?zhǔn)秸Z法將一系列操作串起來。
除了使用臨時變量緩存jquery對象之外,我們還可以使用鏈?zhǔn)秸{(diào)用。
$("#myDiv").addClass("error").show();
此外,當(dāng)鏈?zhǔn)秸{(diào)用多達(dá)3次以上或代碼因綁定回調(diào)略顯復(fù)雜時,使用換行和適當(dāng)?shù)目s進(jìn)來提高代碼的可讀性。
$("#myLink")
.addClass("bold")
.on("click", myClickHandler)
.on("mouseover", myMouseOverHandler)
.show();
當(dāng)然,針對特別長的鏈接調(diào)用,最好還是使用臨時變量緩存一下的好。
// BAD, 調(diào)用了3此attr()方法
$myLink.attr("href", "#").attr("title", "my link").attr("rel", "external");
// GOOD, 只調(diào)用了一次attr()方法
$myLink.attr({
href: "#",
title: "my link",
rel: "external"
});
推薦的方式是,以css class為單位去操作dom元素,而不是直接在dom元素上添加移除css屬性。
$("#mydiv").css({'color':red, 'font-weight':'bold'}); // BAD
error { color: red; font-weight: bold; } /* GOOD */
$("#mydiv").addClass("error"); // GOOD
這點(diǎn)沒什么好說的。我們需要關(guān)注所使用的具體版本以及官方的changlog即可。這里有一份廢棄方法的列表。
在適當(dāng)或者需要的時候,還是可以使用一些原生js代碼的。這里有一份原文給出的相關(guān)性能測試。
譯者注:關(guān)于這一點(diǎn),大家看看就好了,不必較真。在一些js代碼量較大的項(xiàng)目中,是不太會允許這里一坨原生代碼那里一坨原生代碼的。不然維護(hù)的成本會越來越大。性能這一塊,其實(shí)說到底還是得看場景和需求,再一個就是平衡點(diǎn)的把握。
更多建議: