首页 » JavaScript » readystatechange

readystatechange

IE为DOM文档中的某些部分提供了readystatechange事件。这个事件的目的是提供与文档或元素的加载状态有关的信息,但这个事件的行为有时候也很难预料。支持readystatechange事件的每个对象都有一个readyStage属性,可能包含下列5个值中的一个。

  • uninitialized(为初始化):对象正在加载数据;
  • loading(正在加载):对象正在加载数据;
  • loaded(加载完毕):对象加载数据我能撑;
  • interactive(交互):可以操作对象了,但还没有完全加载;
  • complete(完成):对象已经加载完毕。

这些状态看起来很直观,但并非所有对象都会经理readyState的这几个阶段。换句话说,如果某个阶段不适合某个对象,则该对象完全可能跳过该阶段;并没有规定哪个阶段适用于哪个对象。显然,这意味着readystatechange事件经常会少于4次,而readyState属性的值也不总是连续的。

对于document而言,值为“interactive”的readyStage也会在与DOMContentLoaded大致相同的时刻触发readystatechange事件。此时,DOM树已经加载完毕,可以安全地操作它了,因此就会进入交互(interactive)阶段。但与此同时,图像及其它外部文件不一定可用。下面来看一段处理readystatechange事件的代码:

var EventUtil = {
    addHandler: function (element, type, handler) {
        if (element.addEventListener) {
            element.addEventListener(type, handler, false);
        } else if (element.attachEvent) {
            element.attachEvent("on" + type, handler);
        } else {
            element["on" + type] = handler;
        }
    }
};
EventUtil.addHandler(document, "readystatechange", function () {
    if (document.readyState == "interactive") {
        alert("Content loaded");
    }
});

这个事件的event对象不会提供任何信息,也没有目标对象。

在与load事件一起使用时,无法预测两个事件触发的先后顺序。在包含较多或较大的外部资源的页面中,会在load事件触发之前先进入交互阶段;而在包含较少或较小的外部页面资源中,则很难说readystatechange事件会发生在load事件前面。

让问题变得复杂的是,交互阶段可能会遭遇也可能会晚于完成阶段出现,无法确保顺序。在包含较多外部资源的页面中,交互阶段更有可能遭遇完成阶段出现;而在页面中包含较少外部资源的页面中,交互阶段更有可能早于完成阶段出现;而在页面中包含较少外部资源的情况下,完成阶段先于交互阶段出现的可能性更大。因此,为了尽可能抢到先机,有必要同时检测交互很完成阶段,如下面的例子所示:

EventUtil.addHandler(document, "readystatechange", function (event) {
    if (document.readyState == "interactive" || document.readyState == "complete");
    alert("Contet loaded");
})

对于上面的代码来说,当readystatechange事件触发时,会检测document。这样编写代码可以达到与使用DOMContentLoaded十分相似的效果。

虽然使用readystatechange可以十分近地模拟DOMContentLoaded事件,但它们本质上还是不同的。在不同的页面中,load事件与readystatechange事件并不能保证以相同的顺序触发。Opera还支持这个事件的一个精简版,但由于其行为与IE中事件的不一致性,我们建议不要在Opera中使用该事件。

另外,<script>和<link>元素也会触发readystatechange事件,可以用来确定外部的JavaScript和CSS文件是否已经加载完成。与其它浏览器中一样,除非把动态创建的元素添加到页面中,否则浏览器不会开始下载资源。基于元素触发的readystatechange事件也存在同样的问题,即readyState属性无论等于“loaded”还是“complete”都可以表示资源已经可用。有时候,readyState会停在“loaded”阶段而永远不会“完成”;有时候,又会跳过“loaded”阶段而直接“完成”。于是还需要像对待document一样采用相同的编码方式。例如,下面展示了一段加载外部JavaScript文件的代码:

var EventUtil = {
    addHandler: function (element, type, handler) {
        if (element.addEventListener) {
            element.addEventListener(type, handler, false);
        } else if (element.attachEvent) {
            element.attachEvent("on" + type, handler);
        } else {
            element["on" + type] = handler;
        }
    },
    removeHandler: function (element, type, handler) {
        if (element.removeEventListener) {
            element.removeEventListener(type, handler, false);
        } else if (element.detachEvent) {
            element.detachEvent("on" + type, handler);
        } else {
            element["on" + type] = null;
        }
    },
    getEvent: function (event) {
        return event ? event : window.event;
    },
    getTarget: function (event) {
        return event.target || event.srcElement;
    }
};
EventUtil.addHandler(window, "load", function () {
    //创建一个新的<script>元素
    var script = document.createElement("script");
    EventUtil.addHandler(script, "readystatechange", function (event) {
        event = EventUtil.getEvent(event);
        var target = EventUtil.getTarget(event);
        if (target.readyState == "loaded" || target.readyState == "complete") {
            EventUtil.removeHandler(target, "readystatechange", arguments.callee);
            alert("Script Loaded");
        }
    });
    script.src = "example.js";
    document.body.appendChild(script);
});

这个例子为新创建的<script>节点指定了一个事件处理程序。事件的目标是该节点本身,因此当触发reaystatechange事件时,要检测目标的readyState属性是不是等于“loaded”或“complete”。如果进入了其中任何一个阶段,则移除事件处理程序(以防止被执行了两次),并显示一个警告框。与此同时,就可以执行已经加载完毕的外部文件的函数了。

同样的编码方式也使用于通过<link>元素加载CSS文件的情况,如下面的例子所示:

var EventUtil = {
    addHandler: function (element, type, handler) {
        if (element.addEventListener) {
            element.addEventListener(type, handler, false);
        } else if (element.attachEvent) {
            element.attachEvent("on" + type, handler);
        } else {
            element["on" + type] = handler;
        }
    },
    removeHandler: function (element, type, handler) {
        if (element.removeEventListener) {
            element.removeEventListener(type, handler, false);
        } else if (element.detachEvent) {
            element.detachEvent("on" + type, handler);
        } else {
            element["on" + type] = null;
        }
    },
    getEvent: function (event) {
        return event ? event : window.event;
    },
    getTarget: function (event) {
        return event.target || event.srcElement;
    }
};
EventUtil.addHandler(window, "load", function () {
    //创建一个新的<link>元素
    var link = document.createElement("link");
    link.type = "text/css";
    link.rel = "stylesheet";

    EventUtil.addHandler(link, "readystatechange", function (event) {
        event = EventUtil.getEvent(event);
        var target = EventUtil.getTarget(event);
        if (target.readyState == "loaded" || target.readyState == "complete") {
            EventUtil.removeHandler(target, "readystatechange", arguments.callee);
            alert("CSS Loaded");
        }
    });

    link.href = "example.css";
    document.getElementsByTagName("head")[0].appendChild(link);
});

同样,最重要的是要一并检测readyState的两个状态,并在调用了一次事件处理程序后就将其移除。

Opera也支持<script>元素上的readysatechange事件,但不支持<link>元素上的readystatechange事件。

此文章发表在 JavaScript. 将 固定链接 加入收藏.