一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

JavaScript的優(yōu)化處理

 達能牛牛 2006-08-12

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

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

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

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

全文翻譯如下:

讓javascript跑得更快

作者:Cal Henderson

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

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

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

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

好大一沱

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

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

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

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

分解之道

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

對于有著不同開發(fā)和產(chǎn)品環(huán)境的應用來說,用些簡單的技術(shù)可以讓代碼更好管理。在開發(fā)環(huán)境下,為使條理清晰,代碼可以分散為多個邏輯部分(logical components)??梢栽?a href="http://smarty./">Smarty(一種php模板語言)里建立一個簡單的函數(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中會把“src”替換成不知所謂的字符,因此這里只有寫成“SOURCE”,使用代碼時請注意替換,下同)

就這么簡單。然后我們就命令build過程(build process)去把確定的文件合并起來。這個例子里,合并的是foo.js和bar.js,因為它們幾乎總是一起下載。我們能讓應用配置記住這一點,并修改模板函數(shù)去使用它。(代碼如下:)

SMARTY:
{insert_js files="foo.js,bar.js,baz.js"}
PHP:
# 源文件映射圖。在build過程合并文件之后用這個圖找到js的源文件。
$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>

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

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

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

壓縮

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

Accept-Encoding: gzip,deflate

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

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

配置選項會告訴mod_gzip去哪里找到預壓縮過的文件。

mod_gzip_can_negotiate	Yes
mod_gzip_static_suffix	.gz
AddEncoding	gzip	.gz

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

mod_gzip_update_static	Yes

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

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

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

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

與javascript相比,css文件的壓縮相對簡單一些。由于css語法里不會有太多引用字符串(通常是url路徑跟字體名),我們可以用正則表達式大刀闊斧的干掉空格(htmlor注:這句翻的最爽,哈哈)。如果確實有引用字符串的話,我們總可以把一串空格合成一個(因為不需要在url路徑和字體名里查找多個空格和tab)。這樣的話,一個簡單的perl腳本就夠了:

#!/usr/bin/perl
my $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é)束大括號后添加換行
$data =~ s!\n$!!;             # 刪除最后一個換行
$data =~ s! { ! {!g;         # 去除開始大括號后的空格
$data =~ s!; }!}!g;          # 去除結(jié)束大括號前的空格
print $data;

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

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

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

緩存是好東西

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

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

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

聽起來太簡單了?確實如此——因為有些代理(瀏覽器)在某些環(huán)境下將忽略這些header。要確保瀏覽器不緩存文檔,應該更強硬一些:

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

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

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

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

FileETag MTime Size

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

緩存真是個好東西

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

其實,也不盡然。在獲取js或css文件之前,客戶端會用<script>或<link>標記向服務器發(fā)送一個請求,說明哪個頁面要加載這些文件。這時候就可以用服務器的響應來通知客戶端這些文件有了改動。有點含糊,說得再詳細點就是:如果改變css和js文件內(nèi)容的同時,也改變它們的文件名,就可以告訴客戶端對url全都永久緩存——因為每個url都是唯一的。

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

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

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

人力有時而窮

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

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

這個過程(改變文件的url)沒那么痛苦,因為我們根本不需要改文件名。資源的url和磁盤上文件的位置也沒必要保持一致。使用apache的mod_rewrite模塊,可以建立簡單的規(guī)則,讓確定的url重定向到確定的文件。

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

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

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(因為版本號變了)。由于url變了,瀏覽器就認為它是另一個資源(會重新下載)。想更進一步的話,可以把我們之前說的腳本編組函數(shù)結(jié)合起來,根據(jù)需要生成一個帶有版本號的<script>標記列表。

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

現(xiàn)在不移動文件就能更改url了,如果能讓url自動更新就更好了。在小型的產(chǎn)品環(huán)境下(如果有大型的產(chǎn)品環(huán)境,就是開發(fā)環(huán)境了),使用模板功能可以很輕易的實現(xià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" />

對每個鏈接到的資源文件,我們得到它在磁盤上的路徑,檢查它的mtime(文件最后修改的日期和時間),然后把這個時間當作版本號插入到url中。對于低流量的站點(它們的stat操作開銷不大)或者開發(fā)環(huán)境來說,這個方案不錯,但對于高容量的環(huán)境就不適用了——因為每次stat操作都要磁盤讀?。▽е路掌髫撦d升高)。

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

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

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

<?php
function 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‘]);
}
?>

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

只欠東風

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

php出馬,手到擒來。我們要做的僅僅是改變rewrite規(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ā)出一個文件類型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;
}

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

RewriteEngine on
RewriteRule ^/(.*.)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_FILE
Header add "Cache-Control" "max-age=315360000" env=VERSIONED_FILE

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

眼觀六路

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

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

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

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

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

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

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

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

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

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

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

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

    翻譯的不錯!

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

    第一句話里面應該是 下一代web …. 您打錯了.

  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”應該是英語中的一個俚語,相似的還有“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釋疑。又學了一招。

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

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

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

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

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

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

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

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

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

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

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

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

    多謝大家捧場。以后看到有意思的文章,還是會翻的。做自己感興趣的事,動力似乎特別大。

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

    翻譯的不錯

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

    收益!轉(zhuǎn)載下

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

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

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

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

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

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

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

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

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

    本站是提供個人知識管理的網(wǎng)絡存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多

    91福利免费一区二区三区| 初尝人妻少妇中文字幕在线| 爱在午夜降临前在线观看| 欧美日韩中国性生活视频| 男人和女人草逼免费视频| 国产不卡在线免费观看视频| 亚洲永久一区二区三区在线| 91久久精品在这里色伊人| 精品国产av一区二区三区不卡蜜 | 老熟妇乱视频一区二区| 大香蕉久久精品一区二区字幕| 成人日韩视频中文字幕| 久久久免费精品人妻一区二区三区| 国产免费一区二区不卡| 欧美国产在线观看精品| 超薄肉色丝袜脚一区二区| 在线日韩中文字幕一区| 91插插插外国一区二区婷婷| 东京热男人的天堂久久综合| 中文字幕欧美精品人妻一区| 国产精品午夜小视频观看| 亚洲综合日韩精品欧美综合区| 九七人妻一区二区三区| 中国美女偷拍福利视频| 色哟哟精品一区二区三区| 欧美一本在线免费观看| 色婷婷丁香激情五月天| 免费一区二区三区少妇| 欧美一级片日韩一级片| 日本午夜免费啪视频在线| 午夜精品一区二区av| 熟女一区二区三区国产| 日韩欧美高清国内精品| 国产偷拍精品在线视频| 国产黑人一区二区三区| 国产传媒一区二区三区| 国产成人精品国内自产拍| 国产成人亚洲欧美二区综| 国产一级二级三级观看| 国产精品免费自拍视频| 亚洲最大福利在线观看|