第二章 HTML中的JavaScript
2.1
将JavaScript插入HTML的主要方式是使用<script>元素。<script>元素有以下八个属性。
- async:可选。表示应该立即开始下载脚本,但不能阻止其他页面动作,比如下载资源或者等待其他脚本加载。只对外部脚本文件有效。
- charset:可选。使用src属性指定的代码字符集。少用。
- crossorigin:可选。配置相关请求的CORS(跨资源共享)设置。默认不使用CORS。
- defer:可选。表示脚本可以延迟到文档完全被解析和显示置后再执行。只对外部脚本文件有效。
- integrity:可选。允许比对接受到的资源和指定的加密签名以验证子资源完整性。如果接受到的资源的签名与这个属性指定的签名不匹配,则页面会报错,脚本不会执行。这个属性可以用于确保内容分发网络不会提供恶意内容。
- language:废弃。最初用于表示代码块中的脚本语言。大多数浏览器都会忽略这个属性,不应该再使用它。
- src:可选。表示包含要执行的代码块的外部文件。
- type:可选。代替language,表示代码块中脚本语言的内容类型。按照惯例,这个值始终都是text/javascript。
使用<script>的方式有两种:通过它直接在网页中嵌入JavaScript代码,以及通过它在网页中包含外部JavaScript文件。
包含在<script>内的代码会被从上到下解释。在<script>元素中的代码被计算完成之前,页面的其余内容不会被加载,也不会被显示。
要包含外部文件中的JavaScript,就必须使用src属性。这个属性的值是一个URL,指向包含JavaScript代码的文件。比如
<script src="example.js"></script>
与解释行内JavaScript一样,在解释外部JavaScript文件时,页面也会阻塞(阻塞事件也包含下载文件的时间)。在XHTML文档中,可以忽略结束标签,比如:
<script src="example.js /">
注意,按照惯例,外部JavaScript文件的扩展名是.js。这不是必须的,因为浏览器不会检查所包含JavaScript文件的扩展名。这就为使用服务器端脚本语言动态生成JavaScript代码,或者在浏览器中将JavaScript扩展语言(比如TypeScript,或React的JSX)转译为JavaScript提供了可能性。不过服务器经常会根据文件扩展来确定相应的正确MIME类型。如果不打算使用.js扩展名,一定要确保服务器能返回正确的MIME类型。
<script>元素的一个最为强大、同时也备受争议的特性是,它可以包含来自外部域的JavaScript文件。跟<img>元素很像,<script>元素的src属性可以是一个完整的URL,而且这个URL指向的资源可以跟包含它的HTML页面不在同一个域中,比如这个例子:
script src="http://www.somewhere.com/afile.js"></script>
浏览器在解析这个资源时,会向src属性指定的路径发送一个GET请求,以取得相应资源,假定是一个JavaScript文件。这个初始的请求不受浏览器同源策略限制,但返回并被执行的JavaScript则受限制。这个个请求仍然受父页面HTTP/HTTPS协议的限制。
来自外部域的代码会被当成加载它的页面的一部分来加载和解释。这个能力可以让我们通过不同的域分发JavaScript。不过,引用了放在别人服务器上的JavaScript文件时要格外小心,因为恶意的程序员随时可能替换这个文件。在包含外部域的JavaScript文件时,要确保该域是自己所有的,或者该域是一个可信的来源。<script>标签的integrity属性是防范这种问题的一个武器,但这个属性也不是所有浏览器都支持。
不管包含的是什么代码,浏览器都会按照<script>在页面中出现的顺序依次解释它们,前提是它们没有使用defer和async属性。
2.1.1 标签占位符
过去,所有<script>元素都被放在页面的<head>标签内。这种做法的主要目的是把外部的CSS和JavaScript文件都几种放到一起。如:
<!DOCTYPE html>
<html>
<head>
<title>Example HTML Page</title>
<script src="example1.js"></script> //放置于<head>标签中
<script src="example2.js"></script>
</head>
<body>
<!--这里是页面内容-->
</body>
</html>
不过,把所有JavaScript文件都放在<head>里,也就意味着必须把所有JavaScript代码都下载、解析和解释完成后,才能开始渲染页面(页面在浏览器解析到<body>的起始标签时开始渲染)。对于需要很多JavaScript的页面,这会导致页面渲染的明显延迟,在此期间浏览器窗口完全空白。为解决这个问题,现代Web应用程序通常将所有JavaScript引用放在<body>元素中的页面内容后面,如下面的例子所示:
<!DOCTYPE html>
<html>
<head>
<title>Example HTML Page</title>
</head>
<body>
<!--这里是页面内容-->
<script src="example1.js"></script>
<script src="example2.js"></script> //放置于<body>中,页面会在处理JavaScript代码之前完全渲染页面。用户会感觉页面加载更快了,因为浏览器显示空白页面的时间短了。
</body>
</html>
2.1.2 推迟执行的脚本
HTML 4.01为<script>元素定义了一个叫defer的属性。这个属性表示脚本在执行的时候不会改变页面的结构。也就是说,脚本会被延迟到整个页面都解析完毕后再运行。在<script>元素中设置defer属性,相当于告诉浏览器立即下载,但延迟执行。defer属性只对外部脚本才有效。
<!DOCTYPE html>
<html>
<head>
<title>Example HTML Page</title>
<script defer src="example1.js"></script> //立即下载,但延迟执行
<script defer src="example2.js"></script> //虽然包含在页面的<head>中,但它们会在浏览器解析到结束的</html>标签后才会执行
</head>
<body>
<!--这里是页面内容-->
</body>
</html>
2.1.3 异步执行的脚本
HTML5为<script>元素定义了async属性。从改变脚本处理方式上看,async属性与defer类似。当然,它们两者也都只适用于外部脚本,都会告诉浏览器立即开始下载。不过,与defer不同的是,标记为async的脚本并不保证能按照它们出现的次序执行,比如上个例子中,examle2.js可能比第一个脚本先执行。重点在于它们之间没有依赖关系。给脚本添加async属性的目的是告诉浏览器,不必等脚本下载和执行完后再加载页面,同样也不必等到该异步脚本下载和执行后再加载其他脚本。
2.1.4 动态加载脚本
除了<script>标签,还有其他方式可以加载脚本。因为JavaScript可以使用DOM API,所以通过向DOM中动态添加script元素同样可以加载指定的脚本。只要创建一个script元素并将其添加到DOM即可。
2.1.5 XHTML中的变化
可扩展超文本标记语言(XHTML,Extensible HyperText Markup Language)是将HTML作为XML的应用重新包装的结果。
2.2 行内代码与外部文件
通常认为最佳实践是尽可能将JavaScript代码放在外部文件中。不过这个最佳实践并不是明确的强制性规则。推荐使用外部文件的理由如下。
- 可维护性 JavaScript代码如果分散到很多HTML页面,会导致维护困难。而用一个目录保存所有JavaScript文件,则更容易维护,这样开发者就可以独立于使用它们的HTML页面来编辑代码。
- 缓存 浏览器会根据特定的设置缓存所有外部链接的JavaScript文件,这意味着如果两个页面都用到同一个文件,则该文件只需下载一次。这最终意味着页面加载更快。
- 适应未来 通过把JavaScript放到外部文件中,就不必考虑用XHTML或前面提到的注释黑科技。包含外部JavaScript文件的语法在HTML和XHTML中是一样的。
2.3 文档模式
IE5.5发明了文档模式的概念,即可以使用doctype切换文档模式。
2.4 元素
针对早期浏览器不支持JavaScript的问题,需要一个页面优雅降级的处理方案。最终,<noscript>元素出现,被用于给不支持JavaScript的浏览器提供替代内容。
2.5 小结
JavaScript是通过<script>元素插入到HTML页面中的。
- **要包含外部JavaScript文件,必须将src属性设置为要包含文件的URL。**文件可以跟网页在同一台服务器上,也可以位于完全不同的域。
- **所有<script>元素会依照它们在网页中出现的次序被解释。**在不使用defer和async属性的情况下,包含在<script>元素中的代码必须严格按次序解释。
- 对不推迟执行的脚本,浏览器必须解释完位于<script>元素中的代码,然后才能继续渲染页面的剩余部分。为此,通常应该把<script>元素放到页面末尾,介于主内容之后及</body>标签之前。
- 可以使用defer属性把脚本推迟到文档渲染完毕后再执行。推迟的脚本原则上按照它们被列出的次序执行。
- 可以使用async属性表示脚本不需要等待其他脚本,同时也不阻塞文档渲染,即异步加载。异步脚本不能保证按照它们在页面中出现的次序执行。
- 通过使用<noscript>元素,可以指定在浏览器不支持脚本时显示的内容。如果浏览器支持并启用脚本,则<noscript>元素中的任何内容都不会被渲染。