首页 > Web开发, 程序设计 > Javascript 装载和执行

Javascript 装载和执行

2013年6月5日 发表评论 阅读评论 57,593 人阅读    

一两个月前在淘宝内网里看到一个优化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="http://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 = 'http://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 = 'http://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("http://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.cn ,请勿用于任何商业用途)

——=== 访问 酷壳404页面 寻找遗失儿童。 ===——
好烂啊有点差凑合看看还不错很精彩 (53 人打了分,平均分: 4.77 )
Loading...
  1. pake007
    2013年6月15日11:06 | #1

    之前使用过一个不错的js异步加载库bezen.js,是通过重写了document.write方法来实现的。

  2. 亦凡
    2013年6月15日11:16 | #2

    #defer只是IE专用#
    其实吧最新版本的chrome,firefox都支持defer这特性(测试版本:chrome26,firefox20)

  3. 2013年6月16日02:15 | #3

    百度卢松松贴吧 http://tieba.baidu.com/f?kw=%C2%AC%CB%C9%CB%C9 希望大家支持!

  4. 2013年6月16日17:17 | #4

    8年前就在看你的 GDB 调试的文章,没想到你对 JS 也有这么好的研究

  5. 2013年6月18日10:58 | #5

    JS优化对于网站的用户体验至关重要,谢谢博主的分享。

  6. 问一个问题
    2013年6月19日10:42 | #6

    您那么推崇国外邮箱,甚至说自己从来不用国内邮箱。棱镜项目安全么?这就是你的安全,专业?科学是有国际的,洋帮办

  7. 2013年6月20日13:30 | #7

    感觉js一点都不简单的说,动态的东西就是需要灵活掌握

  8. 2013年6月20日13:32 | #8

    @沫小熙iBlog
    卢松松的博客现在还没以前好看了,不过还是挺有内容的

  9. 2013年6月23日10:51 | #9

    – -我怎么一点都看不懂。。肿么办。

  10. dgdg
    2013年6月25日14:24 | #10

    很不错,学习了

  11. 2013年6月26日09:08 | #11

    所以说最好把js加载放到最后

  12. 2013年6月26日13:49 | #12

    大神的文章收益匪浅,果断转载之 http://linjunlong.com/articles/load-and-run-javascript.html

  13. 2013年6月29日11:05 | #13

    怎么这么复杂

  14. 2013年6月29日17:05 | #14

    异步加载可以分成两个步骤来走,一个步骤是在快要滚动到dom节点时,预取js源码,取来的js源码做的事情很简单,仅仅是给指定的dom节点绑定事件,第二个步骤是在滚动到指定的地方时,由鼠标来出发事件,竟而得到异步的效果,这样阻塞的感觉几乎看不到。

  15. asd
    2013年6月30日14:52 | #15

    DOJO AMD吧 比这个先进太多了.模块化,可定制,无全局污染.不加载dijit之类的废品功能和jquery差不多大.

  16. Smith Lo
    2013年6月30日22:49 | #16

    看上去是回事,但是却不少误人之处。

  17. 2013年7月6日12:21 | #17

    写的真好。有时间好好再研究下。谢谢分享。

  18. 2013年7月6日12:22 | #18

    写的真好。有时间好好再研究下。谢谢分享。
    你的评论要写多长?

  19. 2013年7月16日00:55 | #19

    可以写一个模块化加载器,按需加载,通过回调函数的方式依赖加载,不会出现找不到对象的情况。

  20. tezheng
    2013年7月16日19:58 | #20

    不同浏览器实现差别还是有的, 呵呵. 所以最好指明是哪个浏览器, 哪个版本

  21. xiaohan
    2013年7月23日17:11 | #21

    我是在chrome 版本28上测试的
    那个示例6 点击红色按钮后,仍旧会下载alert.js文件

  22. etherdream
    2013年7月25日11:16 | #22

    new Image().src=’x.js’ 可以(少数浏览器不支持)

  23. jack
    2013年7月28日22:24 | #23

    建议可以指出问题所在之处@Smith Lo

  24. lazyPHP
    2013年7月30日13:07 | #24

    最近刚好遇到 类似问题。视野瞬间打开不小。
    谷歌浏览器 Ctrl+Shit+I 还不如F12

  25. 三世叛逆
    2013年9月26日15:55 | #25

    @etherdream 确实不对,不知道楼主是用的什么浏览器!

  26. erpeng
    2013年10月24日00:05 | #26

    学习下。为什么浏览器不直接渲染完后再执行js呢

  27. 2013年11月16日22:51 | #27

    不错的文章!

  28. lxy
    2013年11月19日15:46 | #28

    那个示例6,用object的data属性,但是alert.js没有加载,当点击按钮后,alert.js才加载到浏览中的。是不是哪个地方有什么不对的地方。

  29. 浮出一个美
    2013年12月2日09:06 | #29

    谢谢你,让我长见识。

  30. DINGHERRY
    2013年12月6日15:33 | #30

    不知是不是你没看懂耗子同学的想法。按需预载本身是个问题,提前多久加载合适?网络不好时你这样就是最坏的情况。通常的做法,都是先取过来放着,需要的时候再执行。@weager

  31. liuyunclouder
    2014年1月15日11:40 | #31

    总结得挺全面的呵呵,有一个小地方

    目前浏览器会下载不执行但也不会删除,可以用内联的方式

    alert();

    这样就能获取想要的js内容了。

    另外,下载却不执行js还有个办法,js里发请求,content-type设为text/plain之类,获取js字符串,想要执行的时候再eval,虽然eval不安全,如果想要安全点,限制js字符串内容,比如json,然后用安全的JSON.parse来解析

  32. 惊叹号…
    2014年1月23日09:59 | #32

    学习了,谢谢耗哥。这篇文章在“示例六”下“在Chrome下按 Ctrl+Shit+I,”有个文字错误。”Shit -> Shift”

  33. Gstar
    2014年2月12日16:31 | #33

    @lxy
    试试标签放在body标签后

  34. Gstar
    2014年2月12日16:44 | #34

    object加载一次后,触发P标签的点击事件js又加载了一次,不是从缓存中得到的,但是运行你的例子六是没问题的。

  35. asdfas
    2014年3月5日12:28 | #35

    装载?执行?这恐怕不是interpreter的术语吧?建议看点儿compiler的东西。

  36. asdfas
    2014年3月5日12:30 | #36

    JS优化只能在interpreter内部进行,想要像C语言那样优化根本就不行。

  37. Mr.ninty
    2014年10月8日10:34 | #37
评论分页
1 2 9749
  1. 2013年6月26日13:23 | #1
  2. 2013年7月20日07:26 | #2
  3. 2013年12月3日09:51 | #3
  4. 2013年12月3日14:18 | #4
  5. 2014年1月22日13:57 | #5
  6. 2014年1月22日16:17 | #6
  7. 2015年3月1日20:32 | #7
  8. 2015年5月31日03:01 | #8
  9. 2016年3月11日21:28 | #9