學習js的時候,經常會遇到這樣的問題,如何控制dom、js在頁面上的載入順序。
Peter Michaux 有一篇文章非常具體地分析了各種控制 js 裝載過程的方法和優劣。
結合他的一些做法分析了一下,大致是這樣的思路。
首先可能會用到 defer來強制頁面載入完成後來再運行js,像這樣:
<script src="x.js" type="text/javascript" defer></script>
看似用起來沒什麼問題,但是發現無法相容Mozilla。
既然這樣不行,就只好換種方法,利用 window.onload 來捕獲頁面的載入事件。
在js檔案裡:
window.onload = function() {
alert("load over");
}
這一切的一切想必大家都是很熟悉的了,但是,之後就沒有問題了嗎?假設頁面dom裡有一張圖片,像這樣:
<img src="picture.jpg" >
而這張圖片又非常之大,那麼在dom載入完畢之前,js是無法執行的。問題就在於假設dom提供了使用者互動的功能。例如按鈕,輸入表單等,這個時候他們已經是被呈現了的,因此就很有可能產生無效的使用者行為。
我們不能指望使用者會安分守己地等待頁面顯示載入完畢後再發生動作,而要把使用者考慮成隨時隨刻會到處亂點的朋友。
這個問題又如何解決呢?既然我們需要頁面結構輸出後執行js,我們不妨把js入口函數定義在頁面最下面好了。
<head>
<script src="x.js" type="text/javascript"></script>
</head>
<body>
......
<img src="picture.jpg" >
<script type="text/javascript">init();</script>
</body>
這樣就達到我們的目的了,頁面結構輸出完畢後就執行js,不用考慮圖片的載入。
但是在文檔末尾嵌入一條js指令碼,畢竟容易被忽略,把關鍵的程式入口放在這種渺小的角落,總覺得不太合適。那有什麼預留退路的方法沒有呢?
我們可以把結尾的指令碼稍微修改一下:
<head>
<script src="x.js" type="text/javascript"></script>
</head>
<body>
......
<img src="picture.jpg" >
<script type="text/javascript">window.onload();</script>
</body>
而在js裡預先把入口定義給onload事件:
window.onload = function() {
alert("load over");
}
這時候頁面結構載入完畢後就會調用onload函數,而即使漏寫了dom裡的onload入口,js自身裡的onload定義也會在頁面載入完畢後執行,這樣退路就留出來了。
不過這時候有個問題,onload事件會執行兩次,可以在js的onload實現裡解決這個問題,改成這樣:
var flag = false;
window.onload = function() {
if (flag) {return;}
flag= true;
alert("load over");
}
這樣似乎已經解決我們所有的問題了,不過仍然有些小遺憾,因為最後一行代碼,致使行為與結構沒有分離開來,要 unobtrusive 就要 unobtrusive 的徹底,為了達到完美的分離,還有很大的討論空間。
而對於js檔案內部的onload事件,我們還可以參考 Simon Willison 的addLoadEvent函數來最佳化:
function addLoadEvent(func) {
var oldonload = window.onload;
if (typeof window.onload != 'function') {
window.onload = func;
} else {
window.onload = function() {
if (oldonload) {
oldonload();
}
func();
}
}
}
然後,我們就可以在js裡肆無忌憚地不停地將各個不同的函數添加到onload事件響應中了:
addLoadEvent(funcA);
addLoadEvent(funcB);
addLoadEvent(funcC);
當然,同一個js裡設定多個onload響應函數其實沒什麼必要,我們完全可以把funcA、funcB、funcC封裝在一個函數裡add,addLoadEvent函數,更理想的狀態是為頁面動態調用的多個js檔案添加入口。