没有使用jQuery的$(document).ready()等效方法是什么?

2412

我有一个使用了$(document).ready的脚本,但它没有使用jQuery的其他任何功能。 我想通过去除jQuery依赖来减轻它的负担。

我如何在不使用jQuery的情况下实现自己的$(document).ready功能? 我知道使用window.onload将不同,因为window.onload会在所有图像、框架等都加载完成后触发。


302
不是完全相同的功能。 - Joel Mueller
46
正如这个答案所述,如果你只需要jQuery中的 $(document).ready,那么你可以通过在页面底部而不是顶部运行代码来轻松解决这个问题。 HTML5Boilerplate正是采用了这种方法。 - Blazemonger
5
为什么不直接使用DOMContentLoaded事件?它可以在IE9及以上版本上使用。http://caniuse.com/domcontentloaded https://developer.mozilla.org/en-US/docs/Web/Events/DOMContentLoaded - Brock
值得一看:https://developer.mozilla.org/zh-CN/docs/Web/API/Document/readyState - Andrew
https://developer.mozilla.org/en-US/docs/Web/API/Document/DOMContentLoaded_event - Kithraya
我认为一个有趣的答案是解析jQuery是如何做到这一点的? - Chris Pink
39个回答

1833

有一个基于标准的替代方法,DOMContentLoaded,它被超过99%的浏览器支持,但不包括IE8:

document.addEventListener("DOMContentLoaded", function(event) { 
  //do work
});

jQuery的本机函数比仅仅是window.onload复杂得多,如下所示。

function bindReady(){
    if ( readyBound ) return;
    readyBound = true;

    // Mozilla, Opera and webkit nightlies currently support this event
    if ( document.addEventListener ) {
        // Use the handy event callback
        document.addEventListener( "DOMContentLoaded", function(){
            document.removeEventListener( "DOMContentLoaded", arguments.callee, false );
            jQuery.ready();
        }, false );

    // If IE event model is used
    } else if ( document.attachEvent ) {
        // ensure firing before onload,
        // maybe late but safe also for iframes
        document.attachEvent("onreadystatechange", function(){
            if ( document.readyState === "complete" ) {
                document.detachEvent( "onreadystatechange", arguments.callee );
                jQuery.ready();
            }
        });

        // If IE and not an iframe
        // continually check to see if the document is ready
        if ( document.documentElement.doScroll && window == window.top ) (function(){
            if ( jQuery.isReady ) return;

            try {
                // If IE is used, use the trick by Diego Perini
                // http://javascript.nwbox.com/IEContentLoaded/
                document.documentElement.doScroll("left");
            } catch( error ) {
                setTimeout( arguments.callee, 0 );
                return;
            }

            // and execute any waiting functions
            jQuery.ready();
        })();
    }

    // A fallback to window.onload, that will always work
    jQuery.event.add( window, "load", jQuery.ready );
}

37
如果脚本是后加载的,DOMContentLoaded 将不起作用。JQuery 的文档准备函数总是会被执行。 - Jared Insel
对我来说,DOMContentLoad在某些情况下触发得太早了,虽然它的工作效果很好,但更为复杂。 - maestroosram
我们可以写多个 document.addEventListener("DOMContentLoaded", function(event) 吗?并且期望它们都会被调用吗? - mercury

419
编辑:
2023年更新,使用这个:
function ready(fn) {
  if (document.readyState !== 'loading') {
    fn();
    return;
  }
  document.addEventListener('DOMContentLoaded', fn);
}

来自:https://youmightnotneedjquery.com/

作为一个一行代码的小改进,由Tanuki提供:

const ready = fn => document.readyState !== 'loading' ? fn() : document.addEventListener('DOMContentLoaded', fn);

这里有一个可行的替代方案,用于替代jQuery的ready函数。
function ready(callback){
    // in case the document is already rendered
    if (document.readyState!='loading') callback();
    // modern browsers
    else if (document.addEventListener) document.addEventListener('DOMContentLoaded', callback);
    // IE <= 8
    else document.attachEvent('onreadystatechange', function(){
        if (document.readyState=='complete') callback();
    });
}

ready(function(){
    // do something
});

从中取出 https://plainjs.com/javascript/events/running-code-when-the-document-is-ready-15/ 从中取出这里还有另一个好的domReady函数,取自https://dev59.com/PWkw5IYBdhLWcg3wbqAS#9899701
由于接受的答案远未完整,我根据jQuery 1.6.2源码拼凑了一个类似于jQuery.ready()的"ready"函数。
var ready = (function(){

    var readyList,
        DOMContentLoaded,
        class2type = {};
        class2type["[object Boolean]"] = "boolean";
        class2type["[object Number]"] = "number";
        class2type["[object String]"] = "string";
        class2type["[object Function]"] = "function";
        class2type["[object Array]"] = "array";
        class2type["[object Date]"] = "date";
        class2type["[object RegExp]"] = "regexp";
        class2type["[object Object]"] = "object";

    var ReadyObj = {
        // Is the DOM ready to be used? Set to true once it occurs.
        isReady: false,
        // A counter to track how many items to wait for before
        // the ready event fires. See #6781
        readyWait: 1,
        // Hold (or release) the ready event
        holdReady: function( hold ) {
            if ( hold ) {
                ReadyObj.readyWait++;
            } else {
                ReadyObj.ready( true );
            }
        },
        // Handle when the DOM is ready
        ready: function( wait ) {
            // Either a released hold or an DOMready/load event and not yet ready
            if ( (wait === true && !--ReadyObj.readyWait) || (wait !== true && !ReadyObj.isReady) ) {
                // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
                if ( !document.body ) {
                    return setTimeout( ReadyObj.ready, 1 );
                }

                // Remember that the DOM is ready
                ReadyObj.isReady = true;
                // If a normal DOM Ready event fired, decrement, and wait if need be
                if ( wait !== true && --ReadyObj.readyWait > 0 ) {
                    return;
                }
                // If there are functions bound, to execute
                readyList.resolveWith( document, [ ReadyObj ] );

                // Trigger any bound ready events
                //if ( ReadyObj.fn.trigger ) {
                //    ReadyObj( document ).trigger( "ready" ).unbind( "ready" );
                //}
            }
        },
        bindReady: function() {
            if ( readyList ) {
                return;
            }
            readyList = ReadyObj._Deferred();

            // Catch cases where $(document).ready() is called after the
            // browser event has already occurred.
            if ( document.readyState === "complete" ) {
                // Handle it asynchronously to allow scripts the opportunity to delay ready
                return setTimeout( ReadyObj.ready, 1 );
            }

            // Mozilla, Opera and webkit nightlies currently support this event
            if ( document.addEventListener ) {
                // Use the handy event callback
                document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );
                // A fallback to window.onload, that will always work
                window.addEventListener( "load", ReadyObj.ready, false );

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

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

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

                try {
                    toplevel = window.frameElement == null;
                } catch(e) {}

                if ( document.documentElement.doScroll && toplevel ) {
                    doScrollCheck();
                }
            }
        },
        _Deferred: function() {
            var // callbacks list
                callbacks = [],
                // stored [ context , args ]
                fired,
                // to avoid firing when already doing so
                firing,
                // flag to know if the deferred has been cancelled
                cancelled,
                // the deferred itself
                deferred  = {

                    // done( f1, f2, ...)
                    done: function() {
                        if ( !cancelled ) {
                            var args = arguments,
                                i,
                                length,
                                elem,
                                type,
                                _fired;
                            if ( fired ) {
                                _fired = fired;
                                fired = 0;
                            }
                            for ( i = 0, length = args.length; i < length; i++ ) {
                                elem = args[ i ];
                                type = ReadyObj.type( elem );
                                if ( type === "array" ) {
                                    deferred.done.apply( deferred, elem );
                                } else if ( type === "function" ) {
                                    callbacks.push( elem );
                                }
                            }
                            if ( _fired ) {
                                deferred.resolveWith( _fired[ 0 ], _fired[ 1 ] );
                            }
                        }
                        return this;
                    },

                    // resolve with given context and args
                    resolveWith: function( context, args ) {
                        if ( !cancelled && !fired && !firing ) {
                            // make sure args are available (#8421)
                            args = args || [];
                            firing = 1;
                            try {
                                while( callbacks[ 0 ] ) {
                                    callbacks.shift().apply( context, args );//shifts a callback, and applies it to document
                                }
                            }
                            finally {
                                fired = [ context, args ];
                                firing = 0;
                            }
                        }
                        return this;
                    },

                    // resolve with this as context and given arguments
                    resolve: function() {
                        deferred.resolveWith( this, arguments );
                        return this;
                    },

                    // Has this deferred been resolved?
                    isResolved: function() {
                        return !!( firing || fired );
                    },

                    // Cancel
                    cancel: function() {
                        cancelled = 1;
                        callbacks = [];
                        return this;
                    }
                };

            return deferred;
        },
        type: function( obj ) {
            return obj == null ?
                String( obj ) :
                class2type[ Object.prototype.toString.call(obj) ] || "object";
        }
    }
    // The DOM ready check for Internet Explorer
    function doScrollCheck() {
        if ( ReadyObj.isReady ) {
            return;
        }

        try {
            // If IE is used, use the trick by Diego Perini
            // http://javascript.nwbox.com/IEContentLoaded/
            document.documentElement.doScroll("left");
        } catch(e) {
            setTimeout( doScrollCheck, 1 );
            return;
        }

        // and execute any waiting functions
        ReadyObj.ready();
    }
    // Cleanup functions for the document ready method
    if ( document.addEventListener ) {
        DOMContentLoaded = function() {
            document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false );
            ReadyObj.ready();
        };

    } else if ( document.attachEvent ) {
        DOMContentLoaded = function() {
            // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
            if ( document.readyState === "complete" ) {
                document.detachEvent( "onreadystatechange", DOMContentLoaded );
                ReadyObj.ready();
            }
        };
    }
    function ready( fn ) {
        // Attach the listeners
        ReadyObj.bindReady();

        var type = ReadyObj.type( fn );

        // Add the callback
        readyList.done( fn );//readyList is result of _Deferred()
    }
    return ready;
})();

如何使用:
<script>
    ready(function(){
        alert('It works!');
    });
    ready(function(){
        alert('Also works!');
    });
</script>

我不确定这段代码的功能如何,但在我进行的表面测试中它运行良好。这花了相当长的时间,所以我希望你和其他人都能从中受益。
附注:我建议编译它。
或者你可以使用http://dustindiaz.com/smallest-domready-ever
function r(f){/in/.test(document.readyState)?setTimeout(r,9,f):f()}
r(function(){/*code to run*/});

如果您只需要支持新的浏览器,可以使用原生函数(与jQuery的ready不同,如果您在页面加载后添加此函数,它将不会运行)。
document.addEventListener('DOMContentLoaded',function(){/*fun code to run*/})

18
备选方案:Zepto.js(9.1 kb),Snack.js(8.1 kb),$dom(2.3 kb)和140 Medley(0.5 kb)。*编辑:*您还可以查看Ender。 - Frederik Krautwald
为什么在成功触发函数后我们不需要移除事件监听器呢? - Sloan Reynolds
1
更短的一行代码(“2023更新”版本):function ready(fn){(document.readyState!=='loading')?fn():document.addEventListener('DOMContentLoaded',fn)}当所有资源加载完成时的代码:function load(fn){(document.readyState==='complete')?fn():document.addEventListener('load',fn)} - undefined

227

三种选择:

  1. 如果script是body中的最后一个标签,则在script标签执行前DOM将准备就绪。
  2. 当DOM准备就绪时,“readyState”会更改为“complete”。
  3. 将所有内容放在“DOMContentLoaded”事件监听器下。

onreadystatechange

  document.onreadystatechange = function () {
     if (document.readyState == "complete") {
     // document is ready. Do your stuff here
   }
 }

来源:MDN

DOMContentLoaded

document.addEventListener('DOMContentLoaded', function() {
   console.log('document is ready. I can sleep now');
});

担心古老的浏览器: 前往jQuery源代码并使用ready函数。在这种情况下,您不会解析+执行整个库,而是只做其中非常小的一部分。


9
仅供参考,#1 不完全准确。页面末尾的脚本很可能在 DOM 加载完成之前加载。这就是为什么监听器更好。它们会等待浏览器完成操作。如果将其放在末尾,则是希望脚本加载比浏览器呈现速度慢。 - Machavity
HTML解析在脚本加载/运行时会暂停,因此在文档末尾使用脚本是安全的。它将比DOMContentLoaded稍早运行,但有时这是期望的效果。 - 12Me21

100

将您的<script>/* JavaScript代码 */</script>置于紧接着</body>标签之前。

诚然,这种方法可能并不适合每个人的需求,因为它需要更改HTML文件,而不仅仅是在JavaScript文件中使用document.ready,但还是值得尝试的...


70

穷人的解决方案:

var checkLoad = function() {   
    document.readyState !== "complete" ? setTimeout(checkLoad, 11) : alert("loaded!");   
};  

checkLoad();  

查看演示

添加了这一个,更好一些,具有自己的作用域,非递归。

(function(){
    var tId = setInterval(function() {
        if (document.readyState == "complete") onComplete()
    }, 11);
    function onComplete(){
        clearInterval(tId);    
        alert("loaded!");    
    };
})()

查看示例


41

我使用这个:

document.addEventListener("DOMContentLoaded", function(event) { 
    //Do work
});

注意:这可能只适用于更新的浏览器,尤其是这些浏览器:http://caniuse.com/#feat=domcontentloaded


34

27

2022版本

在2022年,您只需在脚本上添加defer属性,并在head中加载即可!

参考:https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-defer

<!doctype html>
<html>
<head>
  <script src="/script.js" defer></script>
</head>
<body>

 <p>In 2022, all you need to do is put the defer attribute on your script, and load it in the head!</p>

</body>
</html>

请问您能否提供一个相关的参考资料? - Chamara Indrajith
@ChamaraIndrajith 当然可以 - 添加了到MDN的链接 - Greg
到目前为止最好的答案,但请记住它会让你与页面并行下载脚本(对大多数人来说无关紧要)。 - DexieTheSheep

22

这个问题很久以前就被提出了。对于刚看到这个问题的人,现在有一个名为"you might not need jquery"的网站,它按照IE支持的级别分解了jQuery的所有功能,并提供了一些替代的、更小的库。

根据you might not need jquery,IE8文档就绪脚本。

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();
        });
}

我想知道为什么需要使用'onreadystatechange'而不是document.attachEvent('onload', fn); - Luke

22

实际上,如果你只关心Internet Explorer 9+,那么这段代码足以替换jQuery.ready

    document.addEventListener("DOMContentLoaded", callback);

如果你担心Internet Explorer 6和一些非常奇怪且罕见的浏览器,这将起作用:


domReady: function (callback) {
    // Mozilla, Opera and WebKit
    if (document.addEventListener) {
        document.addEventListener("DOMContentLoaded", callback, false);
        // If Internet Explorer, the event model is used
    } else if (document.attachEvent) {
        document.attachEvent("onreadystatechange", function() {
            if (document.readyState === "complete" ) {
                callback();
            }
        });
        // A fallback to window.onload, that will always work
    } else {
        var oldOnload = window.onload;
        window.onload = function () {
            oldOnload && oldOnload();
            callback();
        }
    }
},

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接