【问题标题】:Pure JavaScript equivalent of jQuery's $.ready() - how to call a function when the page/DOM is ready for it [duplicate]纯 JavaScript 等效于 jQuery 的 $.ready() - 当页面/DOM 准备好时如何调用函数[重复]
【发布时间】:2016-10-01 15:37:14
【问题描述】:

有了jQuery,我们都知道.ready()的奇妙功能:

$('document').ready(function(){});

但是,假设我想运行一个用标准 JavaScript 编写且没有库支持它的函数,并且我想在页面准备好处理它时立即启动一个函数。解决这个问题的正确方法是什么?

我知道我能做到:

window.onload="myFunction()";

或者我可以使用body标签:

<body onload="myFunction()">

或者我什至可以尝试在页面底部完成所有操作,但结尾 bodyhtml 标记如:

<script type="text/javascript">
    myFunction();
</script>

以 jQuery 的$.ready() 之类的方式发布一个或多个函数的跨浏览器(旧/新)兼容方法是什么?

【问题讨论】:

标签: javascript jquery html


【解决方案1】:

document.ondomcontentready=function(){} 应该可以解决问题,但它不具备完全的浏览器兼容性。

看来你应该只使用 jQuery min

【讨论】:

    【解决方案2】:

    您的方法(在结束正文标记之前放置脚本)

    <script>
       myFunction()
    </script>
    </body>
    </html>
    

    是支持新旧浏览器的可靠方式。

    【讨论】:

      【解决方案3】:

      在没有为您提供所有跨浏览器兼容性的框架的情况下,最简单的做法就是在正文末尾调用您的代码。这比 onload 处理程序执行得更快,因为它只等待 DOM 准备好,而不是等待所有图像加载。而且,这适用于所有浏览器。

      <!doctype html>
      <html>
      <head>
      </head>
      <body>
      Your HTML here
      
      <script>
      // self executing function here
      (function() {
         // your page initialization code here
         // the DOM will be available here
      
      })();
      </script>
      </body>
      </html>
      

      对于现代浏览器(来自 IE9 和更新版本的任何浏览器以及任何版本的 Chrome、Firefox 或 Safari),如果您希望能够实现类似 $(document).ready() 的 jQuery 方法,您可以从任何地方调用(无需担心调用脚本已定位),您可以使用以下内容:

      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/


      以下是其工作原理的摘要:

      1. 创建一个IIFE(立即调用的函数表达式),这样我们就可以拥有非公共状态变量。
      2. 声明一个公共函数docReady(fn, context)
      3. docReady(fn, context) 被调用时,检查就绪处理程序是否已经触发。如果是这样,只需安排新添加的回调在此 JS 线程以 setTimeout(fn, 1) 结束后立即触发。
      4. 如果就绪处理程序尚未触发,则将此新回调添加到稍后调用的回调列表中。
      5. 检查文档是否已经准备好。如果是,则执行所有就绪的处理程序。
      6. 如果我们还没有安装事件监听器,还不知道文档何时准备就绪,那么现在就安装它们。
      7. 如果存在document.addEventListener,则使用.addEventListener()"DOMContentLoaded""load" 事件安装事件处理程序。 “加载”是为了安全起见的备用事件,不应需要。
      8. 如果document.addEventListener 不存在,则使用.attachEvent()"onreadystatechange""onload" 事件安装事件处理程序。
      9. onreadystatechange 事件中,检查document.readyState === "complete" 是否存在,如果是,则调用一个函数来触发所有就绪的处理程序。
      10. 在所有其他事件处理程序中,调用一个函数来触发所有就绪处理程序。
      11. 在调用所有就绪处理程序的函数中,检查状态变量以查看我们是否已经触发。如果我们有,什么也不做。如果我们还没有被调用,则遍历准备好的函数数组,并按照它们被添加的顺序调用每个函数。设置一个标志以指示这些都已被调用,因此它们不会被多次执行。
      12. 清除函数数组,以便释放它们可能正在使用的任何闭包。

      使用docReady() 注册的处理程序保证按照它们注册的顺序被触发。

      如果您在文档准备就绪后调用docReady(fn),回调将被安排在当前执行线程使用setTimeout(fn, 1) 完成后立即执行。这允许调用代码始终假定它们是稍后将调用的异步回调,即使稍后是 JS 的当前线程完成并保留调用顺序。

      【讨论】:

        【解决方案4】:

        在 IE9 和最新的 Firefox 和 Chrome 中测试,在 IE8 中也受支持。

        document.onreadystatechange = function () {
          var state = document.readyState;
          if (state == 'interactive') {
              init();
          } else if (state == 'complete') {
              initOnCompleteLoad();
          }
        }​;
        

        示例:http://jsfiddle.net/electricvisions/Jacck/

        更新 - 可重复使用的版本

        我刚刚开发了以下内容。这是一个相当简单的等价于 jQuery 或 Dom,没有向后兼容性。它可能需要进一步完善。在最新版本的 Chrome、Firefox 和 IE (10/11) 中进行了测试,并且应该可以在旧版浏览器中使用,如评论所述。如果我发现任何问题,我会更新。

        window.readyHandlers = [];
        window.ready = function ready(handler) {
          window.readyHandlers.push(handler);
          handleState();
        };
        
        window.handleState = function handleState () {
          if (['interactive', 'complete'].indexOf(document.readyState) > -1) {
            while(window.readyHandlers.length > 0) {
              (window.readyHandlers.shift())();
            }
          }
        };
        
        document.onreadystatechange = window.handleState;
        

        用法:

        ready(function () {
          // your code here
        });
        

        它是为处理 JS 的异步加载而编写的,但您可能希望先同步加载此脚本,除非您正在缩小。我发现它在开发中很有用。

        现代浏览器还支持异步加载脚本,这进一步增强了体验。对异步的支持意味着可以同时下载多个脚本,同时仍然呈现页面。当依赖于异步加载的其他脚本或使用 minifier 或类似 browserify 来处理依赖项时,请注意。

        【讨论】:

          【解决方案5】:

          我想在这里提到一些可能的方法以及一个适用于所有浏览器的纯 javascript 技巧

          // with jQuery 
          $(document).ready(function(){ /* ... */ });
          
          // shorter jQuery version 
          $(function(){ /* ... */ });
          
          // without jQuery (doesn't work in older IEs)
          document.addEventListener('DOMContentLoaded', function(){ 
              // your code goes here
          }, false);
          
          // and here's the trick (works everywhere)
          function r(f){/in/.test(document.readyState)?setTimeout('r('+f+')',9):f()}
          // use like
          r(function(){
              alert('DOM Ready!');
          });
          

          这里的技巧,正如 original author 所解释的,是我们正在检查 document.readyState 属性。如果它包含字符串in(如uninitializedloading,5 个中的前两个DOM ready states),我们设置一个超时并再次检查。否则,我们执行传递的函数。

          这里是jsFiddle 用于适用于所有浏览器的技巧。

          感谢Tutorialzine 将其包含在他们的书中。

          【讨论】:

          • 非常糟糕的方法,使用具有任意 9 毫秒间隔的超时循环,并使用 eval。也只检查 /in/ 没有多大意义。
          【解决方案6】:

          HubSpot 的好人有一个资源,您可以在其中找到实现大量 jQuery 优点的纯 Javascript 方法 - 包括 ready

          http://youmightnotneedjquery.com/#ready

          function ready(fn) {
            if (document.readyState != 'loading'){
              fn();
            } else if (document.addEventListener) {
              document.addEventListener('DOMContentLoaded', fn);
            } else {
              document.attachEvent('onreadystatechange', function() {
                if (document.readyState != 'loading')
                  fn();
              });
            }
          }
          

          内联用法示例:

          ready(function() { alert('hello'); });
          

          【讨论】:

            【解决方案7】:

            如果您在没有 jQuery 的情况下使用 VANILLAJavaScript,那么您必须使用(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!");
            });
            

            也可以这样写SHORTHAND,即使occurs,jQuery也会在准备好之后运行。

            $(function() {
                console.log("ready!");
            });
            

            不要与 BELOW 混淆(这并不意味着准备好 DOM):

            不要使用像这样自执行的IIFE

             Example:
            
            (function() {
               // Your page initialization code here  - WRONG
               // The DOM will be available here   - WRONG
            })();
            

            此 IIFE 不会等待您的 DOM 加载。 (我什至说的是最新版本的 Chrome 浏览器!)

            【讨论】:

            • play() 失败,因为用户没有先与文档交互
            【解决方案8】:

            准备好了

            function ready(fn){var d=document;(d.readyState=='loading')?d.addEventListener('DOMContentLoaded',fn):fn();}
            

            使用喜欢

            ready(function(){
                //some code
            });
            

            用于自调用代码

            (function(fn){var d=document;(d.readyState=='loading')?d.addEventListener('DOMContentLoaded',fn):fn();})(function(){
            
                //Some Code here
                //DOM is avaliable
                //var h1s = document.querySelector("h1");
            
            });
            

            支持:IE9+

            【讨论】:

            【解决方案9】:

            我不太确定你在问什么,但也许这会有所帮助:

            window.onload = function(){
                // Code. . .
            
            }
            

            或者:

            window.onload = main;
            
            function main(){
                // Code. . .
            
            }
            

            【讨论】:

              【解决方案10】:

              这是Ram-swaroop's“适用于所有浏览器”的一个经过清理的非评估版本——适用于所有浏览器!

              function onReady(yourMethod) {
                var readyStateCheckInterval = setInterval(function() {
                  if (document && document.readyState === 'complete') { // Or 'interactive'
                    clearInterval(readyStateCheckInterval);
                    yourMethod();
                  }
                }, 10);
              }
              // use like
              onReady(function() { alert('hello'); } );
              

              不过,它确实需要额外等待 10 毫秒才能运行,所以这里有一个不应该的更复杂的方法:

              function onReady(yourMethod) {
                if (document.readyState === 'complete') { // Or also compare to 'interactive'
                  setTimeout(yourMethod, 1); // Schedule to run immediately
                }
                else {
                  readyStateCheckInterval = setInterval(function() {
                    if (document.readyState === 'complete') { // Or also compare to 'interactive'
                      clearInterval(readyStateCheckInterval);
                      yourMethod();
                    }
                  }, 10);
                }
              }
              
              // Use like
              onReady(function() { alert('hello'); } );
              
              // Or
              onReady(functionName);
              

              另请参阅How to check if DOM is ready without a framework?

              【讨论】: