一、ready函數的實現
經常用jQuery類庫或其他類庫中的ready方法,有時候想想它們到底是怎麼實現的,但是看了一下jQuery中的源碼,涉及到的模塊比較多,(水平有限)代碼比較難看懂;自己結合了一些書籍內容,總結一下。
先說一下ready函數的實現思路:
變量ready通過表達式賦值,右側為一個自執行匿名函數,在這個匿名函數中,首先為各個浏覽器的事件綁定處理函數,並為isReady賦值(根據事件異步處理程序來確定),然後返回一個傳參閉包,在閉包中,主要判斷isReady值來執行操作,如果dom結構准備就緒(isReady === true),執行回調,否則將回調加入到要執行的隊列(funs)中,待事件處理程序執行時,循環遍歷隊列(funs),並依次執行隊列中的函數,執行完隊列中的函數後,還需要清除隊列(funs = null)。
. 代碼如下:
var ready = (function(){
var isReady = false,
funs = [];
function handle (e) {
if ( isReady ) {
return;
}
if ( e.type === 'readystatechange' && (document.readyState !== 'interactive' && document.readyState !== 'complete') ) {
return;
}
for ( var i = 0; i < funs.length; i++ ) {
funs[i].call(document);
}
isReady = true;
funs = null;
}
if ( document.addEventListener ) {
document.addEventListener( 'DOMContentLoaded', handle, false );
document.addEventListener( 'readystatechange', handle, false );
document.addEventListener( 'load', handle, false );
}
else if ( document.attachEvent ) {
document.attachEvent( 'onreadystatechange', handle );
document.attachEvent( 'onload', handle );
}
return function ready (callback) {
if ( isReady ) {
callback.call(document);
}
else {
funs.push(callback);
}
};
}());
PS:
該函數代碼參照於權威指南書籍,唯一不同的是,多加了一個判斷document.readyState !== 'interactive'
. 代碼如下:
if ( e.type === 'readystatechange' && (document.readyState !== 'interactive' && document.readyState !== 'complete') ) {
return;
}
在各個浏覽器中交互和完成狀態出現順序並不能保證一致,這取決於浏覽器及頁面的內容,多加了這個判斷document.readyState !== 'interactive'的話,
意思是不管哪個階段先出現,代碼都能更早的執行。
二、按需加載css,js
參照了jQuery源碼,寫了一個type函數,返回參數類型。
. 代碼如下:
/**
*
* 判斷參數類型
* createTime: 2013/9/18
*
*/
function type (obj) {
var classTypes, objectTypes;
if ( obj == null ) {
return String(obj);
}
classTypes = {};
objectTypes = ('Boolean Number String Function Array Date RegExp Object Error').split(' ');
for ( var i = 0, len = objectTypes.length; i < len; i++ ) {
classTypes[ '[object ' + objectTypes[i] + ']' ] = objectTypes[i].toLowerCase();
}
if ( typeof obj === 'object' || typeof obj === 'function' ) {
var key = Object.prototype.toString.call(obj);
return classTypes[key];
}
return typeof obj;
}
. 代碼如下:
// css按需加載
function loadCss (cssUrl, callback) {
var elem, bl,
isExecuted = false; // 防止在ie9中,callback執行兩次
if ( cssUrl == null ) {
return String(cssUrl);
}
elem = document.createElement('link'),
elem.rel = 'stylesheet';
if ( type(callback) === 'function' ) {
bl = true;
}
// for ie
function handle() {
if ( elem.readyState === 'loaded' || elem.readyState === 'complete' ) {
if (bl && !isExecuted) {
callback();
isExecuted = true;
}
elem.onreadystatechange = null;
}
}
elem.onreadystatechange = handle;
// for 非ie
if (bl && !isExecuted) {
elem.onload = callback;
isExecuted = true;
}
elem.href = cssUrl;
document.getElementsByTagName('head')[0].appendChild(elem);
}
// js按需加載
function loadScript(scriptUrl, callback) {
var elem, bl,
isExecuted = false; // 防止在ie9中,callback執行兩次
if (scriptUrl == null) {
return String(fn);
}
elem = document.createElement('script');
if ( type(callback) === 'function' ) {
bl = true;
}
// for ie
function handle(){
var status = elem.readyState;
if (status === 'loaded' || status === 'complete') {
if (bl && !isExecuted) {
callback();
isExecuted = true;
}
elem.onreadystatechange = null;
}
}
elem.onreadystatechange = handle;
// for 非ie
if (bl && !isExecuted) {
elem.onload = callback;
isExecuted = true;
}
elem.src = scriptUrl;
document.getElementsByTagName('head')[0].appendChild(elem);
}
PS: 在判斷link,script元素是否加載完畢,主要依靠load事件;而在ie9以下浏覽器中,並沒有load事件,ie為它們都添加了一個readystatechange事件,通過判斷
元素的readyState狀態確定元素是否已經加載完畢;而奇怪的是,在ie9(還可能存在其他浏覽器版本)中,元素既有load事件又有readystatechange事件,因此在代碼中添加了一個變量isExecuted,如果執行過回調,那麼就不再執行,避免回調執行兩次。
三、調用方式
. 代碼如下:
loadCss('http://www.jb51.net/apps/tbtx/miiee/css/base.css', function(){
console.log('css加載完畢');
});
loadScript('http://www.jb51.net/apps/tbtx/miiee/js/jQuery.js', function(){
console.log('js加載完畢');
});
ready(function(){
console.log('dom is ready!');
});