JavaScript执行顺序详细介绍


Posted in Javascript onDecember 04, 2013

之前从JavaScript引擎的解析机制来探索JavaScript的工作原理,下面我们以更形象的示例来说明JavaScript代码在页面中的执行顺序。如果说,JavaScript引擎的工作机制比较深奥是因为它属于底层行为,那么JavaScript代码执行顺序就比较形象了,因为我们可以直观感觉到这种执行顺序,当然JavaScript代码的执行顺序是比较复杂的,所以在深入JavaScript语言之前也有必要对其进行剖析。
1.1  按HTML文档流顺序执行JavaScript代码
首先,读者应该清楚,HTML文档在浏览器中的解析过程是这样的:浏览器是按着文档流从上到下逐步解析页面结构和信息的。JavaScript代码作为嵌入的脚本应该也算做HTML文档的组成部分,所以JavaScript代码在装载时的执行顺序也是根据脚本标签<script>的出现顺序来确定的。例如,浏览下面文档页面,你会看到代码是从上到下逐步被解析的。

<script>
alert("顶部脚本");
</script>
<html><head>
<script>
alert("头部脚本");
</script>
<title></title>
</head>
<body>
<script>
alert("页面脚本");
</script>
</body></html>
<script>
alert("底部脚本");
</script>

如果通过脚本标签<script>的src属性导入外部JavaScript文件脚本,那么它也将按照其语句出现的顺序来执行,而且执行过程是文档装载的一部分。不会因为是外部JavaScript文件而延期执行。例如,把上面文档中的头部和主体区域的脚本移到外部JavaScript文件中,然后通过src属性导入。继续预览页面文档,你会看到相同的执行顺序。

<script>
alert("顶部脚本");
</script>
<html>
<head>
<script src="https://3water.com/head.js"></script>
<title></title>
</head>
<body>
<script src="https://3water.com/body.js"></script>
</body>
</html>
<script>
alert("底部脚本");
</script>

1.2  预编译与执行顺序的关系

在Javascript中,function才是Javascript的第一型。当我们写下一段函数时,其实不过是建立了一个function类型的实体。
就像我们可以写成这样的形式一样:

functionHello() 
{
alert("Hello"); 
}
Hello(); 
varHello = function() 
{
alert("Hello");
} 
Hello();

其实都是一样的。 但是当我们对其中的函数进行修改时,会发现很奇怪的问题。
<scripttype="text/javascript">        
functionHello() {         
alert("Hello");   
}        
Hello();      
functionHello() {   
alert("Hello World");    
}        
Hello();    
</script>

我们会看到这样的结果:连续输出了两次Hello World。
而非我们想象中的Hello和Hello World。
这是因为Javascript并非完全的按顺序解释执行,而是在解释之前会对Javascript进行一次“预编译”,在预编译的过程中,会把定义式的函数优先执行,也会把所有var变量创建,默认值为undefined,以提高程序的执行效率。
也就是说上面的一段代码其实被JS引擎预编译为这样的形式:
<scripttype="text/javascript">        
varHello = function() {           
alert("Hello");        
}       
Hello = function() { 
           alert("Hello World");  
      }      
Hello();     
Hello();  
</script>

我们可以通过上面的代码很清晰地看到,其实函数也是数据,也是变量,我们也可以对“函数“进行赋值(重赋值)。
当然,我们为了防止这样的情况,也可以这样:
<scripttype="text/javascript">      
functionHello() {        
alert("Hello");       
}        
Hello();  
</script>   
<scripttype="text/javascript">   
functionHello() {          
alert("Hello World");     
}       
Hello();  
</script>

这样,程序被分成了两段,JS引擎也就不会把他们放到一起了。   

当JavaScript引擎解析脚本时,它会在预编译期对所有声明的变量和函数进行处理。

做如下处理:

1. 在执行前会进行类似“预编译”的操作:首先会创建一个当前执行环境下的活动对象,并将那些用var申明的变量设置为活动对象的属性,但是此时这些变量的赋值都是undefined,并将那些以function定义的函数也添加为活动对象的属性,而且它们的值正是函数的定义。

2. 在解释执行阶段,遇到变量需要解析时,会首先从当前执行环境的活动对象中查找,如果没有找到而且该执行环境的拥有者有prototype属性时则会从prototype链中查找,否则将会按照作用域链查找。遇到var a = ...这样的语句时会给相应的变量进行赋值(注意:变量的赋值是在解释执行阶段完成的,如果在这之前使用变量,它的值会是undefined) 所以,就会出现当JavaScript解释器执行下面脚本时不会报错:

alert(a);                            // 返回值undefined
var a =1;
alert(a);                            // 返回值1

由于变量声明是在预编译期被处理的,所以在执行期间对于所有代码来说,都是可见的。但是,你也会看到,执行上面代码,提示的值是undefined,而不是1。这是因为,变量初始化过程发生在执行期,而不是预编译期。在执行期,JavaScript解释器是按着代码先后顺序进行解析的,如果在前面代码行中没有为变量赋值,则JavaScript解释器会使用默认值undefined。由于在第二行中为变量a赋值了,所以在第三行代码中会提示变量a的值为1,而不是undefined。

同理,下面示例在函数声明前调用函数也是合法的,并能够被正确解析,所以返回值为1。

f();                                 // 调用函数,返回值1
function f(){
    alert(1);
}

但是,如果按下面方式定义函数,则JavaScript解释器会提示语法错误。

f();                                 // 调用函数,返回语法错误
var f = function(){
    alert(1);
}

这是因为,上面示例中定义的函数仅作为值赋值给变量f,所以在预编译期,JavaScript解释器只能够为声明变量f进行处理,而对于变量f的值,只能等到执行期时按顺序进行赋值,自然就会出现语法错误,提示找不到对象f。

再见一些例子:

<script type="text/javascript">
/*在预编译过程中func是window环境下的活动对象中的一个属性,值是一个函数,覆盖了undefined值*/
alert(func); //function func(){alert("hello!")}
var func = "this is a variable"
function func(){
alert("hello!")
}
/*在执行过程中遇到了var重新赋值为"this is a variable"*/
alert(func);  //this is a variable
</script>
<script type="text/javascript"> 
var name = "feng"; function func()
{ 
/*首先,在func环境内先把name赋值为undefined,然后在执行过程中先寻找func环境下的活动对象的name属性,此时之前已经预编译值为undefined,所以输出是undefined,而不是feng*/ 
alert(name);  //undefined var name = "JSF"; 
alert(name);  //JSF 
}
func(); 
alert(name); 
//feng
</script>

虽然变量和函数声明可以在文档任意位置,但是良好的习惯应该是在所有JavaScript代码之前声明全局变量和函数,并对变量进行初始化赋值。在函数内部也是先声明变量,然后再引用。

1.3  按块执行JavaScript代码

所谓代码块就是使用<script>标签分隔的代码段。例如,下面两个<script>标签分别代表两个JavaScript代码块。

<script>
// JavaScript代码块1
var a =1;
</script>
<script>
// JavaScript代码块2
function f(){
    alert(1);
}
</script>

JavaScript解释器在执行脚本时,是按块来执行的。通俗地说,就是浏览器在解析HTML文档流时,如果遇到一个<script>标签,则JavaScript解释器会等到这个代码块都加载完后,先对代码块进行预编译,然后再执行。执行完毕后,浏览器会继续解析下面的HTML文档流,同时JavaScript解释器也准备好处理下一个代码块。

由于JavaScript是按块执行的,所以如果在一个JavaScript块中调用后面块中声明的变量或函数就会提示语法错误。例如,当JavaScript解释器执行下面代码时就会提示语法错误,显示变量a未定义,对象f找不到。

<script>
// JavaScript代码块1
alert(a);
f();
</script>
<script>
// JavaScript代码块2
var a =1;
function f(){
    alert(1);
}
</script>

虽然说,JavaScript是按块执行的,但是不同块都属于同一个全局作用域,也就是说,块之间的变量和函数是可以共享的。

1.4  借助事件机制改变JavaScript执行顺序

由于JavaScript是按块处理代码,同时又遵循HTML文档流的解析顺序,所以在上面示例中会看到这样的语法错误。但是当文档流加载完毕,如果再次访问就不会出现这样的错误。例如,把访问第2块代码中的变量和函数的代码放在页面初始化事件函数中,就不会出现语法错误了。

<script>
// JavaScript代码块1
window.onload = function(){        // 页面初始化事件处理函数
    alert(a);
    f();
}
</script>
<script>
// JavaScript代码块2
var a =1;
function f(){
    alert(1);
}
</script>

为了安全起见,我们一般在页面初始化完毕之后才允许JavaScript代码执行,这样可以避免网速对JavaScript执行的影响,同时也避开了HTML文档流对于JavaScript执行的限制。

注意

如果在一个页面中存在多个windows.onload事件处理函数,则只有最后一个才是有效的,为了解决这个问题,可以把所有脚本或调用函数都放在同一个onload事件处理函数中,例如:

window.onload = function(){
    f1();
    f2();
    f3();
}

而且通过这种方式可以改变函数的执行顺序,方法是:简单地调整onload事件处理函数中调用函数的排列顺序。

除了页面初始化事件外,我们还可以通过各种交互事件来改变JavaScript代码的执行顺序,如鼠标事件、键盘事件及时钟触发器等方法,详细讲解请参阅第14章的内容。

1.5  JavaScript输出脚本的执行顺序

在JavaScript开发中,经常会使用document对象的write()方法输出JavaScript脚本。那么这些动态输出的脚本是如何执行的呢?例如:

document.write('<script type="text/javascript">');
document.write('f();');
document.write('function f(){');
document.write('alert(1);');
document.write('}');
document.write('</script>');

运行上面代码,我们会发现:document.write()方法先把输出的脚本字符串写入到脚本所在的文档位置,浏览器在解析完document.write()所在文档内容后,继续解析document.write()输出的内容,然后才按顺序解析后面的HTML文档。也就是说,JavaScript脚本输出的代码字符串会在输出后马上被执行。

请注意,使用document.write()方法输出的JavaScript脚本字符串必须放在同时被输出的<script>标签中,否则JavaScript解释器因为不能够识别这些合法的JavaScript代码,而作为普通的字符串显示在页面文档中。例如,下面的代码就会把JavaScript代码显示出来,而不是执行它。

document.write('f();');
document.write('function f(){');
document.write('alert(1);');
document.write(');');

但是,通过document.write()方法输出脚本并执行也存在一定的风险,因为不同JavaScript引擎对其执行顺序不同,同时不同浏览器在解析时也会出现Bug。

Ø 问题一,找不到通过document.write()方法导入的外部JavaScript文件中声明的变量或函数。例如,看下面示例代码。

document.write('<script type="text/javascript" src="https://3water.com/test.js">
</script>');
document.write('<script type="text/javascript">');
document.write('alert(n);');  // IE提示找不到变量n
document.write('</script>');
alert(n+1);                          // 所有浏览器都会提示找不到变量n

外部JavaScript文件(test.js)的代码如下:

var n = 1;

分别在不同浏览器中进行测试,会发现提示语法错误,找不到变量n。也就是说,如果在JavaScript代码块中访问本代码块中使用document.write()方法输出的脚本中导入的外部JavaScript文件所包含的变量,会显示语法错误。同时,如果在IE浏览器中,不仅在脚本中,而且在输出的脚本中也会提示找不到输出的导入外部JavaScript文件的变量(表述有点长和绕,不懂的读者可以尝试运行上面代码即可明白)。

Ø 问题二,不同JavaScript引擎对输出的外部导入脚本的执行顺序略有不同。例如,看下面示例代码。

<script type="text/javascript">
document.write('<script type="text/javascript" src="http://shaozhuqing.com/test1.js">
</script>');
document.write('<script type="text/javascript">');
document.write('alert(2);')
document.write('alert(n+2);');
document.write('</script>');
</script>
<script type="text/javascript">
alert(n+3);
</script>

外部JavaScript文件(test1.js)的代码如下所示。
var n = 1;
alert(n);

在IE浏览器中的执行顺序如图1-6所示。

JavaScript执行顺序详细介绍

图1-6  IE 7浏览器的执行顺序和提示的语法错误

在符合DOM标准的浏览器中的执行顺序与IE浏览器不同,且没有语法错误,如图1-7所示的是在Firefox 3.0浏览器中的执行顺序。

JavaScript执行顺序详细介绍

图1-7  Firefox 3浏览器的执行顺序和提示的语法错误

解决不同浏览器存在的不同执行顺序,以及可能存在Bug。我们可以把凡是使用输出脚本导入的外部文件,都放在独立的代码块中,这样根据上面介绍的JavaScript代码块执行顺序,就可以避免这个问题。例如,针对上面示例,可以这样设计:

<script type="text/javascript">
document.write('<script type="text/javascript" src="https://3water.com/test1.js"></script>');
</script>
<script type="text/javascript">
document.write('<script type="text/javascript">');
document.write('alert(2);') ; // 提示2
document.write('alert(n+2);'); // 提示3
document.write('</script>');
alert(n+3); // 提示4
</script>
<script type="text/javascript">
alert(n+4); // 提示5
</script>

这样在不同浏览器中都能够按顺序执行上面代码,且输出顺序都是1、2、3、4和5。存在问题的原因是:输出导入的脚本与当前JavaScript代码块之间的矛盾。如果单独输出就不会发生冲突了。

Javascript 相关文章推荐
javascript检查日期格式的函数[比较全]
Oct 17 Javascript
JavaScript Date对象使用总结
May 14 Javascript
style、 currentStyle、 runtimeStyle区别分析
Aug 01 Javascript
jQuery源码中的chunker 正则过滤符分析
Jul 31 Javascript
jquery.Ajax()方法调用Asp.Net后台的方法解析
Feb 13 Javascript
jQuery+ajax中getJSON() 用法实例
Dec 22 Javascript
JS实现下拉菜单赋值到文本框的方法
Aug 18 Javascript
AngularJS仿苹果滑屏删除控件
Jan 18 Javascript
vue2组件实现懒加载浅析
Mar 29 Javascript
详解在微信小程序的JS脚本中使用Promise来优化函数处理
Mar 06 Javascript
vue项目中mock.js的使用及基本用法
May 22 Javascript
vue项目打包后提交到git上为什么没有dist这个文件的解决方法
Sep 16 Javascript
jquery validate添加自定义验证规则(验证邮箱 邮政编码)
Dec 04 #Javascript
浏览器图片选择预览、旋转、批量上传的JS代码实现
Dec 04 #Javascript
jquery内置验证(validate)使用方法示例(表单验证)
Dec 04 #Javascript
两个数组去重的JS代码
Dec 04 #Javascript
jquery使用jquery.zclip插件复制对象的实例教程
Dec 04 #Javascript
验证控件与Button的OnClientClick事件详细解析
Dec 04 #Javascript
快速解决FusionCharts联动的中文乱码问题
Dec 04 #Javascript
You might like
PHP新手上路(十一)
2006/10/09 PHP
PHP遍历目录并返回统计目录大小
2014/06/09 PHP
Codeigniter整合Tank Auth权限类库详解
2014/06/12 PHP
PHP二维数组矩形转置实例
2016/07/20 PHP
Django 标签筛选的实现代码(一对多、多对多)
2018/09/05 PHP
javascript multibox 全选
2009/03/22 Javascript
JS构建页面的DOM节点结构的实现代码
2011/12/09 Javascript
js字符串转换成xml对象并使用技巧解读
2013/04/18 Javascript
最短的IE判断var ie=!-[1,]分析
2014/05/28 Javascript
jQuery on()方法使用技巧详解
2015/04/16 Javascript
跟我学习javascript的prototype使用注意事项
2015/11/17 Javascript
JavaScript中的原始值和复杂值
2016/01/07 Javascript
jQuery原理系列-css选择器的简单实现
2016/06/07 Javascript
微信小程序Server端环境配置详解(SSL, Nginx HTTPS,TLS 1.2 升级)
2017/01/12 Javascript
jQuery插件zTree实现获取当前选中节点在同级节点中序号的方法
2017/03/08 Javascript
web3.js增加eth.getRawTransactionByHash(txhash)方法步骤
2018/03/15 Javascript
vue router 配置路由的方法
2018/07/26 Javascript
Puppeteer 爬取动态生成的网页实战
2018/11/14 Javascript
小程序实现搜索界面 小程序实现推荐搜索列表效果
2019/05/18 Javascript
Layui之table中的radio在切换分页时无法记住选中状态的解决方法
2019/09/02 Javascript
原生js+css调节音量滑块
2020/01/15 Javascript
深入解析Python中的集合类型操作符
2015/08/19 Python
windows下python安装paramiko模块和pycrypto模块(简单三步)
2017/07/06 Python
Python绑定方法与非绑定方法详解
2017/08/18 Python
python中利用队列asyncio.Queue进行通讯详解
2017/09/10 Python
python原类、类的创建过程与方法详解
2019/07/19 Python
应用OpenCV和Python进行SIFT算法的实现详解
2019/08/21 Python
Python新手如何进行闭包时绑定变量操作
2020/05/29 Python
详细分析Python collections工具库
2020/07/16 Python
HTML5基于flash实现播放RTMP协议视频的示例代码
2020/12/04 HTML / CSS
英国一家专门出售品牌鞋子的网站:Allsole
2016/08/07 全球购物
安全责任书范文
2014/03/12 职场文书
个人借款担保书
2014/04/02 职场文书
勤俭节约演讲稿
2014/05/08 职场文书
业务员辞职信范文
2015/03/02 职场文书
MySQL 发生同步延迟时Seconds_Behind_Master还为0的原因
2021/06/21 MySQL