Vanilla JavaScript 等效于 jQuery 的 $ .ready() - 如何在页面/DOM 准备好时调用函数
Vanilla JavaScript 等效于 jQuery 的 $ .ready() - 如何在页面/DOM 准备好时调用函数
这个问题已经有答案了:
使用jQuery,我们都知道美妙的.ready()
函数:
$('document').ready(function(){});
然而,假设我想运行一个用标准JavaScript编写的没有库支持的函数,并且我想在页面准备好处理它时启动一个函数。正确的方法是什么?
我知道我可以这样做:
window.onload="myFunction()";
或者我可以使用body
标签:
或者我甚至可以尝试在页面底部,在所有内容之后,但在body
或html
标签之前:
有没有一种跨浏览器(旧/新)兼容的方法,以类似于jQuery的$.ready()
的方式发出一个或多个函数?
如果你在使用纯JavaScript而不是jQuery,那么你必须使用(Internet Explorer 9或更高版本):
document.addEventListener("DOMContentLoaded", function(event) { // Your code to run since DOM is loaded and ready });
上面的代码相当于jQuery的.ready
:
$(document).ready(function() { console.log("Ready!"); });
也可以像这样用简写来编写,当jQuery准备好后会运行它:(发生)。
$(function() { console.log("ready!"); });
不要与下面混淆(下面的代码不是DOM准备就绪):
不要像这样使用IIFE,使其自运行:
Example: (function() { // Your page initialization code here - WRONG // The DOM will be available here - WRONG })();
这个IIFE不会等待DOM加载。 (我甚至谈论的是最新版本的Chrome浏览器!)
在没有一个框架可以为您完成所有跨浏览器兼容性的情况下,最简单的方法就是在 body 的末尾调用您的代码。这比 onload
处理程序更快,因为它仅等待 DOM 准备就绪,而不是等待所有图像加载完成。而且,这适用于所有浏览器。
Your HTML here
对于现代浏览器(从 IE9 及以上版本以及任何版本的 Chrome、Firefox 或 Safari),如果您想能够实现类似于 jQuery 的 $(document).ready()
方法,而不必担心调用脚本的位置,您可以使用类似于这样的东西:
function docReady(fn) { // see if DOM is already available if (document.readyState === "complete" || document.readyState === "interactive") { // call on next available tick setTimeout(fn, 1); } else { document.addEventListener("DOMContentLoaded", fn); } }
用法:
docReady(function() { // DOM is loaded and ready for manipulation here });
如果您需要完全的跨浏览器兼容性(包括旧版本的 IE)并且您不想等待 window.onload
,那么您可能应该去看一下像 jQuery 这样的框架如何实现它的 $(document).ready()
方法。这取决于浏览器的功能而有所不同。
为了让您对 jQuery 的操作有一个小的概念(在脚本标记放置的位置可以工作的代码)。
如果支持,它会尝试标准的:
document.addEventListener('DOMContentLoaded', fn, false);
并回退到:
window.addEventListener('load', fn, false )
或对于旧版本的 IE,则使用:
document.attachEvent("onreadystatechange", fn);
并回退到:
window.attachEvent("onload", fn);
而且,在 IE 代码路径中有一些解决方案,我不是很清楚,但它看起来与框架有关。
这里是一个完整的用纯 JavaScript 编写的 jQuery 的 .ready()
替代品:
(function(funcName, baseObj) { // The public function name defaults to window.docReady // but you can pass in your own object and own function name and those will be used // if you want to put them in a different namespace funcName = funcName || "docReady"; baseObj = baseObj || window; var readyList = []; var readyFired = false; var readyEventHandlersInstalled = false; // call this when the document is ready // this function protects itself against being called more than once function ready() { if (!readyFired) { // this must be set to true before we start calling callbacks readyFired = true; for (var i = 0; i < readyList.length; i++) { // if a callback here happens to add new ready handlers, // the docReady() function will see that it already fired // and will schedule the callback to run right after // this event loop finishes so all handlers will still execute // in order and no new ones will be added to the readyList // while we are processing the list readyList[i].fn.call(window, readyList[i].ctx); } // allow any closures held by these functions to free readyList = []; } } function readyStateChange() { if ( document.readyState === "complete" ) { ready(); } } // This is the one public interface // docReady(fn, context); // the context argument is optional - if present, it will be passed // as an argument to the callback baseObj[funcName] = function(callback, context) { if (typeof callback !== "function") { throw new TypeError("callback for docReady(fn) must be a function"); } // if ready has already fired, then just schedule the callback // to fire asynchronously, but right away if (readyFired) { setTimeout(function() {callback(context);}, 1); return; } else { // add the function and context to the list readyList.push({fn: callback, ctx: context}); } // if document already ready to go, schedule the ready function to run if (document.readyState === "complete") { setTimeout(ready, 1); } else if (!readyEventHandlersInstalled) { // otherwise if we don't have event handlers installed, install them if (document.addEventListener) { // first choice is DOMContentLoaded event document.addEventListener("DOMContentLoaded", ready, false); // backup is window load event window.addEventListener("load", ready, false); } else { // must be IE document.attachEvent("onreadystatechange", readyStateChange); window.attachEvent("onload", ready); } readyEventHandlersInstalled = true; } } })("docReady", window);
该代码的最新版本在 GitHub 上公开共享,网址为:https://github.com/jfriend00/docReady
用法:
// pass a function reference docReady(fn); // use an anonymous function docReady(function() { // code here }); // pass a function reference and a context // the context will be passed to the function as the first argument docReady(fn, context); // use an anonymous function with a context docReady(function(context) { // code here that can use the context argument that was passed to docReady }, ctx);
这已经经过以下测试:
IE6 and up Firefox 3.6 and up Chrome 14 and up Safari 5.1 and up Opera 11.6 and up Multiple iOS devices Multiple Android devices
工作实现和测试平台:http://jsfiddle.net/jfriend00/YfD3C/
以下是它的工作原理摘要:
- 创建一个立即调用的函数表达式(IIFE),以便我们可以拥有非公共状态变量。
- 声明一个公共函数
docReady(fn, context)
- 当调用
docReady(fn, context)
时,检查是否已经触发了 ready 处理程序。如果是,则只需在此 JS 线程完成后使用setTimeout(fn, 1)
计划新添加的回调即可。 - 如果 ready 处理程序尚未触发,则将此新回调添加到要稍后调用的回调列表中。
- 检查文档是否已准备就绪。如果是,则执行所有 ready 处理程序。
- 如果我们尚未安装事件侦听器以了解文档何时准备就绪,则现在安装它们。
- 如果
document.addEventListener
存在,则使用.addEventListener()
为"DOMContentLoaded"
和"load"
事件安装事件处理程序。"load" 是一种备用事件,用于安全起见,不应该需要。 - 如果
document.addEventListener
不存在,则使用.attachEvent()
为"onreadystatechange"
和" onload "
事件安装事件处理程序。 - 在
onreadystatechange
事件中,检查document.readyState === "complete"
是否为真。如果是,请调用一个函数来触发所有 ready 处理程序。 - 在所有其他事件处理程序中,调用一个函数以触发所有 ready 处理程序。
- 在调用所有准备处理程序的函数中,检查一个状态变量,以查看我们是否已经启动。如果我们已经启动,则无事可做。如果我们尚未调用,则循环遍历准备好的函数数组,并按它们添加的顺序依次调用每个函数。设置一个标志以指示已经调用了所有的处理程序,以便它们永远不会被执行多次。
- 清除函数数组,以便可以释放它们可能正在使用的任何闭包。
使用docReady()
注册的处理程序保证按注册顺序触发。
如果您在文档已准备好之后调用 docReady(fn)
,则回调将被安排在当前执行线程完成后立即执行,使用 setTimeout(fn, 1)
。 这使得调用代码始终可以假设它们是异步回调,即使稍后是指当前 JS 线程完成后,也会保留调用顺序。