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