这篇博客主要讲解JavaScript的执行顺序,通过这篇博客可以理解为什么先使用再声明有时候可以有时候却不可以、JavaScript代码在各种情况下的执行顺序等问题。
文档流
HTML文档再浏览器中的解析顺序,是按照文档流从上到下逐步解析页面结构和信息。JavaScript作为嵌入脚本也是HTML文档的组成部分,所以JavaScript代码在装载时也是根据脚本标签<script>的顺序确定的。
界面脚本
例如下面的代码:
1. <script type="text/javascript">alert('我是最前面的脚本')</script>
2. <html>
3. <head>
4. "text/javascript">alert('我是头部脚本')</script>
5. </head>
6. <body>
7. "text/javascript">alert('我是页面脚本')</script>
8. </body>
9. </html>
10. <script type="text/javascript">alert('我是最后面的脚本')</script>
顺序为:最前面---头部---页面---最后面,依次弹出。
引用脚本
引用的外部脚本也将按照引用出现的顺序执行,不会因为是外部脚本而延期执行,例如把头部脚本和页面脚本作为引用文件时:
1. <script type="text/javascript">alert('我是最前面的脚本')</script>
2. <html>
3. <head>
4. "head.js" type="text/javascript"></script>
5. </head>
6. <body>
7. "body.js" type="text/javascript"></script>
8. </body>
9. </html>
10. <script type="text/javascript">alert('我是最后面的脚本')</script>
最前面---头部---页面---最后面。
与预编译
上一篇博客提到,JavaScript解析脚本时,在编译期即对所有声明的变量和函数进行处理。
变量
所以一下脚本在执行时不会出错:
1. <html>
2. <head>
3. <script type="text/javascript">
4. alert(a);
5. a=1;
6. alert(a);
7. </script>
8. </head>
9. <body>
10. </body>
11. </html>
执行结果为:
这是因为在预编译期,已经对所有变量进行了处理,第一次弹出时,解释器已经知道了a的存在,所以并没有提示错误。你一定想问,既然知道a的存在,为什么第一次弹出的是undefined?这是因为变量在执行期才会对变量赋值,变量的声明和赋值处在不同的阶段;第二次弹出的是“1”,这是因为在第二次弹出时,已经对a赋值,故提示为1。
需要注意的是,显示undefined并不是错误,而是遍历作用域链没有找到值,如果把a换成b,才是错误,提示如下:
函数
同理,函数在声明前调用也是合理的例如:
1. <html>
2. <head>
3. <script type="text/javascript">
4. f();
5. function f()
6. {
7. alert('I am a function');
8. }
9. </script>
10. </head>
11. <body>
12. </body>
13. </html>
运行结果:
但是以下这样会提示语法错误:
这两次代码如此相似,为什么确实完全不同的结果?这是因为第二次,我们把f当做一个变量处理,所以在预编译期,JavaScript解释器只能够为变量f进行处理,对于f的值,只能等到运行期才能赋值,所以提示找不到对象f。
按块执行
此处所谓的块指的是<script>标签分隔的代码块,JavaScript解释器在执行脚本时,是按照块来执行的,也就是说:浏览器在解析HTML文档流时,如果遇到<script>标签,则解释器会等到把这个块加载完后,先对块进行预编译,然后再执行。
正因为如此,当前面一个块调用后面一个块中声明的变量或函数时会提示语法错误:
1. <html>
2. <head>
3. <script type="text/javascript">
4. f();
5. </script>
6. <script type="text/javascript">
7. function f()
8. {
9. alert('I am a function');
10. }
11. </script>
12. </head>
13. <body>
14. </body>
15. </html>
错误提示:
与此同在的是,JavaScript虽然按块执行,但是不同的块都属于同一个全局作用域,也就是说,块之间的变量和函数可以共享。
事件机制
如上面所说,JavaScript按块执行,同时又遵循HTML文档流的解析顺序,所以上面的代码会提示错误。但是如果文档加载完毕再运行就不会出错,所以我们可以用事件来解决:
1. <html>
2. <head>
3. "text/javascript">
4. window.οnlοad=function()
5. {
6. f();
7. };
8. </script>
9. "text/javascript">
10. function f()
11. {
12. 'I am a function');
13. }
14. </script>
15. </head>
16. <body>
17. </body>
18. </html>
运行结果:
输出脚本
输出脚本指的是动态添加的脚本,例如:
1. <html>
2. <head>
3. </head>
4. <body>
5. <script type="text/javascript">
6. <script type='text/javascript'>");
7. document.write(' alert(1); ');
8. <\/script>")
9. </script>
10. </body>
11. </html>
浏览器会弹出框“1”。
输出脚本的处理过程是:document.write()方法先把输出的字符串写入到脚本所在文档,浏览器在解析完毕document.write()文档后,继续解析document.write输出的内容,然后再解析后面的HTML文档,也就是说,JavaScript的代码字符串,会在输出后马上执行。
需要注意的是,输出的如果是JavaScript脚本,一定要放在:
1. <script type="text/javascript">
2. "<script type='text/javascript'>");
3. //要输入的脚本
4. "<\/script>")
5. </script>
之间,否则会当做普通字符出现,而不会执行(Chrome浏览器)。
另外因为不同浏览器对document.write()输出的运行脚本和用document.write()引用的外部执行顺序不同,可能出现解析错误,所以可以二者放在不同的<script>标签下,兼容浏览器。
总结
上一篇博客我们介绍了JavaScript解析机制,比较抽象,而这篇博客说的执行顺序就比较具体,我们可以直观感觉到这种执行顺序。