長(zhǎng)時(shí)間以來(lái)阿里媽媽的廣告載入策略一直存在些問(wèn)題,很多頁(yè)面也因?yàn)樽枞降膹V告載入而被拉低性能,影響用戶(hù)體驗(yàn)。畢竟阿里媽媽廣告的渲染依賴(lài)于諸多嵌套的document.write。ControlJS的目標(biāo)就是解決js的阻塞式渲染,因此靈玉急不可耐想與同仁們共同去揭秘ControlJS……
Steve Souders在2010年12月份發(fā)布了ControlJS項(xiàng)目,該項(xiàng)目是讓開(kāi)發(fā)者更好的去控制javascript文件的下載和執(zhí)行,從而提高了頁(yè)面腳本的加載速度。
Steve提出了一個(gè)非常具有創(chuàng)造性的思想,就是預(yù)先異步下載javascript文件而不解析執(zhí)行,直到需要的javascript處理時(shí)才去真正的執(zhí)行。這一點(diǎn)得到了很多人的關(guān)注與驗(yàn)證。Nicholas Zaka也因ControlJS引發(fā)了很多思考,并分析了ControlJS和LABjs 的區(qū)別所在,詳細(xì)內(nèi)容可以閱讀Thoughts on script loaders和Separating JavaScript download and execution。Steve使用3篇博聞詳細(xì)介紹了ControlJS:異步加載、延遲執(zhí)行、重寫(xiě)document.write。
ControlJS的原理
異步加載
ControlJS本身是異步進(jìn)行加載的,首先將script的標(biāo)簽type屬性值更改為瀏覽器無(wú)法識(shí)別的類(lèi)型,這樣瀏覽器不會(huì)認(rèn)為這是一個(gè)腳本。本身異步加載的ControlJS執(zhí)行時(shí)開(kāi)始遍歷type=”text/cjs”的script標(biāo)簽(包括內(nèi)嵌腳本),如果存在”DATA-CJSSRC”屬性將創(chuàng)建IMAGE或者OBJECT對(duì)象(依賴(lài)瀏覽器而選擇),去異步預(yù)下載腳本文件并緩存文件,直到window.onload時(shí)解析并執(zhí)行javascript,同時(shí)第二次去遍歷遺漏的script標(biāo)簽。具體操作可看Async WITH ControlJS
延遲執(zhí)行
瀏覽器在解析執(zhí)行javascript階段是阻塞任何操作的,這時(shí)的瀏覽器處于假死狀態(tài),Steve分析了美國(guó)的Alexa前10名網(wǎng)站的腳本初始需要加載執(zhí)行情況,發(fā)現(xiàn)只有29%是需要頁(yè)面加載時(shí)初始化解析執(zhí)行的,而其他71%的腳本是在觸發(fā)交互時(shí)才會(huì)執(zhí)行,壓縮后這些腳平均加載是229 kB,未壓縮是500KB,這是個(gè)大量的數(shù)據(jù)。
我們希望的結(jié)果是在頁(yè)面加載中不去解析執(zhí)行javascript,只是提前預(yù)下載好文件。例如通用的點(diǎn)擊彈出二級(jí)導(dǎo)航,用戶(hù)有可能永遠(yuǎn)都沒(méi)有點(diǎn)擊導(dǎo)航的行為,此時(shí)導(dǎo)航的功能腳本根本毫無(wú)用處。但是人們?cè)邳c(diǎn)擊導(dǎo)航時(shí)不希望等太久javascript的執(zhí)行,所以ControlJS會(huì)提前下載文件,這樣javascript只是解析執(zhí)行,不會(huì)花時(shí)間放到下載文件上。代碼一目了然,具體操作可看Menu WITH ControlJS
重寫(xiě)document.write
在默認(rèn)情況下這些異步腳本都是在window.onload解析執(zhí)行,如果此時(shí)腳本調(diào)用window.write方法,將導(dǎo)致一個(gè)不希望發(fā)生的問(wèn)題,就是整個(gè)頁(yè)面被window.write的輸出內(nèi)容替換,所有頁(yè)面內(nèi)容將被刪除,ie下將處于停滯狀態(tài)。產(chǎn)生問(wèn)題的原因是由于在docuemnt被加載完后調(diào)用docuemnt.write方法時(shí)將會(huì)自動(dòng)去觸發(fā)document.open,寫(xiě)入任何處于打開(kāi)狀態(tài)的doucment都將會(huì)會(huì)替換整個(gè)頁(yè)面的內(nèi)容。這便導(dǎo)致目前為止所有異步腳本無(wú)法延遲document.write的問(wèn)題,ControlJS的處理方法是重寫(xiě)docuemnt.write,如下:
CJS.docwriteOrig = document.write;
document.write = CJS.docwrite;
ControlJS創(chuàng)建一個(gè)dom元素(span),將其插入當(dāng)前被解析執(zhí)行的script標(biāo)簽之前,并且設(shè)置SPAN的innerHTML的值為docuemnt.write的內(nèi)容。具體操作可看document.write WITH ControlJS
用ControlJS優(yōu)化阿里媽媽廣告
對(duì)于現(xiàn)在大部分的廣告形式都是采用document.write方式寫(xiě)入,無(wú)法將這些內(nèi)容異步處理是開(kāi)發(fā)者非常頭疼的問(wèn)題。ControlJS可以為我們解決這類(lèi)煩惱。
沒(méi)有應(yīng)用ControlJS的網(wǎng)絡(luò)圖。DEMO可以看http://chesihui.github.com/ad-demo.html
應(yīng)用ControlJS優(yōu)化后的網(wǎng)絡(luò)圖。DEMO可以看http://chesihui.github.com/ControlJS-demo.html
ControlJS的局限性
ControlJS存在一個(gè)問(wèn)題是在document.write中多層嵌套script標(biāo)簽時(shí),頁(yè)面仍然存在觸發(fā)document.open的問(wèn)題。查看源代碼發(fā)現(xiàn)在執(zhí)行完一個(gè)javascript后都會(huì)恢復(fù)document.write的原生方法:
document.write = CJS.docwriteOrig;
動(dòng)態(tài)腳本的異步加載,同樣使得document.write方法也是異步執(zhí)行,因此不能恢復(fù)document.write的原生功能。復(fù)現(xiàn)的情況如 DEMO 。注釋這段腳本雖然解決了不觸發(fā)window.open的問(wèn)題,但是同樣的異步加載執(zhí)行導(dǎo)致無(wú)法正確定位廣告寫(xiě)入的位置。對(duì)于阿里媽媽廣告設(shè)置alimama_type=”i”的時(shí)候,載入圖片廣告是根據(jù)多層document.write實(shí)現(xiàn)的,只能正確渲染最后一個(gè)圖片廣告。復(fù)現(xiàn)如 DEMO 。
因?yàn)镃ontrolJS的異步加載不存在任何依賴(lài)順序,所有腳本都是并行加載執(zhí)行,如果你的頁(yè)面存在太多依賴(lài)關(guān)系,ControlJS將不會(huì)適合你的項(xiàng)目。
最后總結(jié)ControlJS為我們做了什么事,利弊還需要自己去權(quán)衡:
- 異步下載所有腳本
- 同時(shí)處理內(nèi)嵌與外鏈腳本
- 延遲腳本的執(zhí)行直到頁(yè)面被渲染完
- 允許腳本只下載不執(zhí)行
- 解決了異步腳本中存在docuement.write的問(wèn)題
- ControlJS本身是異步加載
原文:http://ued.taobao.com/blog/2011/03/18/controljs-alimama/