一、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://a.tbcdn.cn/apps/tbtx/miiee/css/base.css', function(){ console.log('css加载完毕'); }); loadScript('http://a.tbcdn.cn/apps/tbtx/miiee/js/jQuery.js', function(){ console.log('js加载完毕'); }); ready(function(){ console.log('dom is ready!'); });