js-Dom ready

很多时候我们需要在页面刚加载完的时候给一些DOM元素绑定处理函数。

不过你不会愿意去监听onload这个页面加载完毕的事件的,不信你点击这里

在标准浏览器器中是有DOMContentLoaded事件的,这个事件最早其实是firefox的私有事件,而后其他的浏览器才开始引入这一事件。

看一下这两个事件的区别:


onload 事件触发时,页面上所有的DOM,样式表,脚本,图片,flash
都已经加载完成了。


DOMContentLoaded 事件触发时,仅当DOM加载完成,不包括样式表,图片,flash

很明显DOMContentLoaded机制更加合理,因为我们可以容忍图片,flash延迟加载,却不可以容忍看见内容后页面不可交互。

不过在DOMContentLoaded事件出现之前,很多类库就已经实现了自己的Dom ready事件监听,例如jQuery的$(document).ready()函数。

DOMContentLoaded事件应该是在DOM加载完毕后触发,可因为浏览器的不同,触发的时机也略有不同。


加载样式表会阻塞外链脚本的执行

一些Gecko和Webkit引擎版本的浏览器,包括IE8在内,会同时发起多个Http请求来并行下载样式表和脚本。但 脚本不会被执行,直到样式被加载完成。在未加载完之前甚至页面也不会被渲染。

但是在opera中样式的加载不会阻塞脚本的执行。

在Explorer和Gecko中,样式的加载同样也会阻塞直接写在页面上的脚本的执行(脚本接在样式表中)。在Webkit和Opera中页面上的脚本会被立即执行。

不过在很多情况下我们可能会需要对元素的一些css属性进行操作,所以在脚本之前引入css样式是一个好的习惯。


下面看下实现ready方法的思路:

1、标准浏览器:直接注册DOMContentLoaded事件。

2、IE浏览器:

方法一:在页面临时插入一个script元素,并设置defer属性,最后把该脚本加载完成视作DOMContentLoaded事件来触发。defer 属性规定是否对脚本执行进行延迟,直到页面加载为止。缺点是:插入脚本的页面包含iframe的话,会等到iframe加载完才触发。

    方法二:通过setTiemout来不断的调用documentElement的doScroll方法,直到调用成功则出触发DOMContentLoaded。原理是doScroll方法在页面未加载完毕之前调用会抛出异常,反过来说当调用这个方法成功后也就代表了页面已经加载完毕。

       方法三:监听document的onreadystatechange事件,当document.readyState==='complete'事则表示页面加载完毕。但经测试后该犯方法与window.onload相当,也会在图片等资源加载完毕之后才触发。不过在老式版本的IE中可以使用该方法,贴一下jQuery里面的一句注释:// readyState === "complete" is good enough for us to call the dom ready in oldIE


方法都有了,你可以试着写一个自己的DOM ready事件了,来看下jQuery是怎么实现的吧,看jQuery.11.2的源码:

jQuery.ready.promise = function( obj ) {
	if ( !readyList ) {

		readyList = jQuery.Deferred();

		// Catch cases where $(document).ready() is called after the browser event has already occurred.
		// we once tried to use readyState "interactive" here, but it caused issues like the one
		// discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15
		if ( document.readyState === "complete" ) {
			// Handle it asynchronously to allow scripts the opportunity to delay ready
			setTimeout( jQuery.ready );

		// Standards-based browsers support DOMContentLoaded
		} else if ( document.addEventListener ) {
			// Use the handy event callback
			document.addEventListener( "DOMContentLoaded", completed, false );

			// A fallback to window.onload, that will always work
			window.addEventListener( "load", completed, false );

		// If IE event model is used
		} else {
			// Ensure firing before onload, maybe late but safe also for iframes
			document.attachEvent( "onreadystatechange", completed );

			// A fallback to window.onload, that will always work
			window.attachEvent( "onload", completed );

			// If IE and not a frame
			// continually check to see if the document is ready
			var top = false;

			try {
				top = window.frameElement == null && document.documentElement;
			} catch(e) {}

			if ( top && top.doScroll ) {
				(function doScrollCheck() {
					if ( !jQuery.isReady ) {

						try {
							// Use the trick by Diego Perini
							// http://javascript.nwbox.com/IEContentLoaded/
							top.doScroll("left");
						} catch(e) {
							return setTimeout( doScrollCheck, 50 );
						}

						// detach all dom ready events
						detach();

						// and execute any waiting functions
						jQuery.ready();
					}
				})();
			}
		}
	}
	return readyList.promise( obj );
};
1、因为有可能在调用该事件的时候,页面已经加载完毕了,所以先判断document的readyState属性是否为complete。

2、如果是标准浏览器则注册DOMContentLoaded事件。

3、如果是IE浏览器,注册onreadystatechange事件,然后当页面不是一个frame时,不停调用doScroll这个trick,如果不发生异常则DOM is ready。

4、在上述的2、3情况下都注册了一个load事件作为fallback,保证总是会正确工作。

另外值得一提的是:在情况1中,jQuery使用了setTimeout来进行延迟调用,原因是:jQuery.ready.promise方法返回的是一个promise对象(具体可以看jQuery.Deferred对象,有时间讲解该对象),而在jQuery.fn.ready方法中是这么写的:

jQuery.fn.ready = function( fn ) {
	// Add the callback
	jQuery.ready.promise().done( fn );

	return this;
};
也就是说,在jQuery.ready.promise()后返回一个promise对象,然后执行done方法把fn函数添加到readList的执行队列中来。扯了这么远,回到setTimeout(jQuery.ready)这一句来,如果不使用异步回调,他会立即执行jQuery.ready方法,jQuery.ready的源码中有这么一句:
readyList.resolveWith( document, [ jQuery ] );
如果立即执行,那么fn还没有被添加到readyList执行队列中,那么就不会调用到fn函数,所以异步调用就是让fn函数首先添加到readyList这个执行队列中来之后再调用jQuery.ready方法,那么就可以正常执行该函数。


同样,如果你再查看prototype的源码,你会发现注册该事件的思路基本相同,可以自行阅读:

(function() {
  /* Support for the DOMContentLoaded event is based on work by Dan Webb,
     Matthias Miller, Dean Edwards, John Resig, and Diego Perini. */

  var timer;

  function fireContentLoadedEvent() {
    if (document.loaded) return;
    if (timer) window.clearTimeout(timer);
    document.loaded = true;
    document.fire('dom:loaded');
  }

  function checkReadyState() {
    if (document.readyState === 'complete') {
      document.stopObserving('readystatechange', checkReadyState);
      fireContentLoadedEvent();
    }
  }

  function pollDoScroll() {
    try { document.documentElement.doScroll('left'); }
    catch(e) {
      timer = pollDoScroll.defer();
      return;
    }
    fireContentLoadedEvent();
  }

  if (document.addEventListener) {
    document.addEventListener('DOMContentLoaded', fireContentLoadedEvent, false);
  } else {
    document.observe('readystatechange', checkReadyState);
    if (window == top)
      timer = pollDoScroll.defer();
  }

  Event.observe(window, 'load', fireContentLoadedEvent);
})();

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值