从零开始写类库(1)——加载脚本的研究

在笔者看来,进阶javascript的最好方式是从编写类库开始。当然,很多人认为是通过做大型项目来进阶技术,我也不否认这一点。但要注意的是,在你有资格接触到足够有深度的大项目之前,如何提高自己的水平呢?所以,我认为编写类库就是一个提高自己的好方法,模拟著名类库的实现方式,将自己的实现与之相比较,找出不足,从根本上弄懂javascript这门语言。
javascript的脚本都是要通过网络传输,浏览器接收完成后进行解析,那么弄清楚脚本何时加载完毕,何时可以运行尤为重要,我们就从一个简单的外部脚本加载的例子开始学习。

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
    <!-- 外部代码放在头部加载 -->
    <script src="js/loader.js"></script>
</head>
<body>

</body>
</html>
//输出执行该脚本时,文档对象的状态
console.log(document.readyState);

运行代码,console打出了loading,说明在loader执行时,文档树仍然在构建中。
此时,有些人会认为把脚本放到body块中的末尾就可以了,可以自己尝试一下,仍然是loading,这是只会用jQuery做外包的新手常出现的问题。

<!-- 为其加上defer选项 -->
<script src="js/loader.js" defer></script>

为script标签增加选项defer,此选项表明该脚本可以延迟到文档完全解析和显示之后执行。再次运行,发现此时console输出了interactive,此时文档树已经构建完成,并且可以与用户交互。
我们需要的正是浏览器的这样一个状态,此时我们才可以进行为DOM节点增加事件,操作DOM等各种操作,那么问题来了,如何在这个时机运行我们需要的代码呢?
机智一些的童鞋一定会马上从上面的例子想到,我可以通过一个包含defer选项的外部脚本得到这个状态啊,先将需要调用的代码作为一个全局函数或一个全局变量中的函数,然后再利用defer外部脚本执行该代码,实现如下:

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
<script src="js/loader.js" defer></script>
<script src="js/a.js"></script>
<script src="js/b.js"></script>
<script>
    window.onReady = function () {
        a();
        b();
    };
</script>
</body>
</html>
//a文件内容
function a() {
    console.log("a is run");
}
//b文件内容
function b() {
    console.log("b is run");
}
//loader文件内容
console.log(document.readyState);
if (typeof window.onReady == "function") window.onReady();

执行代码后输出如下:
interactive
a is run
b is run

用defer的方式的确可以实现该需求,但是缺陷也是显而易见的:
1. 必须多引入一个无谓的外部文件,拖慢了加载速度
2. 如果页面中包含iframe,那么defer会在iframe的内容都加载完后才执行,延后的执行时间。
该方法并不可取,不过也可以解决IE的旧版本中没有文档加载完成事件的问题。

当然,对于IE9及以上和webkit内核的浏览器来说,采用标准的DOMContentLoaded是最好的方案了,实现如下:

(function () {
    var callBackList = [];
    window.domReady = function (func) {
        if (typeof func == "function") {
            callBackList.push(func);
        }
    };
    document.addEventListener("DOMContentLoaded", function () {
        for (var i in callBackList)
            callBackList[i]();
    });
})(window, document);

这一段代码非常简单,首先将需要在DOM树构造完毕后执行的函数依次放入数组中,再监听document的DOMContentLoaded事件,当文档构建完毕后,依次执行数组中的函数。
这段函数有两个明显的缺陷:
1. 不支持IE8及以下的浏览器,因为他们不支持DOMContentLoaded事件。
2. 如果代码在DOMContentLoaded事件发生后才执行肿么办。
3. 在回调函数执行后,事件监听器并没有取消,降低性能。

修改后的代码如下:

(function () {
    var callBackList = [],
        isReady = false;

    //判断当前文档状态
    if (document.readyState === "complete") {
        isReady = true;
    }
    else {
        if (document.addEventListener) {
            document.addEventListener("DOMContentLoaded", complete, false);
            window.addEventListener("load", complete, false);
        }
        else {
            document.attachEvent("onreadystatechange", complete);
            window.attachEvent("onload", complete);
        }

    }

    window.domReady = function (func) {
        if (typeof func == "function") {
            if (isReady)
                func();
            else
                callBackList.push(func);
        }
    };

    function complete() {
        isReady = true;
        if (document.addEventListener) {
            document.removeEventListener("DOMContentLoaded", complete, false);
            window.removeEventListener("load", complete, false);
        }
        else {
            document.detachEvent("onreadystatechange", complete);
            window.detachEvent("onload", complete);
        }
        for (var i in callBackList)
            callBackList[i]();
    }
})(window, document);

以上代码在进入时先处理了文档已经加载完成的情况,其次在完成回调后取消了事件监听器,在对待低版本IE的方案上,采取了同时监听onreadystatechange事件的方法,但上文也强调过该方法并不完美,因为IE8及以下版本占有率已经很低,我们不要在此处耗费太多时间,好用即可。如果想用更好的方法,可以使用doScroll方法进行检测。同时,监听window对象的load事件,确保代码在DOMContentLoaded事件后还能执行。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值