Javascript 装载和执行

Javascript 装载和执行

一两个月前在淘宝内网里看到一个优化Javascript代码的竞赛,发现有不少的人对Javascript的执行和装载的基础并不懂,所以,从那天起我就想写一篇文章,但一直耽搁了。自上篇《浏览器渲染原理简介》,正好也可以承前启后。

首先,我想说一下Javascript的装载和执行。通常来说,浏览器对于Javascript的运行有两大特性:1)载入后马上执行,2)执行时会阻塞页面后续的内容(包括页面的渲染、其它资源的下载)。于是,如果有多个js文件被引入,那么对于浏览器来说,这些js文件被被串行地载入,并依次执行。

因为javascript可能会来操作HTML文档的DOM树,所以,浏览器一般都不会像并行下载css文件并行下载js文件,因为这是js文件的特殊性造成的。所以,如果你的javascript想操作后面的DOM元素,基本上来说,浏览器都会报错说对象找不到。因为Javascript执行时,后面的HTML被阻塞住了,DOM树时还没有后面的DOM结点。所以程序也就报错了。

传统的方式

所以,当你写在代码中写下如下的代码:

<script type="text/javascript"
        src="https://coolshell.cn/asyncjs/alert.js"></script>

基本上来说,head里的 <script>标签会阻塞后续资源的载入以及整个页面的生成。我专门做了一个示例你可以看看:示例一。 注意:我的alert.js中只有一句话:alert(“hello world”) ,这更容易让你看到javascript是怎么阻塞后面的东西的。

所以,你知道为什么有很多网站把javascript放在网页的最后面了,要么就是动用了window.onload或是docmuemt ready之类的事件。

另外,因为绝大多数的Javascript代码并不需要等页面,所以,我们异步载入的功能。那么我们怎么异步载入呢?

document.write方式

于是,你可能以为document.write()这种方式能够解决不阻塞的方式。你当然会觉得,document.write了的<script>标签后就可以执行后面的东西去了,这没错。对于在同一个script标签里的Javascript的代码来说,是这样的,但是对于整个页面来说,这个还是会阻塞。 下面是一段测试代码:

<script type="text/javascript" language="javascript">
    function loadjs(script_filename) {
        document.write('<' + 'script language="javascript" type="text/javascript"');
        document.write(' src="' + script_filename + '">');
        document.write('<'+'/script'+'>');
        alert("loadjs() exit...");
    }

    var script = 'https://coolshell.cn/asyncjs/alert.js';

    loadjs(script);
    alert("loadjs() finished!");
</script>

<script type="text/javascript" language="javascript">
   alert("another block");
</script>

你觉得alert的顺序是什么?你可以在不同的浏览器里试一试。这里的想关的测试页面:示例二

script的defer和async属性

IE自从IE6就支持defer标签,如:

<script defer type="text/javascript" src="./alert.js" >
</script>

对于IE来说,这个标签会让IE并行下载js文件,并且把其执行hold到了整个DOM装载完毕(DOMContentLoaded),多个defer的<script>在执行时也会按照其出现的顺序来运行。最重要的是<script>被加上defer后,其不会阻塞后续DOM的的渲染。但是因为这个defer只是IE专用,所以一般用得比较少。

而我们标准的的HTML5也加入了一个异步载入javascript的属性:async,无论你对它赋什么样的值,只要它出现,它就开始异步加载js文件。但是, async的异步加载会有一个比较严重的问题,那就是它忠实地践行着“载入后马上执行”这条军规,所以,虽然它并不阻塞页面的渲染,但是你也无法控制他执行的次序和时机。你可以看看这个示例去感受一下

支持 async标签的浏览器是:Firefox3.6+,Chrome 8.0+,Safari 5.0+,IE 10+,Opera还不支持(来自这里)所以这个方法也不是太好。因为并不是所有的浏览器你都能行。

动态创建DOM方式

这种方式可能是用得最多的了。

function loadjs(script_filename) {
    var script = document.createElement('script');
    script.setAttribute('type', 'text/javascript');
    script.setAttribute('src', script_filename);
    script.setAttribute('id', 'coolshell_script_id');

    script_id = document.getElementById('coolshell_script_id');
    if(script_id){
        document.getElementsByTagName('head')[0].removeChild(script_id);
    }
    document.getElementsByTagName('head')[0].appendChild(script);
}

var script = 'https://coolshell.cn/asyncjs/alert.js';
loadjs(script);

这个方式几乎成了标准的异步载入js文件的方式,这个方式的演示请参看:示例三。这方式还被玩出了JSONP的东东,也就是我可以为script的src指定某个后台的脚本(如PHP),而这个PHP返回一个javascript函数,其参数是一个json的字符串,返回来调用我们的预先定义好的javascript的函数。你可以看一下这个示例:t.js (这个示例是我之前在微博征集的一个异步ajax调用的小例子

按需异步载入js

上面那个DOM方式的例子解决了异步载入Javascript的问题,但是没有解决我们想让他按我们指定的时机运行的问题。所以,我们只需要把上面那个DOM方式绑到某个事件上来就可以了。

比如:

绑在window.load事件上——示例四 

你一定要比较一下示例四和示例三在执行上有什么不同,我在这两个示例中都专门用了个代码高亮的javascript,看看那个代码高亮的的脚本的执行和我的alert.js的执行的情况,你就知道不同了)

window.load = loadjs("https://coolshell.cn/asyncjs/alert.js")

绑在特定的事件上——示例五

<p style="cursor: pointer" onclick="LoadJS()">Click to load alert.js </p>

这个示例很简单了。当你点击某个DOM元素,才会真正载入我们的alert.js。

更多

但是,绑定在某个特定事件上这个事似乎又过了一点,因为只有在点击的时候才会去真正的下载js,这又会太慢了了。好了,到这里,要抛出我们的终极问题——我们想要异步地把js文件下载到用户的本地,但是不执行,仅当在我们想要执行的时候去执行

要是我们有下面这样的方式就好了:

var script = document.createElement("script");
script.noexecute = true;
script.src = "alert.js";
document.body.appendChild(script);

//后面我们可以这么干
script.execute();

可惜的是,这只是一个美丽的梦想,今天我们的Javascript还比较原始,这个“JS梦”还没有实现呢。

所以,我们的程序员只能使用hack的方式来搞。

有的程序员使用了非标准的script的type来cache javascript。如:

<script type=cache/script src="./alert.js"></script>

因为”cache/script”,这个东西根本就不能被浏览器解析,所以浏览器也就不能把alert.js当javascript去执行,但是他又要去下载js文件,所以就可以搞定了。可惜的是,webkit严格符从了HTML的标准——对于这种不认识的东西,直接删除,什么也不干。于是,我们的梦又破了。

所以,我们需要再hack一下,就像N多年前玩preload图片那样,我们可以动用object标签(也可以动用iframe标签),于是我们有下面这样的代码:

    function cachejs(script_filename){
        var cache = document.createElement('object');
        cache.data = script_filename;
        cache.id = "coolshell_script_cache_id";
        cache.width = 0;
        cache.height = 0;
        document.body.appendChild(cache);
    }

然后,我们在的最后调用一下这个函数。请参看一下相关的示例:示例六

在Chrome下按 Ctrl+Shit+I,切换到network页,你就可以看到下载了alert.js但是没有执行,然后我们再用示例五的方式,因为浏览器端有缓存了,不会再从服务器上下载alert.js了。所以,就能保证执行速度了。

关于这种preload这种东西你应该不会陌生了。你还可以使用Ajax的方式,如:

var xhr = new XMLHttpRequest();
xhr.open('GET', 'new.js');
xhr.send('');

到这里我就不再多说了,也不给示例了,大家可以自己试试去。

最后再提两个js,一个是ControlJS,一个叫HeadJS,专门用来做异步load javascript文件的。

好了,这是所有的内容了,希望大家看过后能对Javascript的载入和执行,以及相关的技术有个了解。同时,也希望各前端高手不吝赐教!

(全文完)


关注CoolShell微信公众账号和微信小程序

(转载本站文章请注明作者和出处 酷 壳 – CoolShell ,请勿用于任何商业用途)

——=== 访问 酷壳404页面 寻找遗失儿童。 ===——
好烂啊有点差凑合看看还不错很精彩 (62 人打了分,平均分: 4.35 )
Loading...

Javascript 装载和执行》的相关评论

  1. 谢谢分享,文章写得细致深入,很受教!
    想问一下,实例6里面,为什么要用object元素而不是用其他的div,span之类的元素呢?
    谢谢!

  2. 示例六的html页面是不是有点问题,出现了两次cachejs调用;
    loadjs没有重新请求js文件是不是浏览器从缓存中获取的js内容?如果加载的js的url是类似aa.js?v=20120202是不是还会重新请求还是走缓存?上班了,等有时间我测试一下

  3. 在测试第二个例子的时候,chrome与firefox中,行为不一样.
    在chrome(27.0.1453.94 m)中:
    1.loadjs() exit …
    2.loadjs() finished!
    3. hello world
    4. another block

    在firefox(v21.0)中
    1.hello world
    2. loadjs() exit…
    3. loadjs() finished!
    4. another block

  4. 这篇内容简单易懂,我这个前端盲都获益不少。不过这次你文章中错别字太多了,休息不够是吗?

  5. 有一种常见的需求是,js并发下载,但是顺序执行:http://www.raychase.net/123

  6. 其实预加载最后一种ajax最顺畅,前面hack方式都太绕了。反正代码来实现动态加载,不用纠结一定要用htmlTag,所谓的script.execute,一直有,ie下可以execScript方法,也支持通用方法eval。一定要用script element ,就把ajax的responseText设置到script.text再append也可。

  7. mylovemywife :在测试第二个例子的时候,chrome与firefox中,行为不一样.在chrome(27.0.1453.94 m)中:1.loadjs() exit …2.loadjs() finished!3. hello world4. another block
    在firefox(v21.0)中1.hello world2. loadjs() exit…3. loadjs() finished!4. another block

    确实是这个样子,测试了一下

  8. 关注一段时间了,做嵌入式的。发现LZ功底涉及面很广,像我们做silicon级的完全不懂上层,OS底层涉及较多,相对server简单吧,也从LZ的blog学到很多。想问与本文无关两个问题:(1)职业IT技术就这样一直忙下去吗?没时间陪家人吗?我是想两边都兼顾的。(2)有什么小技巧能share给我们这些后生的 少走走弯路 就像你一样,成为内外兼修之才。

  9. 小白,提个 t.js 的问题,事先知道总数,为什么不先初始好30个div,返回结果直接填进去不就保持顺序了?

  10. 又一次看到因为js语法、实现的限制,开发者不得不做的反复折腾。。。。。
    这么多互联网巨头、浏览器厂商,就不能把js改进得自然方便一点吗?

  11. 我们项目一直使用LABjs, 这是一个特别成熟的小项目,特别适合有复杂依赖的js文件异步导入的项目

  12. @由 LABjs是个不错的库,我自己在项目里也用过,不过这个项目主页上说不会再有进一步的开发了。

  13. 异步是解决在不阻塞当前”主要”处理任务的情况下,执行其他处理任务的需求的方法.

  14. @darklonely
    本文中说到的消除阻塞,个人认为是:用来提高用户使用体验,尽快完成页面生成任务,尽早地给用户一种”可以开始进行交互”的感觉的需求.而完成页面生成任务中关键的一部是,尽快的分析处理完html文档.
    之所以说它是”需求”,而不是”任务”,是因为在XML中所有标签应该是平等的,不同标签代表的意义,所需要的处理操作可能不同.但是归于一点,它们都是”标签”.流文档的特性是,一个标签分析处理完成后,再处理另一个标签,直至所有标签处理完毕.
    浏览器是根据”HTML DOM”这种数据结构来进行页面的生成操作.不同于其他标签,标签因为它的”底层性”,提供了直接与该数据结构交互的能力.由于这种其他标签所不具备的操作上的”灵活性”与功能上的”强大性”,人们往往”过度”的使用标签:用它进行大量的操作,并且因为”载入后马上执行”这条军规,”很容易不自觉地”造成阻塞整个页面生成流程的后果,最终影响用户体验.
    所以单纯的获取数据这个角度上考量,去除数据大小,网络状况等因素后,单个//的性能应该是一样的.的异步需求,主要是由于它的”执行”特性,影响生成页面速度造成的.
    不知这么理解是否正确.望指正.

  15. 我觉得现有的工具已经很成熟, 比如说就我来说执行时机有jquery的ready控制就挺够用了. 有了妥善的方案之后再去研究其它各种方案的具体细节, 不是说没有价值, 但这恐怕对于一般js使用者来说使用价值是完全没有了. 再比如文中纠结很多的那个被加载者的执行时机问题, 就不能从被调用者身上着手么, 里面只写函数定义不写裸的语句, 调用者不就能完全控制执行时机了?

  16. @Mr.Wiredan 刚刚去查了一下,没有看见说不更新了啊!labjs还是特别稳定了,尤其是在国内各种低版本浏览器盛行的情况下。

  17. 皓哥的文章写很好,但我有几点建议,第一,示例代码中script的language属性是没有必要的可以去掉。
    第二,好像不仅仅只有IE才支持defer属性,我们可以通过一下网站测试我们的浏览器是否支持defer属性,
    第三,随便一下RequestJS也是支持异步加载,谢谢。

    http://browserspy.dk/javascript.php

  18. litsand :

    mylovemywife :在测试第二个例子的时候,chrome与firefox中,行为不一样.在chrome(27.0.1453.94 m)中:1.loadjs() exit …2.loadjs() finished!3. hello world4. another block
    在firefox(v21.0)中1.hello world2. loadjs() exit…3. loadjs() finished!4. another block

    确实是这个样子,测试了一下

    测试了一和#5和#9结果相同,另IE和Chrome结果相同,独独Firefox让人吃惊。

  19. se a n220 :其实预加载最后一种ajax最顺畅,前面hack方式都太绕了。反正代码来实现动态加载,不用纠结一定要用htmlTag,所谓的script.execute,一直有,ie下可以execScript方法,也支持通用方法eval。一定要用script element ,就把ajax的responseText设置到script.text再append也可。

    +1

  20. 其实labjs的文件加载的几种实现就是博主说的这些,如果只是管理js文件加载的问题建议采用labjs,但是需要模块管理的话建议requirejs或淘宝的seajs

  21. 示例二 loadjs(script); 的时候为什么dom里write()生成的js代码是在第一段script结束后的?按照js立即执行的方法不应该在执行alert(“loadjs() finished!”);之前write(),最后成为一个这样的怪东西吗

  22. 这样的
    <script type=”text/javascript” language=”javascript”>
    function loadjs(script_filename) {
    document.write(”);
    document.write(”);
    alert(“loadjs() exit…”);
    }

    var script = ‘http://coolshell.cn/asyncjs/alert.js’;

    loadjs(script);
    <script type=”text/javascript” language=”javascript” src=”http://coolshell.cn/asyncjs/alert.js”></script>
    alert(“loadjs() finished!”);
    </script>

  23. 简单写了并下下载的脚本,我觉得还是蛮方便的。。。

    当所有的脚本并下下载完毕后,执行一个回调

    (function( $, win, undefined ){

    $.loadScripts = function( urlArrays, callback ){
    urlArrays = $.isArray( urlArrays ) ? urlArrays : [ urlArrays ];
    callback = $.isFunction( callback ) ? callback : $.noop
    var loadScript = function( url ) {
    var deferred = $.Deferred(),
    script = document.createElement(“script”);
    script.type = “text/javascript”;
    script.src = url;
    script.onload = script.onreadystatechange = function() {
    if( !this.readyState || this.readyState === “loaded” || this.readyState === “completed”) {
    deferred.resolve();
    }
    }
    document.getElementsByTagName(“head”)[0].appendChild( script );
    return deferred.promise();
    };

    var deferredObject = $.map( urlArrays, function( url ){
    return loadScript( url );
    });

    $.when.apply( $, deferredObject).then( callback );

    }

    })( jQuery, window );

  24. 也应该是很多人不懂吧。
    要是老大录些普及的视频就好了。现在网上好多高手都在录制视频教程,老大要是能将C++和LINUX编程录制下就好了。

发表评论

您的电子邮箱地址不会被公开。