JavaScript 被定义为嵌入式 Web 脚本语言。在 HTML 页面中嵌入 JavaScript 脚本,需要用 <srcipt> 标签。用户可以在 <script> 标签总直接编写 JavaScript 代码,或者单独编写 JavaScript 文件,让后通过 <script> 标签导入。
1、编写脚本
使用 <script> 标签又两种方式:直接在页面中嵌入 JavaScript 代码和包含外部 JavaScript 文件。
包含在 <script> 标签内的 JavaScript 代码在浏览器总按照从上至下的顺序依次解释。
所有 <script> 标签都会按照他们在 HTML 中出现的先后顺序依次被解析。
HTML 为 <script> 定义了几个属性:
(1)async:可选。表示应该立即下载脚本,但不妨碍页面中其他操作。该功能只对外部 JavaScript 文件有效。
如果给一个外部引入的js文件设置了这个属性,那页面在解析代码的时候遇到这个<script>的时候,一边下载该脚本文件,一边异步加载页面其他内容。
(2)defer:可选。表示脚本可以延迟到整个页面完全被解析和显示之后再执行。该属性只对外部 JavaScript 文件有效。
(3)src可选。表示包含要执行代码的外部文件。
(4)type:可选。表示编写代码使用的脚本语言的内容类型,目前在客户端,type 属性值一般使用 text/javascript。不过这个属性并不是必需的,如果没有指定这个属性,则其默认值仍为text/javascript。
2、脚本标签位置
所有 <script> 标签都会按照它们在 HTML 中出现的先后顺序被解析。在不适用 defer 和 async 属性的情况下,只有在解析完前面的 <script> 标签中的代码后,才会开始解析后面的 <script> 标签中的代码。
在默认情况下,所有 <script> 标签都应该放在页面的头部 <head> 标签中。这样就可以把所有外部文件(包括CSS文件和JavaScript文件)的引用都放在相同的地方。但是,在文档的 <head> 标签中包含所有 JavaScript 文件,意味着必须等到全部JavaScript 代码都被下载、解析和执行完以后,才能开始呈现页面的内容。如果页面需要很多JavaScript代码,这样无疑会导致浏览器在呈现页面时出现明显的延迟,而延迟期间的浏览器窗口中将是一片空白。
为了避免上述操作带来的延迟问题,现在 Web 应用程序一般会把所有的 JavaScript 引用放到 <body> 标签中页面内容的后面,这样在解析包含的 JavaScript代码之前,页面的内容完全呈现在浏览器中,同时会感到打开网页的速度加快了。
JavaScript 解析过程包括两个阶段:预处理(也称预编译)和执行。在编译期,JavaScript 解析器将完成对 JavaScript 代码的预处理操作,把 JavaScript 代码转换成字节码;在执行期,JavaScript 解析器把字节码生成二进制机械码,并按顺序执行,完成程序设计的任务。
1、执行过程
HTML 文档在浏览器中的解析过程是:按照文档流从上到下逐步解析页面结构和信息。JavaScript 代码作为嵌入的脚本应该也算做 HTML 文档的组成部分,所以 JavaScript 代码在装载时的执行顺序也是根据 <script> 标签出现的顺序来确定。
2、预编译
当 JavaScript 引擎解析脚本时候,他会在与编译期对所有声明的变量和函数预先进行处理。当 JavaScript 解析器执行下面脚本时不会报错。
alert(a); //返回值 undefined var a = 1; alert(a); //返回值 1由于变量声明是在预编译期被处理的,在执行期间对于所有的代码来说,都是可见的,但是执行上面代码,提示的值是 undefined 而不是 1。因为变量初始化过程发生在执行期,而不是预编译期。在执行期,JavaScript 解析器是按照代码先后顺序进行解析的,如果在前面代码行中没有为变量赋值,则 JavaScript 解析器会使用默认值 undefined 。由于第二行中为变量 a 赋值了,所以在第三行代码中会提示变量 a 的值为 1,而不是 undefined。
fun(); //调用函数,返回值1 function fun(){ alert(1); }函数声明前调用函数也是合法的,并能够正确解析,所以返回值是 1。但如果是下面这种方式则 JavaScript 解释器会报错。
fun(); //调用函数,返回语法错误 var fun = function(){ alert(1); }上面的这个例子中定义的函数仅作为值赋值给变量 fun 。在预编译期,JavaScript 解释器只能够为声明变量 fun 进行处理,而对于变量 fun 的值,只能等到执行期时按照顺序进行赋值,自然就会出现语法错误,提示找不到对象 fun。
总结:声明变量和函数可以在文档的任意位置,但是良好的习惯应该是在所有 JavaScript 代码之前声明全局变量和函数,并对变量进行初始化赋值。在函数内部也是先声明变量,后引用。
3、代码块
JavaScript 解释器在执行脚本时,是按块来执行的。浏览器在解析 HTML 文档流时,如果遇到一个<script> 标签,则 JavaScript 解释器会等到这个代码块都加载完后,先对代码块进行预编译,然后再执行。执行完毕后,浏览器会继续解析下面的HTML 文档流,同时 JavaScript 解释器也准备好处理下个代码块。
如果在一个 JavaScript 块中调用后面块中声明的变量或函数就会提示语法错误。比如:
<script> //JavaScript 代码块 1 alert(a); fun(); </script> <script> //JavaScript 代码块 2 var a = 1; function fun(){ alert(1); } </script>但是如果上面两个代码块的前后顺序颠倒以下,就不会报错,因为虽然 JavaScipt 是按块执行的,但是不同块都属于同一个全局作用域,块之间的变量和函数是可以共享的。
4、响应事件
JavaScript 响应操作是通过事件驱动的模式来实现的,由于事件发生的不确定性,所以JavaScript事件响应的顺序也是不确定的。上面的例子说两个代码块之间的先后顺序如果不对会出现问题,但是可以不用改变代码块的顺序,只需要将代码块 2 中的变量和函数的调用代码放到页面初始化函数中,就不会出现错误了。
<script> //JavaScript 代码块 1 window.onload = functiong(){ //页面初始化事件处理函数 alert(a); fun(); } </script> <script> //JavaScript 代码块 2 var a = 1; function fun(){ alert(1); } </script>onload 事件只有在文档加载完毕才会响应。因此为了运行安全,一般都设计在页面初始化完毕之后才允许 JavasScript 代码执行,这样就可以避免因为代码加载延迟对 JavaScript 执行的影响。同时也避开了 HIML 文档流对于 JavaScript 执行的限制。
5、设置动态脚本
使用 document 对象的 write() 方法输出 JavaScript 脚本时,这些动态输出的脚本的执行顺序也不同。JavaScript脚本输出的代码字符串会在输出后马上被执行。
document. write('<script type="text/javascript">'); document. write('f();); document. write(' function f(){'); document, write(' alert(1);'); document. write('}); document. write('<\/script>1);运行代码,document.write() 方法先把输出的 JavaScript 字符串写入到标签所在的文档位置,浏览器在解析完document.write() 所在文档内容后,继续解析 document.write() 输出的内容,然后才按顺序解析后面的HTML文档。
使用 document.write() 方法输的 JavaScript 字符串必须放在同时被输出的 <script> 标签中,否则 JavaScript 解释器因为不能识别这些合法的 JavaScript 代码,而作为普通的字符串显示在页面文档中。