js脚本加载总结
這段時(shí)間工作工作上不是很緊,零星的在研究瀏覽器的一些東西,剛好這個(gè)月又一次輪到我做沙龍講座了,想好了好久,就來一次js腳本加載的總結(jié)吧!這一塊應(yīng)該對(duì)于很多做項(xiàng)目的朋友來會(huì)有所幫助吧!
1、js起源
總所周知網(wǎng)頁(yè)最開始的形態(tài)是靜態(tài)的(也就是所謂的靜態(tài)網(wǎng)頁(yè)),那時(shí)候的網(wǎng)頁(yè)主要用于瀏覽資料信息,可是隨著用戶需求的增加,用戶希望在頁(yè)面上做一些交互操作,比如頁(yè)面需要驗(yàn)證才能訪問、頁(yè)面輸入的一些數(shù)據(jù)希望下次還可以訪問等等,js的出現(xiàn)滿足了人們的需求。也就出現(xiàn)了所謂的動(dòng)態(tài)網(wǎng)頁(yè)。
雖然js給人們帶來了很好的交互性,但是起初人們亂用腳本,只是網(wǎng)頁(yè)頁(yè)面代碼混亂不堪,當(dāng)css的出現(xiàn)解決了這一問題:
知道如今,我們的網(wǎng)頁(yè)也是分為三部分:HTML(主要放置界面元素)、CSS(主要負(fù)責(zé)界面布局)和JS(主要負(fù)責(zé)界面交互)。
大家都知道這三部分,但是有很多人不注意一些細(xì)節(jié),導(dǎo)致做出的網(wǎng)頁(yè)訪問時(shí)效率低下。在這里我將和大家討論一下關(guān)于js的一些知識(shí),希望給大家?guī)硪欢◣椭桑?/p>
2、三種使用腳本方式
2.1內(nèi)部引用JavaScript
2.11通過HTML的script標(biāo)簽加載JavaScript代碼
如:
<head>
<script type="text/javascript">
document.write("Hello World !");
</script>
</head>
2.12通過注釋隱藏JavaScript代碼
如:
<head>
<script type="text/javascript">
<!--
document.write("Hello World !");
//-->
</script>
</head>
<!-- ... //-->當(dāng)瀏覽器不支持JavaScript時(shí),屏蔽JavaScript代碼。這個(gè)代碼是駭客技術(shù),<!-- ... -->于HTML注釋,//是JavaScript注釋。當(dāng)瀏覽器支持JavaScript時(shí)//代碼生效,因此HTML的注釋沒有效果;當(dāng)瀏覽器不支JavaScript時(shí),//代碼無(wú)效,因此屏蔽了<!-- ... -->之間的JavaScript代碼。現(xiàn)在這種隱藏JavaScript代碼的方式可以忽略,因?yàn)闆]有瀏覽器不支持JavaScript,除了部分用戶手動(dòng)禁止瀏覽器的JavaScript功能,但是這種情況很少發(fā)生。
2.1.3使用noscript標(biāo)簽為用戶提供更好的體驗(yàn)
如:
<body>
<script type="text/javascript">
document.write("Hello World !");
</script>
<noscript>
<p>如果您想查看此網(wǎng)頁(yè),則必須啟用JavaScript。
然而,JavaScript 似乎被禁用,要么就是您的
瀏覽器不支持 JavaScript。請(qǐng)更改您的瀏覽器
選項(xiàng)以啟用 JavaScript,然后刷新。
</p>
</noscript>
</body>
通過JavaScript注釋的方式可以隱藏JavaScript代碼,通過noscript標(biāo)簽可以為用戶提供更好的體驗(yàn)(提示用戶你的瀏覽器不支持JavaScript)。
2.2外部引用JavaScript
使用<script>標(biāo)簽的src屬性來加載js腳本。通常JavaScript文件可以使用script標(biāo)簽加載到網(wǎng)頁(yè)的任何一個(gè)地方,但是標(biāo)準(zhǔn)的方式是加載在head標(biāo)簽內(nèi)。為防止網(wǎng)頁(yè)加載緩慢,也可以把非關(guān)鍵的JavaScript放到網(wǎng)頁(yè)底部。
<script type="text/javascript" src=“SuperMap.js"></script>
這里有幾點(diǎn)好處:
1)避免使用<!-- ... //-->,駭客技術(shù)。
2)統(tǒng)一定義JavaScript代碼,方便查看,方便維護(hù)。
3)使代碼更安全,可以壓縮,加密單個(gè)JavaScript文件。
4)瀏覽器可以緩存JavaScript文件,減少寬帶使用。
2.3內(nèi)聯(lián)引用JavaScript
內(nèi)聯(lián)引用是通過HTML標(biāo)簽中的事件屬性實(shí)現(xiàn)的。
<input type="button" value="點(diǎn)我" onclick="alert('你點(diǎn)擊了一個(gè)按鈕');">
上面示例將調(diào)用input標(biāo)簽的onclick屬性,彈出一個(gè)提示框。
3內(nèi)外腳本的比較
內(nèi)聯(lián)腳本方式使用場(chǎng)景很少,幾乎沒什么優(yōu)勢(shì)。
內(nèi)部腳本示例:http://stevesouders.com/hpws/inlined.php
外部腳本示例:http://stevesouders.com/hpws/external.php
內(nèi)部腳本示例只有一個(gè)HTML文檔,其大小為87kb,所有的js和css都包含在HTML文檔自身中。外部腳本示例包含一個(gè)HTML文檔(7kb)、一個(gè)樣式表(59kb)和三個(gè)腳本(1kb、11kb和9kb),總計(jì)87kb。盡管所需下載的總數(shù)據(jù)量是相同的,內(nèi)部腳本示例還是比外部示例快30%到50%。這主要是因?yàn)橥獠渴纠枰袚?dān)多個(gè)HTTP請(qǐng)求帶來的開銷。盡管外部腳本示例可以從樣式表和腳本的并行下載中獲益,但一個(gè)HTTP請(qǐng)求與五個(gè)HTTP請(qǐng)求之間的差距導(dǎo)致內(nèi)部腳本示例更快一些。
盡管結(jié)果如此,現(xiàn)實(shí)中還是使用外部文件會(huì)更合理一些,因?yàn)橥獠课募鶐淼氖找?-------js文件有機(jī)會(huì)被瀏覽器緩存起來。HTML文檔--------至少是那些包含動(dòng)態(tài)內(nèi)容的HTML文檔--------通常不會(huì)被配置為可以進(jìn)行緩存。當(dāng)遇到這種情況時(shí)(HTML沒有被緩存),每次請(qǐng)求HTML文檔都要下載內(nèi)部的js。另一方面,如果js是外部文件,瀏覽器就能緩存它們,HTML文檔的大小減小,而且不會(huì)增加HTTP請(qǐng)求的數(shù)量。
關(guān)鍵因素是,與HTML文檔請(qǐng)求數(shù)量相關(guān)的、外部js組件被緩存的頻率。這個(gè)因素盡管難以量化,但可以通過下面的手段進(jìn)行衡量:
3.1頁(yè)面查看
每個(gè)用戶產(chǎn)生的頁(yè)面查看越少,內(nèi)部js的論據(jù)越強(qiáng)勢(shì)。想象一個(gè)普通用戶每個(gè)月只訪問你的網(wǎng)站一次。在每次訪問之間,外部js文件很可能從瀏覽器的緩存中移除。另一方面,如果普通用戶能夠產(chǎn)生很多的頁(yè)面查看,瀏覽器很可能將外部Js文件放在緩存中。使用外部文件提供js帶來的收益會(huì)隨著用戶每月的頁(yè)面查看次數(shù)或用戶每會(huì)話產(chǎn)生的頁(yè)面查看次數(shù)的增長(zhǎng)而增加。
3.2空緩存VS完整緩存
在比較內(nèi)部和外部文件時(shí),知道用戶緩存外部組件的可能性這一點(diǎn)非常重要。我們?cè)赮ahoo!進(jìn)行了測(cè)量,發(fā)現(xiàn)每天至少攜帶完整緩存訪問Yahoo!功能一次的用戶占40%到60%。同樣的研究表明,具有完整緩存額的頁(yè)面查看數(shù)量占75%到85%。注意第一個(gè)統(tǒng)計(jì)測(cè)量的是“唯一用戶”而第二個(gè)是“頁(yè)面查看”。具有完整緩存的頁(yè)面查看所占的百分比比攜帶完整緩存的唯一用戶的百分比高,這是因?yàn)楹芏嘤脩粼谝淮螘?huì)話中進(jìn)行了多次頁(yè)面查看。每天,用戶可能只有開始的一次訪問攜帶的是空緩存,之后的多次后續(xù)頁(yè)面查看都具有完整緩存。如果你的網(wǎng)站的本質(zhì)上能夠?yàn)橛脩魩砀咄暾彺媛剩褂猛獠课募氖找婢透蟆H绻惶赡墚a(chǎn)生完整緩存,則內(nèi)部腳本是更好的選擇。
3.3組件重用
如果你的網(wǎng)站中的每個(gè)頁(yè)面都使用了相同的js,使用外部文件可以提高這些組件的重用率。在這種情況下使用外部文件更加具有優(yōu)勢(shì),因?yàn)楫?dāng)用戶在頁(yè)面間導(dǎo)航時(shí),js組件已經(jīng)位于瀏覽器的緩存中了。相反的情況也很容易理解--------如果沒有任何兩個(gè)頁(yè)面共享相同的js,重用率就會(huì)非常低。難的是絕大多數(shù)網(wǎng)站不是非黑即白的。這就帶來一個(gè)單獨(dú)相關(guān)的問題--------當(dāng)把js打包到外部文件中時(shí),應(yīng)該把邊界劃在哪里?
在典型情況下,頁(yè)面之間的js的重用即不可能100%重疊,也不可能100%無(wú)關(guān)。在這種中間情形中,一個(gè)極端就是為每個(gè)頁(yè)面提供一組分離的外部文件。這種方式的缺點(diǎn)在于,每個(gè)頁(yè)面都強(qiáng)制用戶使用另外一組外部組件并產(chǎn)生令響應(yīng)時(shí)間變慢的HTTP請(qǐng)求。這種方式對(duì)于普通用戶只訪問一個(gè)頁(yè)面和很少進(jìn)行跨頁(yè)訪問的網(wǎng)站來說是有意義的。
另一個(gè)極端是創(chuàng)建一個(gè)單獨(dú)的、聯(lián)合了所有js的文件。這只要求用戶生成一個(gè)HTTP請(qǐng)求,但它增加了用戶首次進(jìn)行頁(yè)面查看時(shí)的下載數(shù)據(jù)量。在這種情況下,用戶瀏覽頁(yè)面時(shí)要下載的js多于所需的數(shù)量。而且,在任何一塊獨(dú)立的腳本改變后,都需要更新這個(gè)文件,使所有用戶已經(jīng)緩存了的當(dāng)前版本無(wú)效。這種情況對(duì)于那些每月會(huì)話數(shù)量較高、普通用戶在一個(gè)會(huì)話中訪問多個(gè)不同頁(yè)面的網(wǎng)站來說是有意義的。
如果你的網(wǎng)站不符合這兩種極端情況,最好的答案就是折中。將你的頁(yè)面劃分成幾種頁(yè)面類型,然后為每種類型創(chuàng)建單獨(dú)的腳本,這比維護(hù)一個(gè)單獨(dú)的文件要復(fù)雜,但通常比為每個(gè)頁(yè)面維護(hù)不同的腳本要容易,并且對(duì)于給定的任意頁(yè)面都只需要下載很少的多余的js。
最后你做出的與js外部文件的邊界相關(guān)的決定影響著組件的重用程度。如果你可以找到一個(gè)平衡點(diǎn),實(shí)現(xiàn)較高的重用性,那么外部文件的論據(jù)更強(qiáng)勢(shì)一些。如果重用度很低,還是內(nèi)部腳本更有意義些。
在對(duì)于內(nèi)部和外部腳本進(jìn)行比較分析時(shí),關(guān)鍵點(diǎn)在于與HTML文檔請(qǐng)求數(shù)量相關(guān)的外部js組件被緩存的頻率。在此我介紹了三種基準(zhǔn)(頁(yè)面查看、空緩存VS完整緩存和組件重用),這有助于你確定最好的選擇。對(duì)于任何網(wǎng)站來說,正確答案都依賴于這些基準(zhǔn)。
大家如果還想更詳細(xì)的了解瀏覽器腳本、css等的一些效率問題,可以看《高性能網(wǎng)站建設(shè)指南》,那里面的14條具體的優(yōu)化原則的確很精辟。
4 將腳本放在底部
4.1腳本帶來的問題
下面是一個(gè)腳本放在中部的示例
http://stevesouders.com/hpws/js-middle.php
經(jīng)過編程的腳本下載需要很長(zhǎng)時(shí)間,因此很容易看到問題--------頁(yè)面的下半部分要花很長(zhǎng)時(shí)間才能顯示。出現(xiàn)這一現(xiàn)象是因?yàn)槟_本阻塞了并行下載。在回顧了瀏覽器如何并行下載之后,我們?cè)倩剡^頭解決這一問題。
4.2并行下載
對(duì)響應(yīng)時(shí)間影響最大的是頁(yè)面中組件的數(shù)量。當(dāng)緩存為空,每個(gè)組件都會(huì)產(chǎn)生一個(gè)HTTP請(qǐng)求,有時(shí)即便緩存是完整的亦是如此。要知道瀏覽器會(huì)并行地執(zhí)行HTTP請(qǐng)求,你可能會(huì)問,為什么HTTP請(qǐng)求的數(shù)量會(huì)影響響應(yīng)時(shí)間呢?瀏覽器不能一次將它們都下載下來嗎?
對(duì)此的解釋要回到HTTP 1.1規(guī)范,該規(guī)范建議瀏覽器從每個(gè)主機(jī)名并行下載兩個(gè)組件。很多web頁(yè)面需要從一個(gè)主機(jī)名下載所有的組件。查看這些HTTP請(qǐng)求會(huì)發(fā)現(xiàn)它們是呈階梯狀的,如圖所示:
圖4.1
如果一個(gè)Web頁(yè)面平均地將其組件分別放在兩個(gè)主機(jī)名下,整體響應(yīng)時(shí)間將可以減少大約一半。HTTP請(qǐng)求的行為看起來會(huì)是圖4.2所示
圖4.2
此處可以并行下載四個(gè)組件(每個(gè)主機(jī)名兩個(gè))。為了對(duì)頁(yè)面加載變快的現(xiàn)象給出可視的效果,其中每個(gè)時(shí)間塊的橫向?qū)挾群蛨D4.1是一樣的。
每個(gè)主機(jī)名并行下載兩個(gè)組件的限制只是一個(gè)建議。默認(rèn)情況下瀏覽器都遵守這一建議,但用戶也可以重寫該默認(rèn)設(shè)置。但增加并行下載數(shù)量并不是沒有開銷的,其優(yōu)劣取決于你的寬帶和CPU速度。
4.3腳本阻塞下載
并行下載組件的優(yōu)點(diǎn)是很明顯的。然而,在一些比較舊的瀏覽器在下載腳本時(shí)并行下載實(shí)際上是被禁用的--------即使使用了不同的主機(jī)名,瀏覽器也不會(huì)啟動(dòng)其他的下載。其中一個(gè)原因是,腳本可能使用document.write來修飾頁(yè)面內(nèi)容,因此瀏覽器會(huì)等待,以確保頁(yè)面能夠恰當(dāng)?shù)牟季帧#ìF(xiàn)在的瀏覽器雖然可以并行下載,但是同樣阻塞布局)在下載腳本時(shí)瀏覽器阻塞并行下載的另一個(gè)原因是為了保證腳本能夠按照正確的順序執(zhí)行。如果并行下載多個(gè)腳本,就無(wú)法保證響應(yīng)是按照特定順序到達(dá)瀏覽器的。例如:后面的腳本比頁(yè)面中之前出現(xiàn)的腳本更小,它可能首先執(zhí)行。如果它們之間存在著依賴關(guān)系,不按照順序執(zhí)行就會(huì)導(dǎo)致js錯(cuò)誤。
如下是一個(gè)例子:
http://stevesouders.com/hpws/js-blocking.php
該頁(yè)面按照順序包含下列組件
1、來至host1的一個(gè)圖片
2、來至host2的一個(gè)圖片
3、來至host1的一個(gè)加載需要大約10秒的腳本
4、來至host1的一個(gè)圖片
5、來至host2的一個(gè)圖片
4.4最差情況:將腳本放在頂部
至此,腳本對(duì)Web頁(yè)面的影響就清楚了:
1、腳本會(huì)阻塞對(duì)其后面內(nèi)容的呈現(xiàn)
2、腳本會(huì)阻塞對(duì)其后面組件的下載
如果將腳本放在頁(yè)面頂部--------正如通常情況那樣--------頁(yè)面中的所有東西都位于腳本之后,整個(gè)頁(yè)面的呈現(xiàn)和下載都會(huì)被阻塞,直到腳本加載完畢腳本放在頂部的示例:
http://stevesouders.com/hpws/js-top.php
由于整個(gè)頁(yè)面的呈現(xiàn)被阻塞,因此導(dǎo)致了白屏現(xiàn)象。逐步呈現(xiàn)對(duì)于良好的用戶體驗(yàn)來說是非常重要的,但緩慢的腳本下載延遲了用戶所期待的反饋。
4.5最佳情況:將腳本放在底部
放置腳本的最好地方時(shí)頁(yè)面的底部。這不會(huì)阻止頁(yè)面內(nèi)容的呈現(xiàn),而且頁(yè)面中的可是組件可以盡早下載。腳本放在底部的示例:
http://stevesouders.com/hpws/js-bottom.php
把兩個(gè)頁(yè)面--------腳本放在頂部的和腳本放在底部的--------并列放在一起瀏覽,其對(duì)比更為突出。可以在下面這個(gè)示例中看到這一點(diǎn):
http://stevesouders.com/hpws/move-scripts.php
4.6正確地放置
前面那些示例是使用了大概需要10秒才能下載完的腳本。希望你使用的腳本不需要這么長(zhǎng)時(shí)間的延遲,但一個(gè)腳本很可能花費(fèi)比預(yù)期長(zhǎng)的時(shí)間,用戶的寬帶也會(huì)影響腳本的響應(yīng)時(shí)間。你的頁(yè)面中的腳本所產(chǎn)生的影響可能沒有這里展示的那么嚴(yán)重,但仍需要注意。在頁(yè)面中包含多個(gè)腳本也會(huì)帶來問題。
在很多情況下,很難將腳本移到底部。例如,如果腳本使用document.write向頁(yè)面中插入了內(nèi)容,就不能將其移動(dòng)到頁(yè)面中靠后的位置。
經(jīng)常出現(xiàn)的另外一種建議是使用延遲腳本。Defer屬性表明腳本不包含document.write,瀏覽器得到這一線索就可繼續(xù)進(jìn)行呈現(xiàn)。從下面的示例可以看到這一點(diǎn):
http://stevesouders.com/hpws/js-defer.php
但是不保險(xiǎn),有一些老的瀏覽器不能識(shí)別defer,所以最好還是將腳本放于底部。
5動(dòng)態(tài)加載腳本
詳見我之前的博客
js動(dòng)態(tài)加載腳本
6三種實(shí)用方式
6.1異步批量添加外部腳本
很多時(shí)候我們由于產(chǎn)品模塊的劃分,一個(gè)頁(yè)面可能需要加載幾個(gè)腳本,我們需要考慮兩點(diǎn):1、腳本之間是否有依賴關(guān)系,如果存在依賴關(guān)系即使我們使用script標(biāo)簽是按照順序的,但是并行下載是一起下載的,如果出現(xiàn)后面的包先下載完,那么執(zhí)行腳本時(shí)就可能出現(xiàn)錯(cuò)誤;2、考慮到效率,一般情況下異步加載比同步加載會(huì)快一些。為了解決如上問題,我們可以利用之前討論的知識(shí)進(jìn)行組合
<html>
<head>
<title></title>
<script type="text/javascript">
function init()
{
//這里第一個(gè)參數(shù)是一個(gè)數(shù)組,可以任意多個(gè),加載順序按照數(shù)組的順序進(jìn)行保證
//第二個(gè)參數(shù)是回調(diào)函數(shù),當(dāng)所有包都確認(rèn)加載完畢后需要執(zhí)行的腳本
//第三個(gè)參數(shù)是script的標(biāo)簽,這個(gè)參數(shù)可以省略,沒有實(shí)質(zhì)意義
attachScript(["http://www.cnblogs.com/5/loadJS.js","http://www.cnblogs.com/5/package.js"],operation,"yy")();
}
function operation()
{
//可以運(yùn)行,顯示“成功加載”
functionOne();
}
//異步批量加載腳本,并且根據(jù)數(shù)組urlArray中的url順序來加載
function attachScript(urlArray, callback, id) {
if(urlArray && ((typeof urlArray) == "object"))
{
if(urlArray.constructor == Array)
{
if(urlArray.length>1)
{
var array = urlArray.splice(0,1);
return function(){
var dataScript = document.createElement('script');
dataScript.type = 'text/javascript';
if(dataScript.readyState) { //IE
dataScript.onreadystatechange = function() {
if(dataScript.readyState == 'complete'|| dataScript.readyState == 'loaded'){
dataScript.onreadystatechange = null;
attachScript(urlArray,callback,id)();
}
}
} else { //standers
dataScript.onload = function() {
attachScript(urlArray,callback,id)();
}
}
dataScript.src = array[0];
dataScript.id = id;
document.body.appendChild(dataScript);
}
}
else if(urlArray.length == 1)
{
return function(){
var dataScript = document.createElement('script');
dataScript.type = 'text/javascript';
if(dataScript.readyState) { //IE
dataScript.onreadystatechange = function() {
if(dataScript.readyState == 'complete'|| dataScript.readyState == 'loaded'){
dataScript.onreadystatechange = null;
callback();
}
}
} else { //standers
dataScript.onload = function() {
callback();
}
}
dataScript.src = urlArray[0];
dataScript.id = id;
document.body.appendChild(dataScript);
}
}
}
}
}
</script>
</head>
<body>
<input type="button" value="測(cè)試按鈕" onclick="init()"/>
</body>
</html>
使用很方便,通過一個(gè)方法attachScript可以加載你的任意多個(gè)有序的ja包,并且下載的時(shí)候還是并行下載,效率上也還不錯(cuò),你可以把這個(gè)方法單獨(dú)打包,方便以后使用,不過里面的代碼也許需要稍微修改一下,有些地方不嚴(yán)謹(jǐn)哦!
6.2同步分類(或模塊)動(dòng)態(tài)加載
這里的動(dòng)態(tài)加載是指當(dāng)用戶使用到了某個(gè)類或者模塊才去加載,并且加載不是用戶來控制,而是自動(dòng)的。
優(yōu)點(diǎn):
1、同步加載可以很好的保證腳本的依賴關(guān)系
2、用時(shí)才加載,可以保證基礎(chǔ)包盡量小,提高用戶體驗(yàn)
缺點(diǎn):
1、同步加載相對(duì)異步加載來說一般偏慢
2、在未發(fā)布的情況下不支持Chrome、Opera
詳細(xì)的說明請(qǐng)看:js動(dòng)態(tài)加載腳本之實(shí)用小技巧
6.3異步分類(或模塊)動(dòng)態(tài)加載
這里采用回調(diào)函數(shù)形式的異步加載
優(yōu)點(diǎn):
1、異步加載速度快
2、使用回調(diào)函數(shù)也可以保證腳本的良好依賴關(guān)系
3、同樣時(shí)基礎(chǔ)包小,提高用戶體驗(yàn)
缺點(diǎn):
1、調(diào)試不太方便
2、類被劃分為兩個(gè)部分,劃分難度大
說明:大家如果了解了6.2的同步分模塊加載后發(fā)現(xiàn)最大的缺點(diǎn)在于未發(fā)布的情況下不支持某些瀏覽器,并且代碼的執(zhí)行中途會(huì)強(qiáng)制被阻止,當(dāng)某些代碼下載下來后在執(zhí)行,這樣的話所有代碼的下載都是呈線性的下載,沒有并行,效率會(huì)比較低。
不知道大家有木有注意過百度地圖的包和google地圖的包是如何實(shí)現(xiàn)的,他們實(shí)現(xiàn)方式一樣:基礎(chǔ)包都是大概70到80kb的樣子,比較小(所有地圖功能包加起來有200到300左右),第一次訪問地圖的時(shí)候就比較小,效率快,用戶體驗(yàn)好,但是用戶在地圖上面觸發(fā)了其他復(fù)雜的功能(比如交通換乘)時(shí),你會(huì)發(fā)現(xiàn)它開始下載一些比較小的包,但這些包是并行下載的。它是如何做到的呢?
其實(shí)基礎(chǔ)包里面已經(jīng)將所有的API都已經(jīng)定義了,如果沒定義,那運(yùn)行到那一塊的時(shí)候?yàn)g覽器肯定會(huì)報(bào)錯(cuò)誤,但是所有功能都在基礎(chǔ)包里不可能那么小,是因?yàn)榘俣鹊幕A(chǔ)包里面定義的所有的接口(API),但是只有簡(jiǎn)單的屬性是真實(shí)可用的,而那些復(fù)雜的方法都是空的,也就是這些方法里面只是負(fù)責(zé)記錄是否執(zhí)行了此方法,那樣這些方法必定代碼很少,所以基礎(chǔ)包就很小,等程序執(zhí)行一遍后再去下載需要的功能模塊,下完后再按照之前記錄的信息重新執(zhí)行一遍,而這些包里面的方法必將覆蓋以前的假的方法,當(dāng)?shù)诙问褂玫臅r(shí)候就執(zhí)行的真正的方法了。
下面我們來寫一個(gè)比較簡(jiǎn)單的例子看一下:
先看一下測(cè)試的頁(yè)面:
<html>
<head>
<title></title>
<script type="text/javascript" src="Core.js"></script>
<script type="text/javascript">
var button;
function init1()
{
button =new SuperMap.Control.Button(20,10);
var height = button.getHeight();
button.draw();
}
function init2()
{
button.draw();
}
</script>
</head>
<body>
<input type="button" onclick="init1()" value="按鈕" />
<input type="button" onclick="init2()" value="按鈕2" />
</body>
</html>
這里點(diǎn)擊“按鈕”,初始化了一個(gè)Button的對(duì)象,然后調(diào)用了方法getHeight(真的簡(jiǎn)單方法)和draw(假的復(fù)雜方法,第一次調(diào)用,只是記錄),再次點(diǎn)擊“按鈕2”,調(diào)用方法draw(真的復(fù)雜方法,第二次調(diào)用,假的已經(jīng)被覆蓋了)。
這里Core.js是基礎(chǔ)包,代碼如下:
//一下為框架代碼,大家不需要了解
window.SuperMap = {
VERSION_NUMBER: "Release 6.1.3",
_getScriptLocation: (function() {
//SuperMap-6.1.1-8828
var r = new RegExp("(^|(.*?\\/))(SuperMap(-(\\d{1}\.)*\\d{1}-\\d{4,})?\.js)(\\?|$)"),
s = document.getElementsByTagName('script'),
src, m, l = "";
for(var i=0, len=s.length; i<len; i++) {
src = s[i].getAttribute('src');
if(src) {
var m = src.match(r);
if(m) {
l = m[1];
break;
}
}
}
return (function() { return l; });
})()
};
SuperMap.Control = SuperMap.Control || {};
SuperMap.Util = SuperMap.Util || {};
SuperMap.Class = function() {
var len = arguments.length;
var P = arguments[0];
var F = arguments[len-1];
var C = typeof F.initialize == "function" ? F.initialize : function(){ P.prototype.initialize.apply(this, arguments); };
if (len > 1) {
var newArgs = [C, P].concat( Array.prototype.slice.call(arguments).slice(1, len-1), F);
SuperMap.inherit.apply(null, newArgs);
} else {
C.prototype = F;
}
return C;
};
SuperMap.inherit = function(C, P) {
var F = function() {};
F.prototype = P.prototype;
C.prototype = new F;
var i, l, o;
for(i=2, l=arguments.length; i<l; i++) {
o = arguments[i];
if(typeof o === "function") {
o = o.prototype;
}
SuperMap.Util.extend(C.prototype, o);
}
};
SuperMap.Util = SuperMap.Util || {};
SuperMap.Util.extend = function(destination, source) {
destination = destination || {};
if (source) {
for (var property in source) {
var value = source[property];
if (value !== undefined) {
destination[property] = value;
}
}
var sourceIsEvt = typeof window.Event == "function"
&& source instanceof window.Event;
if (!sourceIsEvt
&& source.hasOwnProperty && source.hasOwnProperty("toString")) {
destination.toString = source.toString;
}
}
return destination;
};
SuperMap.Util.copy = function(des, soc) {
des = des || {};
var v;
if(soc) {
for(var p in des) {
v = soc[p];
if(typeof v !== 'undefined') {
des[p] = v;
}
}
}
};
SuperMap.Util.reset = function(obj) {
obj = obj || {};
for(var p in obj) {
if(obj.hasOwnProperty(p)) {
if(typeof obj[p] === "object" && obj[p] instanceof Array) {
for(var i in obj[p]) {
if(obj[p][i].destroy) {
obj[p][i].destroy();
}
}
obj[p].length = 0;
} else if(typeof obj[p] === "object" && obj[p] instanceof Object) {
if(obj[p].destroy) {
obj[p].destroy();
}
}
obj[p] = null;
}
}
};
//以下為核心代碼
//加載腳本的方法
SuperMap.Util.loadJs = function(urlArray, callback, id) {
if(urlArray && ((typeof urlArray) == "object"))
{
if(urlArray.constructor == Array)
{
if(urlArray.length>1)
{
var array = urlArray.splice(0,1);
return function(){
var dataScript = document.createElement('script');
dataScript.type = 'text/javascript';
if(dataScript.readyState) { //IE
dataScript.onreadystatechange = function() {
if(dataScript.readyState == 'complete'|| dataScript.readyState == 'loaded'){
dataScript.onreadystatechange = null;
SuperMap.Util.loadJs(urlArray,callback,id)();
}
}
} else { //standers
dataScript.onload = function() {
SuperMap.Util.loadJs(urlArray,callback,id)();
}
}
dataScript.src = array[0];
dataScript.id = id;
document.body.appendChild(dataScript);
}
}
else if(urlArray.length == 1)
{
return function(){
var dataScript = document.createElement('script');
dataScript.type = 'text/javascript';
if(dataScript.readyState) { //IE
dataScript.onreadystatechange = function() {
if(dataScript.readyState == 'complete'|| dataScript.readyState == 'loaded'){
dataScript.onreadystatechange = null;
callback();
}
}
} else { //standers
dataScript.onload = function() {
callback();
}
}
dataScript.src = urlArray[0];
dataScript.id = id;
document.body.appendChild(dataScript);
}
}
}
}
}
//用于記錄模塊是否加載
SuperMap.Util.IsControl = false;
//按照模塊名稱來加載腳本
SuperMap.Util.load = function(backName,callbackFunction){
if(backName == "Control")
{
if(SuperMap.Util.IsControl == false)
{
SuperMap.Util.loadJs(["Control.js"],callbackFunction,546756)();
}
SuperMap.Util.IsControl == true;
}
//其他模塊
else if(...)
{
...
}
//....
}
//用于測(cè)試的類 Control模塊
SuperMap.Control.Button = SuperMap.Class({
w: 0.0,
h: 0.0,
initialize: function(w, h) {
this.w = parseFloat(w);
this.h = parseFloat(h);
this.flow = [];
//需要注冊(cè)一個(gè)回調(diào)函數(shù)
var c = this;
SuperMap.Util.load("Control",function(){
//等加載完腳本后重新執(zhí)行一遍
c.init();
});
},
getWidth:function()
{
return this.w;
},
setWidth:function(value)
{
this.w = value;
},
getHeight:function()
{
return this.h;
},
setHeight:function(value)
{
this.h = value;
},
getArea:function() {
return this.w * this.h;
},
clone:function() {
return new SuperMap.Control.Button(this.w, this.h);
},
//假的方法
draw:function(){
this.flow.push({method: "draw", arguments: null});
},
CLASS_NAME: "SuperMap.Control.Button"
});
這里的SuperMap.Control.Button類為不完整的類,注意構(gòu)造函數(shù)里面有一個(gè)回調(diào)函數(shù),當(dāng)整個(gè)類加載完后通過init入口重新執(zhí)行一遍操作,而里面除了draw是假的以外,其他都是真的。
再來看一下Control.js模塊:
var Button = SuperMap.Control.Button;
//必須有的入口方法
Button.prototype.init = function()
{
for(var i = 0;i<this.flow.length;i++)
{
//挨個(gè)執(zhí)行
this[this.flow[i].method](this.flow[i].arguments);
}
delete this.flow;
}
//比較復(fù)雜的方法
Button.prototype.draw = function()
{
//....
//alert("繪制完畢");
}
看完思路大家就會(huì)發(fā)現(xiàn)他有自己的缺點(diǎn):我們調(diào)試怎么辦,調(diào)試的時(shí)候獲取的對(duì)象就不是那么明顯了,很不方便;怎么把一個(gè)類劃分成為兩個(gè)部分,這一點(diǎn)特別的難,我不是百度的員工,也不清楚他們的標(biāo)準(zhǔn)。不過可以猜想他們這樣的有點(diǎn)很明顯:你管我代碼怎么的,反正用戶用起來發(fā)現(xiàn)效率很快就行,基礎(chǔ)包很小,用到哪塊再加載哪塊,用戶體驗(yàn)特別的好,并行加載,效率快。
沒有什么是100%滿意的,只要抓住重點(diǎn)就行,百度和google舍棄了調(diào)試的便利以及開發(fā)的簡(jiǎn)易(使程序員痛苦),但是獲得了廣大用戶的良好評(píng)價(jià),這就是他們的目的。
這里的三點(diǎn)實(shí)用方式希望能給大家給予一定的幫助吧!
總結(jié)
- 上一篇: Tcl与Design Compiler
- 下一篇: jinja2的使用