国产一级a片免费看高清,亚洲熟女中文字幕在线视频,黄三级高清在线播放,免费黄色视频在线看

打開APP
userphoto
未登錄

開通VIP,暢享免費(fèi)電子書等14項(xiàng)超值服

開通VIP
JavaScript的優(yōu)化處理

在一個(gè)討論web技術(shù)的網(wǎng)站vitamin上發(fā)現(xiàn)這篇《Serving JavaScript Fast》,讀過之后大有收獲,茅塞頓開。于是就有了翻譯過來的念頭——我這人有個(gè)毛病,看到有意思的英文文章,就想自己翻過來(雖然英文水平很爛)。先在網(wǎng)上查了查,已經(jīng)有blog談到這篇文章(我算是后知后覺了),有總結(jié)要點(diǎn)的《Flickr 的開發(fā)者的 Web 應(yīng)用優(yōu)化技巧》,也有延伸開來的《接著講Flickr的八卦》,但似乎沒有全文翻譯的(這下就好,不會(huì)忙了半天發(fā)現(xiàn)是無用功)。之后,就寫信問作者可不可以,作者一口答應(yīng):“sure - i’d love you to translate it”,只是要求我翻好之后給他一個(gè)鏈接地址。得到準(zhǔn)許,心里就有底了。

先介紹一下作者。Cal Henderson,倫敦人,現(xiàn)居加利福尼亞的舊金山。PHP,MySQL和Perl專家,現(xiàn)任flickr架構(gòu)師(flickr被收購后就在yahoo了),同時(shí)也是vitamin的特聘顧問(寫些技術(shù)性文章)。

既然他是架構(gòu)師,flickr用的應(yīng)該就是文中談到的這些技術(shù),于是參照文章,再對(duì)比網(wǎng)站,種種跡象表明確實(shí)如此。雖然在中國訪問flickr速度不敢恭維,加速效果不得而知,但其用了n多css和javascript資源卻似乎從沒出過什么問題,也從側(cè)面印證了這些技術(shù)的有效性。

仔細(xì)的看完文章,還有個(gè)強(qiáng)烈的感覺:這老兄也太能賣關(guān)子了,一句話非分成三句說,擺事實(shí)講道理是夠透徹,就是有點(diǎn)太@#$%了…… 算了,他怎么說我怎么翻吧,忠實(shí)于原著嘛,要不就成篡改了。經(jīng)過幾天努力,加上同事thincat兄傾力援手(小弟不勝感激?。?,終于完工(@_@ 真是苦力活啊,我再也不想干了~)。

全文翻譯如下:

讓javascript跑得更快

作者:Cal Henderson

下一代web應(yīng)用讓javascript和css得堪大用。我們會(huì)告訴你怎樣使這些應(yīng)用又快又靈。

建立了號(hào)稱“Web 2.0”的應(yīng)用,也實(shí)現(xiàn)了富內(nèi)容(rich content)和交互,我們期待著css和javascript扮演更加重要的角色。為使應(yīng)用干凈利落,我們需要完善那些渲染頁面的文件,優(yōu)化其大小和形態(tài),以確保提供最好的用戶體驗(yàn)——在實(shí)踐中,這就意味著一種結(jié)合:使內(nèi)容盡可能小、下載盡可能快,同時(shí)避免對(duì)未改動(dòng)資源不必要的重新獲取。

由于css和js文件的形態(tài),情況有點(diǎn)復(fù)雜。跟圖片相比,其源代碼很有可能頻繁改動(dòng)。而一旦改動(dòng),就需要客戶端重新下載,使本地緩存無效(保存在其他緩存里的版本也是如此)。在這篇文章里,我們將著重探討怎樣使用戶體驗(yàn)最快:包括初始頁面的下載,隨后頁面的下載,以及隨著應(yīng)用漸進(jìn)、內(nèi)容變化而進(jìn)行的資源下載。

我始終堅(jiān)信這一點(diǎn):對(duì)開發(fā)者來說,應(yīng)該盡可能讓事情變得簡(jiǎn)單。所以我們青睞于那些能讓系統(tǒng)自動(dòng)處理優(yōu)化難題的方法。只需少許工作量,我們就能建立一舉多得的環(huán)境:它使開發(fā)變得簡(jiǎn)單,有極佳的終端性能,也不會(huì)改變現(xiàn)有的工作方式。

好大一沱

老的思路是,為優(yōu)化性能,可以把多個(gè)css和js文件合并成極少數(shù)大文件。跟十個(gè)5k的js文件相比,合并成一個(gè)50k的文件更好。雖然代碼總字節(jié)數(shù)沒變,卻避免了多個(gè)HTTP請(qǐng)求造成的開銷。每個(gè)請(qǐng)求都會(huì)在客戶端和服務(wù)器兩邊有個(gè)建立和消除的過程,導(dǎo)致請(qǐng)求和響應(yīng)header帶來開銷,還有服務(wù)器端更多的進(jìn)程和線程資源消耗(可能還有為壓縮內(nèi)容耗費(fèi)的cpu時(shí)間)。

(除了HTTP請(qǐng)求,)并發(fā)問題也很重要。默認(rèn)情況下,在使用持久連接(persistent connections)時(shí),ie和firefox在同一域名內(nèi)只會(huì)同時(shí)下載兩個(gè)資源(在HTTP 1.1規(guī)格書中第8.1.4節(jié)的建議)(htmlor注:可以通過修改注冊(cè)表等方法改變這一默認(rèn)配置)。這就意味著,在我們等待下載2個(gè)js文件的同時(shí),將無法下載圖片資源。也就是說,這段時(shí)間內(nèi)用戶在頁面上看不到圖片。

(雖然合并文件能解決以上兩個(gè)問題,)可是,這個(gè)方法有兩個(gè)缺點(diǎn)。第一,把所有資源一起打包,將強(qiáng)制用戶一次下載完所有資源。如果(不這么做,而是)把大塊內(nèi)容變成多個(gè)文件,下載開銷就分散到了多個(gè)頁面,同時(shí)緩解了會(huì)話中的速度壓力(或完全避免了某些開銷,這取決于用戶選擇的路徑)。如果為了隨后頁面下載得更快而讓初始頁面下載得很慢,我們將發(fā)現(xiàn)更多用戶根本不會(huì)傻等著再去打開下一個(gè)頁面。

第二(這個(gè)影響更大,一直以來卻沒怎么被考慮過),在一個(gè)文件改動(dòng)很頻繁的環(huán)境里,如果采用單文件系統(tǒng),那么每次改動(dòng)文件都需要客戶端把所有css和js重新下載一遍。假如我們的應(yīng)用有個(gè)100k的合成的js大文件,任何微小的改動(dòng)都將強(qiáng)制客戶端把這100k再消化一遍。

分解之道

(看來合并成大文件不太合適。)替代方案是個(gè)折中的辦法:把css和js資源分散成多個(gè)子文件,按功能劃分、保持文件個(gè)數(shù)盡可能少。這個(gè)方案也是有代價(jià)的,雖說開發(fā)時(shí)代碼分散成邏輯塊(logical chunks)能提高效率,可在下載時(shí)為提高性能還得合并文件。不過,只要給build系統(tǒng)(把開發(fā)代碼變成產(chǎn)品代碼的工具集,是為部署準(zhǔn)備的)加點(diǎn)東西,就沒什么問題了。

對(duì)于有著不同開發(fā)和產(chǎn)品環(huán)境的應(yīng)用來說,用些簡(jiǎn)單的技術(shù)可以讓代碼更好管理。在開發(fā)環(huán)境下,為使條理清晰,代碼可以分散為多個(gè)邏輯部分(logical components)??梢栽?a >Smarty(一種php模板語言)里建立一個(gè)簡(jiǎn)單的函數(shù)來管理javascript的下載:

SMARTY:{insert_js files="foo.js,bar.js,baz.js"}PHP:function smarty_insert_js($args){  foreach (explode(‘,‘, $args[‘files‘]) as $file){    echo "<script type=\"text/javascript\" SOURCE=\"/javascript/$file\"></script>\n";  }}OUTPUT:<script type="text/javascript" SOURCE="/javascript/foo.js"></script><script type="text/javascript" SOURCE="/javascript/bar.js"></script><script type="text/javascript" SOURCE="/javascript/baz.js"></script>

(htmlor注:wordpress中會(huì)把“src”替換成不知所謂的字符,因此這里只有寫成“SOURCE”,使用代碼時(shí)請(qǐng)注意替換,下同)

就這么簡(jiǎn)單。然后我們就命令build過程(build process)去把確定的文件合并起來。這個(gè)例子里,合并的是foo.js和bar.js,因?yàn)樗鼈儙缀蹩偸且黄鹣螺d。我們能讓應(yīng)用配置記住這一點(diǎn),并修改模板函數(shù)去使用它。(代碼如下:)

SMARTY:{insert_js files="foo.js,bar.js,baz.js"}PHP:# 源文件映射圖。在build過程合并文件之后用這個(gè)圖找到j(luò)s的源文件。$GLOBALS[‘config‘][‘js_source_map‘] = array(  ‘foo.js‘	=> ‘foobar.js‘,  ‘bar.js‘	=> ‘foobar.js‘,  ‘baz.js‘	=> ‘baz.js‘,);function smarty_insert_js($args){  if ($GLOBALS[‘config‘][‘is_dev_site‘]){    $files = explode(‘,‘, $args[‘files‘]);  }else{    $files = array();    foreach (explode(‘,‘, $args[‘files‘]) as $file){      $files[$GLOBALS[‘config‘][‘js_source_map‘][$file]]++;    }    $files = array_keys($files);  }  foreach ($files as $file){    echo "<script type=\"text/javascript\" SOURCE=\"/javascript/$file\"></script>\n";  }}OUTPUT:<script type="text/javascript" SOURCE="/javascript/foobar.js"></script><script type="text/javascript" SOURCE="/javascript/baz.js"></script>

模板里的源代碼沒必要為了分別適應(yīng)開發(fā)和產(chǎn)品階段而改動(dòng),它幫助我們?cè)陂_發(fā)時(shí)保持文件分散,發(fā)布成產(chǎn)品時(shí)把文件合并。想更進(jìn)一步的話,可以把合并過程(merge process)寫在php里,然后使用同一個(gè)(合并文件的)配置去執(zhí)行。這樣就只有一個(gè)配置文件,避免了同步問題。為了做的更加完美,我們還可以分析css和js文件在頁面中同時(shí)出現(xiàn)的幾率,以此決定合并哪些文件最合理(幾乎總是同時(shí)出現(xiàn)的文件是合并的首選)。

對(duì)css來說,可以先建立一個(gè)主從關(guān)系的模型,它很有用。一個(gè)主樣式表控制應(yīng)用的所有樣式表,多個(gè)子樣式表控制不同的應(yīng)用區(qū)域。采用這個(gè)方法,大多數(shù)頁面只需下載兩個(gè)css文件,而其中一個(gè)(指主樣式表)在頁面第一次請(qǐng)求時(shí)就會(huì)緩存。

對(duì)沒有太多css和js資源的應(yīng)用來說,這個(gè)方法在第一次請(qǐng)求時(shí)可能比單個(gè)大文件慢,但如果保持文件數(shù)量很少的話,你會(huì)發(fā)現(xiàn)其實(shí)它更快,因?yàn)槊總€(gè)頁面的數(shù)據(jù)量更小。讓人頭疼的下載花銷被分散到不同的應(yīng)用區(qū)域,因此并發(fā)下載數(shù)保持在一個(gè)最小值,同時(shí)也使得頁面的平均下載數(shù)據(jù)量很小。

壓縮

談到資源壓縮,大多數(shù)人馬上會(huì)想到mod_gzip(但要當(dāng)心,mod_gzip實(shí)際上是個(gè)魔鬼,至少能讓人做惡夢(mèng))。它的原理很簡(jiǎn)單:瀏覽器請(qǐng)求資源時(shí),會(huì)發(fā)送一個(gè)header表明自己能接受的內(nèi)容編碼。就像這樣:

Accept-Encoding: gzip,deflate

服務(wù)器遇到這樣的header請(qǐng)求時(shí),就用gzip或deflate壓縮內(nèi)容發(fā)往客戶端,然后客戶端解壓縮。這過程減少了數(shù)據(jù)傳輸量,同時(shí)消耗了客戶端和服務(wù)器的cpu時(shí)間。也算差強(qiáng)人意。但是,mod_gzip的工作方式是這樣的:先在磁盤上創(chuàng)建一個(gè)臨時(shí)文件,然后發(fā)送(給客戶端),最后刪除這個(gè)文件。在高容量的系統(tǒng)中,由于磁盤io問題,很快就會(huì)達(dá)到極限。要避免這種情況,可以改用mod_deflate(apache 2才支持)。它采用更合理的方式:在內(nèi)存里做壓縮。對(duì)于apache 1的用戶來說,可以建立一塊ram磁盤,讓mod_gzip在它上面寫臨時(shí)文件。雖然沒有純內(nèi)存方式快,但也不會(huì)比往磁盤上寫文件慢。

話雖如此,其實(shí)還是有辦法完全避免壓縮開銷的,那就是預(yù)壓縮相關(guān)靜態(tài)資源,下載時(shí)由mod_gzip提供合適的壓縮版本。如果把壓縮添加在build過程,它就很透明了。需要壓縮的文件通常很少(用不著壓縮圖片,因?yàn)椴⒉荒軠p小更多體積),只有css和js文件(和其他未壓縮的靜態(tài)內(nèi)容)。

配置選項(xiàng)會(huì)告訴mod_gzip去哪里找到預(yù)壓縮過的文件。

mod_gzip_can_negotiate	Yesmod_gzip_static_suffix	.gzAddEncoding	gzip	.gz

新一點(diǎn)的mod_gzip版本(從1.3.26.1a開始)添加一個(gè)額外的配置選項(xiàng)后,就能自動(dòng)預(yù)壓縮文件。不過在此之前,必須確認(rèn)apache有正確的權(quán)限去創(chuàng)建和覆蓋壓縮文件。

mod_gzip_update_static	Yes

可惜,事情沒那么簡(jiǎn)單。某些Netscape 4的版本(尤其是4.06-4.08)認(rèn)為自己能夠解釋壓縮內(nèi)容(它們發(fā)送一個(gè)header這么說來著),但其實(shí)它們不能正確的解壓縮。大多數(shù)其他版本的Netscape 4在下載壓縮內(nèi)容時(shí)也有各種各樣的問題。所以要在服務(wù)器端探測(cè)代理類型,(如果是Netscape 4,就要)讓它們得到未壓縮的版本。這還算簡(jiǎn)單的。ie(版本4-6)有些更有意思的問題:當(dāng)下載壓縮的javascript時(shí),有時(shí)候ie會(huì)不正確的解壓縮文件,或者解壓縮到一半中斷,然后把這半個(gè)文件顯示在客戶端。如果你的應(yīng)用對(duì)javascript的依賴比較大(htmlor注:比如ajax應(yīng)用),那么就得避免發(fā)送壓縮文件給ie。在某些情況下,一些更老的5.x版本的ie倒是能正確的收到壓縮的javascript,可它們會(huì)忽略這個(gè)文件的etag header,不緩存它。(thincat友情提示:盡管壓縮存在一些瀏覽器不兼容的現(xiàn)象,由于這些不能很好的支持壓縮的瀏覽器數(shù)量現(xiàn)在已經(jīng)非常少了,我認(rèn)為這種由于瀏覽器導(dǎo)致的壓縮不正常的情況可以忽略不計(jì)。這些過時(shí)的瀏覽器還能不能在現(xiàn)在流行的windows或unix環(huán)境下面安裝都存在不小的問題)

既然gzip壓縮有這么多問題,我們不妨把注意力轉(zhuǎn)到另一邊:不改變文件格式的壓縮?,F(xiàn)在有很多這樣的javascript壓縮腳本可用,大多數(shù)都用一個(gè)正則表達(dá)式驅(qū)動(dòng)的語句集來減小源代碼的體積。它們做的不外乎幾件事:去掉注釋,壓縮空格,縮短私有變量名和去掉可省略的語法。

不幸的是,大多數(shù)腳本效果并不理想,要么壓縮率相當(dāng)?shù)?,要么某種情形下會(huì)把代碼搞得一團(tuán)糟(或者兩者兼而有之)。由于對(duì)解析樹的理解不完整,壓縮器很難區(qū)分一句注釋和一句看似注釋的引用字符串。因?yàn)殚]合結(jié)構(gòu)的混合使用,要用正則表達(dá)式發(fā)現(xiàn)哪些變量是私有的并不容易,因此一些縮短變量名的技術(shù)會(huì)打亂某些閉合代碼。

還好有個(gè)壓縮器能避免這些問題:dojo壓縮器(現(xiàn)成的版本在這里)。它使用rhino(mozilla的javascript引擎,是用java實(shí)現(xiàn)的)建立一個(gè)解析樹,然后將其提交給文件。它能很好的減小代碼體積,僅用很小的成本:因?yàn)橹辉赽uild時(shí)壓縮一次。由于壓縮是在build過程中實(shí)現(xiàn)的,所以一清二楚。(既然壓縮沒有問題了,)我們可以在源代碼里隨心所欲的添加空格和注釋,而不必?fù)?dān)心影響到產(chǎn)品代碼。

與javascript相比,css文件的壓縮相對(duì)簡(jiǎn)單一些。由于css語法里不會(huì)有太多引用字符串(通常是url路徑跟字體名),我們可以用正則表達(dá)式大刀闊斧的干掉空格(htmlor注:這句翻的最爽,哈哈)。如果確實(shí)有引用字符串的話,我們總可以把一串空格合成一個(gè)(因?yàn)椴恍枰趗rl路徑和字體名里查找多個(gè)空格和tab)。這樣的話,一個(gè)簡(jiǎn)單的perl腳本就夠了:

#!/usr/bin/perlmy $data = ‘‘;open F, $ARGV[0] or die "Can‘t open source file: $!";$data .= $_ while <F>;close F;$data =~ s!/*(.*?)*/!!g;  # 去掉注釋$data =~ s!s+! !g;           # 壓縮空格$data =~ s!} !}\n!g;         # 在結(jié)束大括號(hào)后添加換行$data =~ s!\n$!!;             # 刪除最后一個(gè)換行$data =~ s! { ! {!g;         # 去除開始大括號(hào)后的空格$data =~ s!; }!}!g;          # 去除結(jié)束大括號(hào)前的空格print $data;

然后,就可以把單個(gè)的css文件傳給腳本去壓縮了。命令如下:

perl compress.pl site.source.css > site.compress.css

做完這些簡(jiǎn)單的純文本優(yōu)化工作后,我們就能減少數(shù)據(jù)傳輸量多達(dá)50%了(這個(gè)量取決于你的代碼格式,可能更多)。這帶來了更快的用戶體驗(yàn)。不過我們真正想做的是,盡可能避免用戶請(qǐng)求的發(fā)生——除非確實(shí)有必要。這下HTTP緩存知識(shí)派上用場(chǎng)了。

緩存是好東西

當(dāng)用戶代理(如瀏覽器)向服務(wù)器請(qǐng)求一個(gè)資源時(shí),第一次請(qǐng)求過后它就會(huì)緩存服務(wù)器的響應(yīng),以避免重復(fù)之后的相同請(qǐng)求。緩存時(shí)間的長短取決于兩個(gè)因素:代理的配置和服務(wù)器的緩存控制header。所有瀏覽器都有不同的配置選項(xiàng)和處理方式,但大多數(shù)都會(huì)把一個(gè)資源至少緩存到會(huì)話結(jié)束(除非被明確告知)。

為了不讓瀏覽器緩存改動(dòng)頻繁的頁面,你很可能已經(jīng)發(fā)送過header不緩存動(dòng)態(tài)內(nèi)容。在php中,以下兩行命令可以做到:

<?phpheader("Cache-Control: private");header("Cache-Control: no-cache", false);?>

聽起來太簡(jiǎn)單了?確實(shí)如此——因?yàn)橛行┐恚g覽器)在某些環(huán)境下將忽略這些header。要確保瀏覽器不緩存文檔,應(yīng)該更強(qiáng)硬一些:

<?php# 讓它在過去就“失效”header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");# 永遠(yuǎn)是改動(dòng)過的header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT");# HTTP/1.1header("Cache-Control: no-store, no-cache, must-revalidate");header("Cache-Control: post-check=0, pre-check=0", false);# HTTP/1.0header("Pragma: no-cache");?>

這樣,對(duì)于我們不想緩存的內(nèi)容來說已經(jīng)行了。但對(duì)于那些不會(huì)每次請(qǐng)求時(shí)都有改動(dòng)的內(nèi)容,應(yīng)該鼓勵(lì)瀏覽器更霸道的緩存它?!癐f-Modified-Since”請(qǐng)求header能夠做到這點(diǎn)。如果客戶端在請(qǐng)求中發(fā)送一個(gè)“If-Modified-Since”header,apache(或其他服務(wù)器)會(huì)以狀態(tài)代碼304(沒改過)響應(yīng),告訴瀏覽器緩存已經(jīng)是最新的。使用這個(gè)機(jī)制,能夠避免重復(fù)發(fā)送文件給瀏覽器,不過仍然導(dǎo)致了一個(gè)HTTP請(qǐng)求的消耗。嗯,再想想。

與If-Modified-Since機(jī)制類似的是實(shí)體標(biāo)記(entity tags)。在apache環(huán)境下,每個(gè)對(duì)靜態(tài)文件的響應(yīng)都會(huì)發(fā)出一個(gè)“ETag”header,它包含了一個(gè)由文件修改時(shí)間、文件大小和inode號(hào)生成的校驗(yàn)和(checksum)。在下載文件之前,瀏覽器會(huì)發(fā)送一個(gè)HEAD請(qǐng)求去檢查文件的etag??蒃Tag跟If-Modified-Since有同樣的問題:客戶端仍舊需要執(zhí)行HTTP請(qǐng)求來驗(yàn)證本地緩存是否有效。

此外,如果你使用多臺(tái)服務(wù)器提供內(nèi)容,得小心使用if-modified-since和etags。在兩臺(tái)負(fù)載平衡的服務(wù)器環(huán)境下,對(duì)一個(gè)代理(瀏覽器)來說,一個(gè)資源可以這次從A服務(wù)器得到,下次從B服務(wù)器得到(htmlor注:lvs負(fù)載平衡系統(tǒng)就是個(gè)典型的例子)。這很好,也是采用平衡負(fù)載的原因??墒牵绻麅膳_(tái)服務(wù)器給同一個(gè)文件生成了不同的etag或者文件修改日期,瀏覽器就無所適從了(每次都會(huì)重新下載)。默認(rèn)情況下,etag是由文件的inode號(hào)生成的,而多臺(tái)服務(wù)器之間文件的inode號(hào)是不同的??梢允褂胊pache的配置選項(xiàng)關(guān)掉它:

FileETag MTime Size

使用這個(gè)選項(xiàng),apache將只用文件修改日期和文件大小來決定etag。很不幸,這導(dǎo)致了另一個(gè)問題(一樣能影響if-modified-since)。既然etag依賴于修改時(shí)間,就得讓時(shí)間同步??赏嗯_(tái)服務(wù)器上傳文件時(shí),上傳時(shí)間差個(gè)一到兩秒是常有的事。這樣一來,兩臺(tái)服務(wù)器生成的etag還是不一樣。當(dāng)然,我們還可以改變配置,讓etag的生成只取決于文件大小,但這就意味著如果文件內(nèi)容變了而大小沒變,etag也不會(huì)變。這可不行。

緩存真是個(gè)好東西

看來我們正從錯(cuò)誤的方向入手解決問題。(現(xiàn)在的問題是,)這些可能的緩存策略導(dǎo)致了一件事情反復(fù)發(fā)生,那就是:客戶端向服務(wù)器查詢本地緩存是否最新。假如服務(wù)器在改動(dòng)文件的時(shí)候通知客戶端,客戶端不就知道它的緩存是最新的了(直到接到下一次通知)?可惜天公不做美——(事實(shí))是客戶端向服務(wù)器發(fā)出請(qǐng)求。

其實(shí),也不盡然。在獲取js或css文件之前,客戶端會(huì)用<script>或<link>標(biāo)記向服務(wù)器發(fā)送一個(gè)請(qǐng)求,說明哪個(gè)頁面要加載這些文件。這時(shí)候就可以用服務(wù)器的響應(yīng)來通知客戶端這些文件有了改動(dòng)。有點(diǎn)含糊,說得再詳細(xì)點(diǎn)就是:如果改變css和js文件內(nèi)容的同時(shí),也改變它們的文件名,就可以告訴客戶端對(duì)url全都永久緩存——因?yàn)槊總€(gè)url都是唯一的。

假如能確定一個(gè)資源永不更改,我們就可以發(fā)出一些霸氣十足的緩存header(htmlor注:這句也很有氣勢(shì)吧)。在php里,兩行就好:

<?phpheader("Expires: ".gmdate("D, d M Y H:i:s", time()+315360000)." GMT");header("Cache-Control: max-age=315360000");?>

我們告訴瀏覽器這個(gè)內(nèi)容在10年后(10年大概會(huì)有315,360,000秒,或多或少)過期,瀏覽器將會(huì)保留它10年。當(dāng)然,很有可能不用php輸出css和js文件(因此就不能發(fā)出header),這種情況將在稍后說明。

人力有時(shí)而窮

當(dāng)文件內(nèi)容更改時(shí),手動(dòng)去改文件名是很危險(xiǎn)的。假如你改了文件名,模板卻沒有指向它?假如你改了一些模板另一些卻沒改?假如你改了模板卻沒改文件名?還有最糟的,假如你改動(dòng)了文件卻忘了改名或者忘了改變對(duì)它的引用?最好的結(jié)果,是用戶看到老的而看不到新的內(nèi)容。最壞的結(jié)果,是找不到文件,網(wǎng)站沒法運(yùn)轉(zhuǎn)了。聽起來這(指改動(dòng)文件內(nèi)容時(shí)修改url)似乎是個(gè)餿主意。

幸運(yùn)的是,計(jì)算機(jī)做這類事情——當(dāng)某種變化發(fā)生,需要相當(dāng)準(zhǔn)確地完成的、重復(fù)重復(fù)再重復(fù)的(htmlor注:番茄雞蛋伺候~)、枯燥乏味的工作——總是十分在行。

這個(gè)過程(改變文件的url)沒那么痛苦,因?yàn)槲覀兏静恍枰奈募YY源的url和磁盤上文件的位置也沒必要保持一致。使用apache的mod_rewrite模塊,可以建立簡(jiǎn)單的規(guī)則,讓確定的url重定向到確定的文件。

RewriteEngine onRewriteRule ^/(.*.)v[0-9.]+.(css|js|gif|png|jpg)$	/$1$2	[L]

這條規(guī)則匹配任何帶有指定擴(kuò)展名同時(shí)含有“版本”信息(version nugget)的url,它會(huì)把這些url重定向到一個(gè)不含版本信息的路徑。如下所示:

URL			   Path/images/foo.v2.gif	-> /images/foo.gif/css/main.v1.27.css	-> /css/main.css/javascript/md5.v6.js	-> /javascript/md5.js

使用這條規(guī)則,就可以做到不改變文件路徑而更改url(因?yàn)榘姹咎?hào)變了)。由于url變了,瀏覽器就認(rèn)為它是另一個(gè)資源(會(huì)重新下載)。想更進(jìn)一步的話,可以把我們之前說的腳本編組函數(shù)結(jié)合起來,根據(jù)需要生成一個(gè)帶有版本號(hào)的<script>標(biāo)記列表。

說到這里,你可能會(huì)問我,為什么不在url結(jié)尾加一個(gè)查詢字符串(query string)呢(如/css/main.css?v=4)?根據(jù)HTTP緩存規(guī)格書所說,用戶代理對(duì)含有查詢字符串的url永不緩存。雖然ie跟firefox忽略了這點(diǎn),opera和safari卻沒有——為了確保所有瀏覽器都緩存你的資源,還是不要在url里用查詢字符串的好。

現(xiàn)在不移動(dòng)文件就能更改url了,如果能讓url自動(dòng)更新就更好了。在小型的產(chǎn)品環(huán)境下(如果有大型的產(chǎn)品環(huán)境,就是開發(fā)環(huán)境了),使用模板功能可以很輕易的實(shí)現(xiàn)這點(diǎn)。這里用的是smarty,用其他模板引擎也行。

SMARTY:<link xhref="{version xsrc=‘/css/group.css‘}" rel="stylesheet" type="text/css" />PHP:function smarty_version($args){  $stat = stat($GLOBALS[‘config‘][‘site_root‘].$args[‘src‘]);  $version = $stat[‘mtime‘];  echo preg_replace(‘!.([a-z]+?)$!‘, ".v$version.$1", $args[‘src‘]);}OUTPUT:<link xhref="/css/group.v1234567890.css" mce_href="/css/group.v1234567890.css" rel="stylesheet" type="text/css" />

對(duì)每個(gè)鏈接到的資源文件,我們得到它在磁盤上的路徑,檢查它的mtime(文件最后修改的日期和時(shí)間),然后把這個(gè)時(shí)間當(dāng)作版本號(hào)插入到url中。對(duì)于低流量的站點(diǎn)(它們的stat操作開銷不大)或者開發(fā)環(huán)境來說,這個(gè)方案不錯(cuò),但對(duì)于高容量的環(huán)境就不適用了——因?yàn)槊看蝧tat操作都要磁盤讀?。▽?dǎo)致服務(wù)器負(fù)載升高)。

解決方案相當(dāng)簡(jiǎn)單。在大型系統(tǒng)中每個(gè)資源都已經(jīng)有了一個(gè)版本號(hào),就是版本控制的修訂號(hào)(你們應(yīng)該使用了版本控制,對(duì)吧?)。當(dāng)我們建立站點(diǎn)準(zhǔn)備部署的時(shí)候,可以輕易的查到每個(gè)文件的修訂號(hào),寫在一個(gè)靜態(tài)配置文件里。

<?php$GLOBALS[‘config‘][‘resource_versions‘] = array(  ‘/images/foo.gif‘    => ‘2.1‘,  ‘/css/main.css‘      => ‘1.27‘,  ‘/javascript/md5.js‘ => ‘6.1.4‘,);?>

當(dāng)我們發(fā)布產(chǎn)品時(shí),可以修改模板函數(shù)來使用版本號(hào)。

<?phpfunction smarty_version($args){  if ($GLOBALS[‘config‘][‘is_dev_site‘]){    $stat = stat($GLOBALS[‘config‘][‘site_root‘].$args[‘src‘]);    $version = $stat[‘mtime‘];  }else{    $version = $GLOBALS[‘config‘][‘resource_versions‘][$args[‘src‘]];  }  echo preg_replace(‘!.([a-z]+?)$!‘, ".v$version.$1", $args[‘src‘]);}?>

就這樣,不需要改文件名,也不需要記住改了哪些文件——當(dāng)文件有新版本發(fā)布時(shí)它的url就會(huì)自動(dòng)更新——有意思吧?我們就快搞定了。

只欠東風(fēng)

之前談到為靜態(tài)文件發(fā)送超長周期(very-long-period)的緩存header時(shí)曾說過,如果不用php輸出,就不能輕易的發(fā)送緩存header。很顯然,有兩個(gè)辦法可以解決:用php輸出,或者讓apache來做。

php出馬,手到擒來。我們要做的僅僅是改變r(jià)ewrite規(guī)則,把靜態(tài)文件指向php腳本,用php在輸出文件內(nèi)容之前發(fā)送header。

Apache:RewriteRule ^/(.*.)v[0-9.]+.(css|js|gif|png|jpg)$  /redir.php?path=$1$2  [L]PHP:header("Expires: ".gmdate("D, d M Y H:i:s", time()+315360000)." GMT");header("Cache-Control: max-age=315360000");# 忽略帶有“..”的路徑if (preg_match(‘!..!‘, $_GET[path])){ go_404(); }# 保證路徑開頭是確定的目錄if (!preg_match(‘!^(javascript|css|images)!‘, $_GET[path])){ go_404(); }# 文件不存在?if (!file_exists($_GET[path])){ go_404(); }# 發(fā)出一個(gè)文件類型header$ext = array_pop(explode(‘.‘, $_GET[path]));switch ($ext){  case ‘css‘:    header("Content-type: text/css");    break;  case ‘js‘ :    header("Content-type: text/javascript");    break;  case ‘gif‘:    header("Content-type: image/gif");    break;  case ‘jpg‘:    header("Content-type: image/jpeg");    break;  case ‘png‘:    header("Content-type: image/png");    break;  default:    header("Content-type: text/plain");}# 輸出文件內(nèi)容echo implode(‘‘, file($_GET[path]));function go_404(){  header("HTTP/1.0 404 File not found");  exit;}

這個(gè)方案有效,但并不出色。(因?yàn)椋└鷄pache相比,php需要更多內(nèi)存和執(zhí)行時(shí)間。另外,我們還得小心防止可能由path參數(shù)傳遞偽造值引起的exploits。為避免這些問題,應(yīng)該用apache直接發(fā)送header。rewrite規(guī)則語句允許當(dāng)規(guī)則匹配時(shí)設(shè)置環(huán)境變量(environment variable),當(dāng)給定的環(huán)境變量設(shè)置后,Header命令就可以添加header。結(jié)合以下兩條語句,我們就把rewrite規(guī)則和header設(shè)置綁定在了一起:

RewriteEngine onRewriteRule ^/(.*.)v[0-9.]+.(css|js|gif|png|jpg)$ /$1$2 [L,E=VERSIONED_FILE:1]Header add "Expires" "Mon, 28 Jul 2014 23:30:00 GMT" env=VERSIONED_FILEHeader add "Cache-Control" "max-age=315360000" env=VERSIONED_FILE

考慮到apache的執(zhí)行順序,應(yīng)該把rewrite規(guī)則加在主配置文件(httpd.conf)而不是目錄配置文件(.htaccess)中。否則在環(huán)境變量設(shè)置之前,header行會(huì)先執(zhí)行(就那沒意義了)。至于header行,則可以放在兩文件任何一個(gè)當(dāng)中,沒什么區(qū)別。

眼觀六路

(htmlor注:多謝tchaikov告知“skinning rabbits”的含義,但我不想翻的太正式,眼下的這個(gè)應(yīng)該不算太離譜吧。)

通過結(jié)合使用以上技術(shù),我們可以建立一個(gè)靈活的開發(fā)環(huán)境和一個(gè)快速又高性能的產(chǎn)品環(huán)境。當(dāng)然,這離終極目標(biāo)“速度”還有一段距離。有許多更深層的技術(shù)(比如分離伺服靜態(tài)內(nèi)容,用多域名提升并發(fā)量等)值得我們關(guān)注,包括與我們談到的方法(建立apache過濾器,修改資源url,加上版本信息)殊途同歸的其他路子。你可以留下評(píng)論,告訴我們那些你正在使用的卓有成效的技術(shù)和方法。

(完)

This entry was posted on 星期四, 八月 3rd, 2006 at 02:23:57 and is filed under javascript, web. You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.

23 Responses to “flickr對(duì)javascript干的好事”

  1. xiaoxiSays:
    八月 3rd, 2006 at 12:06:44 e

    現(xiàn)在電腦內(nèi)存很高,上網(wǎng)速度也快了很多,得像緩存、文件大小幾乎可以忽略不計(jì)吧

  2. htmlorSays:
    八月 3rd, 2006 at 12:22:17 e

    呵呵,客戶端是可以忽略掉,但是服務(wù)器端呢?系統(tǒng)負(fù)載、資源開銷、流量等問題對(duì)大型網(wǎng)站是非常非常重要的。

  3. 大雄Says:
    八月 3rd, 2006 at 17:41:54 e

    不過好的程序,確實(shí)相差很多,不過最好所有的文件都是靜態(tài),這樣訪問的速度肯定快。

    現(xiàn)在用ASP做,不知道IIS的性能如何。

  4. my5151.meibu.comSays:
    八月 3rd, 2006 at 17:43:49 e

    收藏??! (可視化自定義web表單工具, 在:my5151.meibu.com )

  5. zolaSays:
    八月 4th, 2006 at 16:23:56 e

    翻譯的不錯(cuò)!

  6. SusanSays:
    八月 4th, 2006 at 17:17:15 e

    第一句話里面應(yīng)該是 下一代web …. 您打錯(cuò)了.

  7. htmlorSays:
    八月 4th, 2006 at 17:59:05 e

    多謝susan,還真是手誤了。已改正。

  8. modifySays:
    八月 4th, 2006 at 18:28:22 e

    to xiaoxi:
    那是你家的電腦。。。。

    我家的電腦就沒那么快。。。。

  9. tchaikovSays:
    八月 4th, 2006 at 20:48:41 e

    “Skinning rabbits”應(yīng)該是英語中的一個(gè)俚語,相似的還有“there’s more than one way to skin a cat”。
    和文中翻譯的一樣,都是“殊途同歸”的意思,不妨譯作“條條大路通羅馬”。

  10. RieSays:
    八月 4th, 2006 at 21:11:22 e

    真是好文章,翻譯的也很符合中國人的閱讀思維

  11. htmlorSays:
    八月 4th, 2006 at 21:33:06 e

    多謝tchaikov釋疑。又學(xué)了一招。

  12. htmlorSays:
    八月 4th, 2006 at 21:50:37 e

    Rie
    很高興你這么說。這篇文章能對(duì)看的人有所啟發(fā),我就最開心了。

  13. xLightSays:
    八月 5th, 2006 at 13:21:06 e

    受教了,我要轉(zhuǎn)一個(gè)

  14. om19Says:
    八月 5th, 2006 at 19:09:33 e

    雖然個(gè)人電腦快,網(wǎng)速看看網(wǎng)頁基本還好。但是緩存可以節(jié)省大量服務(wù)器資源~服務(wù)器資源是永遠(yuǎn)不夠的!

  15. dengSays:
    八月 5th, 2006 at 19:44:41 e

    翻譯的不錯(cuò),雖然我不是搞js開發(fā)的,還是忍不住留個(gè)言(一般情況我直接就關(guān)窗口)

  16. AmirFishSays:
    八月 5th, 2006 at 22:29:05 e

    感謝你的翻譯。受益匪淺。 :)

    希望繼續(xù)翻譯更多的精品文章。。收藏了。

  17. htmlorSays:
    八月 5th, 2006 at 22:42:23 e

    多謝大家捧場(chǎng)。以后看到有意思的文章,還是會(huì)翻的。做自己感興趣的事,動(dòng)力似乎特別大。

  18. SikoSays:
    八月 5th, 2006 at 23:32:57 e

    翻譯的不錯(cuò)

  19. http://computer.mblogger.cn/lugisSays:
    八月 6th, 2006 at 19:04:26 e

    收益!轉(zhuǎn)載下

  20. xLightSays:
    八月 8th, 2006 at 11:23:47 e

    文章中沒有提到mod_headers這個(gè)apache模塊。
    誰能告訴我哪里有下載mod_headers?

  21. htmlorSays:
    八月 8th, 2006 at 11:56:37 e

    xLight
    mod_headers無需下載,只要安裝apache時(shí)編譯進(jìn)去就行了(或者動(dòng)態(tài)加載也可以)。

  22. toddSays:
    八月 9th, 2006 at 21:14:07 e

    好文。之前看過一次,不過不太全。

    只有是制作大型應(yīng)用人,才能真正理解其中的意義。

  23. Suave’s Blog ? Web Cache TutorialSays:
    八月 10th, 2006 at 08:51:24 e

    […] Serving Javascript Fast (中文版) […]

本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊舉報(bào)。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
如何設(shè)置CSS、JS、圖片緩存
如何優(yōu)化Web網(wǎng)站性能?
大規(guī)模、高流量互聯(lián)網(wǎng)站性能調(diào)優(yōu)的22條準(zhǔn)則
PHP 網(wǎng)站優(yōu)化
在服務(wù)端合并和壓縮JavaScript和CSS文件 @ 隨網(wǎng)之舞
【第685期】如何運(yùn)用最新的技術(shù)提升網(wǎng)頁速度和性能
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長圖 關(guān)注 下載文章
綁定賬號(hào)成功
后續(xù)可登錄賬號(hào)暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服