本文主要介绍JavaScript执行顺序,有需要的朋友可以参考一下。
之前我们从JavaScript引擎的解析机制探讨了JavaScript的工作原理。下面,我们用一个更生动的例子来说明JavaScript代码在页面中的执行顺序。如果说JavaScript引擎的工作机制因为属于底层行为而显得深奥的话,那么JavaScript代码的执行顺序就更加形象了,因为我们可以直观的感受到这个执行顺序。当然,JavaScript代码的执行顺序比较复杂,所以在深入JavaScript语言之前有必要先分析一下。1.1 按HTML文档流顺序执行JavaScript代码首先,读者应该清楚,HTML文档在浏览器中的解析过程是这样的:浏览器按照文档流程,自上而下逐级解析页面结构和信息。JavaScript作为嵌入式脚本,也应该被认为是HTML文档的组成部分,所以JavaScript代码在加载时的执行顺序也是根据脚本标签Script的出现顺序来决定的。比如浏览下面的文档页面,你会看到代码是从上到下一步一步解析的。
复制代码如下:script alert(' top script ');/scripthtmlheadscriptalert('标头脚本');/script title/title/headbyscriptalert('页面脚本');/script/body/htmlscriptalert('底部脚本');/脚本
如果一个外部JavaScript文件脚本是通过脚本标签Script的src属性导入的,那么它也会按照其语句出现的顺序执行,执行过程是文档加载的一部分。执行不会延迟,因为它是一个外部JavaScript文件。例如,将上面文档中的页眉和正文区域的脚本移动到一个外部JavaScript文件中,然后通过src属性导入它们。继续预览页面文档,您将看到相同的执行序列。如下复制代码:脚本
Alert('顶层脚本');
/脚本
超文本标记语言
头
script src=' https://www . jb51 . net/head . js '/script
标题/标题
/头
身体
script src=' https://www . jb51 . net/body . js '/script
/body
/html
脚本
Alert(“底层脚本”);
/脚本
1.2 预编译与执行顺序的关系
在Javascript中,函数是Javascript的第一种类型。当我们写一段函数的时候,我们只是创建了一个函数类型的实体。正如我们可以这样写:复制代码code如下:function Hello(){ alert(' Hello ');} Hello();var Hello=function(){ alert(' Hello ');} Hello();其实都一样。但是当我们修改函数时,我们会发现奇怪的问题。复制的代码如下:script type=' text/JavaScript ' function hello(){ alert(' hello ');} Hello();function Hello(){ alert(' Hello World ');} Hello();/script我们会看到Hello World连续输出两次的结果。而不是我们想象中的Hello和Hello的世界。这是因为Javascript不是完全按顺序解释和执行的,而是在解释之前“预编译”的。在预编译过程中,将首先执行已定义的函数,并创建所有的var变量。默认值是未定义的,这样可以提高程序的执行效率。也就是说,上面的代码实际上是被JS引擎预编译成这样的形式:复制代码如下:script type=' text/JavaScript ' var hello=function(){ alert(' hello ');} Hello=function(){ alert(' Hello World ');} Hello();hello();/script从上面的代码我们可以清楚的看到,其实函数也是数据和变量。我们也可以分配(重新分配)给“功能”。当然为了防止这样,我们也可以这样做:复制代码代码如下:script type=' text/JavaScript ' function hello(){ alert(' hello ');} Hello();/script script type=' text/JavaScript ' function Hello(){ alert(' Hello World ');} Hello();/script这样程序就分成了两段,JS引擎就不会把它们放在一起了。
JavaScript引擎解析脚本时,会处理预编译期内所有声明的变量和函数。
进行以下处理:
1.在执行之前,会做一些类似“预编译”的事情:首先会创建一个当前执行环境中的活动对象,那些用var声明的变量会被设置为活动对象的属性。但此时这些变量的赋值是未定义的,那些用function定义的函数也会作为活动对象的属性加入,它们的值正是函数的定义。
2.在解释执行阶段,当需要解析一个变量时,会先从当前执行环境的活动对象中搜索。如果没有找到,且执行环境的所有者具有prototype属性,则从prototype链中搜索,否则根据作用域链进行搜索。当像var a=这样的语句.遇到了,就会给相应的变量赋值(注意:变量的赋值是在解释执行阶段完成的,如果在此之前使用了变量,其值将是未定义的)。因此,当JavaScript解释器执行以下脚本时,似乎不会报告任何错误:
复制代码如下:alert(a);//返回值未定义
var a=1;
警戒(一);//返回值1由于变量声明是在预编译过程中处理的,所以在执行过程中对所有代码都是可见的。但是,您还会看到,当执行上述代码时,提示的值是未定义的,而不是1。这是因为变量初始化过程发生在执行阶段,而不是在预编译阶段。在运行时,JavaScript解释器根据代码序列进行解析。如果在前面的代码行中没有给变量赋值,JavaScript解释器将使用缺省值undefined。因为变量A是在第二行赋值的,所以第三行代码会提示变量A的值是1,而不是undefined。
同样,在下面的例子中,在声明函数之前调用它是合法的,并且它可以被正确地解析,所以返回值是1。
复制代码如下:f();//调用返回值为1的函数
函数f(){
警报(1);
}
但是,如果函数定义如下,JavaScript解释器将提示语法错误。
复制代码如下:f();//调用函数并返回语法错误
var f=function(){
警报(1);
}
这是因为上例中定义的函数只是作为一个值赋给了变量F,所以在预编译期,JavaScript解释器只能处理变量F的声明,而对于变量F的值,只能等到执行的时候再按顺序赋值。自然会有语法错误,表示找不到对象F。
告别一些例子:
复制代码如下:脚本类型='text/javascript '
/*预编译过程中,func是窗口环境下活动对象中的一个属性,其值是一个函数,覆盖了未定义的值*/
alert(func);//func func(){ alert(' hello!')}
var func='这是一个变量'
函数func(){
alert('你好!')
}
/*在执行过程中,var被重新分配为'这是一个变量' */
alert(func);//这是一个变量
/脚本
复制的代码如下:script type=' text/JavaScript ' var name=' feng ';func(){ /*首先在func环境中将名称赋给undefined,然后在执行过程中在func环境中寻找活动对象的name属性。此时预编译值未定义,所以输出未定义而不是feng */alert(name);//未定义的var name=' JSF ';警报(名称);//JSF } func();警报(名称);//冯/剧本
尽管变量和函数可以在文档中的任何地方声明,但在所有JavaScript代码之前声明全局变量和函数,并对变量进行初始化和赋值是一个好习惯。在函数中,变量也是先声明,然后引用。
1.3 按块执行JavaScript代码
代码块是由脚本标记分隔的代码段。例如,下面两个脚本标记代表两个JavaScript代码块。
如下复制代码:脚本
//JavaScript代码块1
var a=1;
/脚本
脚本
//JavaScript代码块2
函数f(){
警报(1);
}
/脚本
当JavaScript解释器执行脚本时,它是分块执行的。一般来说,如果浏览器在解析HTML文档流时遇到script标签,JavaScript解释器会等到所有代码块加载完毕,预编译代码块后再执行。执行后,浏览器将继续解析下面的HTML文档流,而JavaScript解释器则准备处理下一个代码块。
因为JavaScript是分块执行的,所以如果在一个JavaScript块中调用了后面块中声明的变量或函数,就会提示语法错误。比如JavaScript解释器执行下面这段代码,会提示语法错误,显示变量A未定义,找不到对象F。
如下复制代码:脚本
//JavaScript代码块1
警戒(一);
f();
/脚本
脚本
//JavaScript代码块2
var a=1;
函数f(){
警报(1);
}
/脚本
虽然JavaScript是分块执行的,但是不同的块属于同一个全局范围,也就是说块之间的变量和函数是可以共享的。
1.4 借助事件机制改变JavaScript执行顺序
因为JavaScript以块为单位处理代码,并遵循HTML文档流的解析顺序,所以您会在上面的示例中看到这样的语法错误。但是,当加载文档流时,如果再次访问它,就不会出现这样的错误。例如,如果将访问变量和函数的代码放在页面初始化事件函数的第二个代码块中,就不会出现语法错误。
如下复制代码:脚本
//JavaScript代码块1
Window.onload=function(){ //页面初始化事件处理程序
警戒(一);
f();
}
/脚本
脚本
//JavaScript代码块2
var a=1;
函数f(){
警报(1);
}
/脚本
为了保险起见,我们通常允许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=' 3359 www.jb51.net/test.js'
/script’);
document . write(' script type=' text/JavaScript ' ');
document . write(' alert(n);');//IE提示找不到变量n
document . write('/script ');
警报(n 1);//所有浏览器都会提示找不到变量n
外部JavaScript文件(test.js)的代码如下:Copy code代码如下:var n=1;在不同的浏览器中测试,会发现提示语法错误,变量n找不到。也就是说,如果在JavaScript代码块中访问这个代码块中的document.write()方法在脚本输出中导入的外部JavaScript文件中包含的变量,就会显示语法错误。同时,如果你在IE浏览器中,不仅在脚本中,在输出脚本中也会提示找不到导入到外部JavaScript文件中的输出变量(表达式有点长且复杂,不懂的读者可以尝试运行上面的代码)。
其次,不同的JavaScript引擎执行输出外部导入脚本的顺序略有不同。例如,看看下面的示例代码。
复制代码如下:脚本类型='text/javascript '
document . write(' script type=' text/JavaScript ' src=' http://shaozhu Qing . com/test1 . js '
/script’);
document . write(' script type=' text/JavaScript ' ');
document . write(' alert(2);')
document . write(' alert(N2);');
document . write('/script ');
/脚本
脚本类型='文本/javascript '
警报(n 3);
/script外部JavaScript文件(test1.js)的代码如下所示。复制代码如下:var n=1;
警报(n);
IE浏览器中的执行顺序如图1-6所示。
图1-6 IE 7浏览器的执行顺序和提示的语法错误
兼容DOM的浏览器中的执行顺序与IE浏览器中的不同,没有语法错误。Firefox 3.0浏览器中的执行顺序如图1-7所示。
图1-7 Firefox 3浏览器的执行顺序和提示的语法错误
解决不同浏览器的不同执行顺序和可能出现的bug。我们可以把所有使用输出脚本导入的外部文件放在单独的代码块中,这样按照上面描述的JavaScript代码块执行顺序就可以避免这个问题。例如,对于上面的例子,可以这样设计:
复制代码如下:脚本类型='text/javascript '
document . write(' script type=' text/JavaScript ' src=' https://www . jb51 . net/test1 . js '/script ');
/脚本
脚本类型='文本/javascript '
document . write(' script type=' text/JavaScript ' ');
document . write(' alert(2);') ;//提示2
document . write(' alert(N2);');//提示3
document . write('/script ');
警报(n 3);//提示4
/脚本
脚本类型='文本/javascript '
警报(第4号);//提示5
/脚本
这样,上述代码可以在不同的浏览器中依次执行,输出顺序为1、2、3、4、5。问题的原因是:输出导入脚本与当前JavaScript代码块的矛盾。如果分开输出,就不会有冲突。
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。