红宝书 第2章 HTML中的JavaScript
在JavaScript早期,网景公司的工作人员希望在将JavaScript引入HTML页面的同时,不会导致页面在其他浏览器中渲染初问题
最终他们达成了向网页中引入通用脚本能力的共识
当初他们的工作得到了保留,并且最终形成了HTML规范
2.1 < script>
元素
将JavaScript插入HTML的主要方法是使用
<script>
元素。这个元素是由网景公司创造出来,并最早在Netscape Navigator2 中实现的。后来,这个元素被正式加入到HTML规范。
- 其有以下8个属性:
属性名 | 状态 | 作用 | 使用 |
---|---|---|---|
async | 可选 | 立即开始下载文件,但不能阻止其他页面动作,比如下载资源或等待其他脚本加载 | 外部脚本 |
charset | 可选 | 使用src属性指定的代码字符集 | 少用,大多浏览器不在乎它的值 |
crossorigin | 可选 | 配置相关请求的CORS(跨源资源共享)设置, crossorigin=“anonymous”配置文件请求不必设置凭据标志 crossorigin=“use-credentials”设置凭据标志,意味着出站请求会包含凭据 | 默认不使用CORS |
defer | 可选 | 表示脚本可以延迟到文档完全被解析和显示之后再执行 | 外部脚本,IE7及更早版本,行内也有效 |
integrity | 可选 | 允许对比接收到的资源和指定的加密签名以验证子资源完整性(SRI,Subresource Integrity),如果接收到的资源的签名与这个属性指定的签名不匹配,则页面会报错,脚本不会执行。这个属性可以用于确保内容分发网络(CDN,Content Delivery Network)不会提供恶意内容 | |
language | 废弃 | 最初用于表示代码块中的脚本语言(如:”JavaScript“、”JavaScript1.2“,”VBScript“) | 大多数浏览器都会忽略这个属性 |
src | 可选 | 表示包含要执行的代码的外部文件 | |
type | 可选 | 代替language,表示代码块中脚本语言的内容类型(MIME类型) |
type中:按照惯例,这个值始终都是”text/javascript“,尽管”text/javascript“和”text/ecmascript“都已经废弃了,
javascript文件的MIME类型通常是”application/x-javascript“,不过给type这个值有可能导致脚本被忽略。
在非IE浏览器中有效的其他值还有”application/javascript“和”application/javascript“。
如果这个值是module,则代码会被当成es6模块,只有这时候代码中才能出现import和export关键字
- 使用
<script>
的方式:
-
通过它直接在网页中嵌入JavaScript代码
- 直接把代码放在
<script>
元素中就行 - 在
<script>
元素中的代码被计算完成前,页面的其余内容不会被加载,也不会被显示 - 注意代码中不能出现
</script>
字符串,否则会报错 - if要避免上述问题,只需转义字符“\”
- 直接把代码放在
-
通过它在网页中包含外部JavaScript文件
- 必须使用src属性
- 与解释行内一样,解释外部JavaScript文件时,页面会阻塞(阻塞时间包含下载文件的时间)
- 在XHTML中,可以
<script src='excample.js' />
,但其是无效的HTML,有些浏览器不能正常处理 - 其url指向的资源可以与其HTML页面不在同一个域中
- 例如
<script src='http://www.somewhere.com/afile.js'></script>
- 浏览器在解析这个资源时,会向src属性指定的路径发送一个GET请求,以取得相应资源
- 这个初始请求不受浏览器同源策略限制
- 返回并被执行的JavaScript受限制
- 这个请求仍然受父页面HTTP/HTTPS协议的限制
- 例如
- 来自外部域的代码会被当成加载它的页面的一部分来加载和解释
- 这个能力可以让我们通过不同的域分发JavaScript
- 若引用了别人服务器上的文件,要确保文件不会被替换
使用了src属性的
<script>
元素不应该在标签中再包含其他JavaScript代码,否则浏览器只会下载执行脚本文件,从而忽略行内代码
2.1.1 标签位置
-
放在
<head></head>
标签内(过去)意味着必须把所有的JavaScript代码都下载、解析和解释完成后,才能开始渲染页面,
若有很多JavaScript文件的页面,会有明显的延迟
-
<body>
元素中的页面内容后面(现代web应用程序)
2.1.2 推迟执行脚本
HTML4.01定义了defer
属性,表示脚本在执行时不会改变页面的结构。即脚本会被延迟到整个页面都解析完毕后再运行
告诉浏览器立即下载,但延迟执行
<head>
<script defer src='example.js'></script>
<!-- if XHTML
defer="defer"
-->
</head>
HTML5规范要求脚本应该按照他们出现的顺序执行,且均会在DOMContentLoaded事件之前执行;
但实际中,推迟执行的脚本不一定总会按照顺序执行或者再DOMContentLoaded事件之前执行;
因此最好只包含一个这样的脚本。
对
defer
属性的支持时从IE4、Firefox3.5、Safari5和Chrome7开始的。其他浏览器会忽略这个属性,所以还是把要延迟的脚本放在页面底部比较好
2.1.3 异步执行脚本
HTML5定义了async
属性,其与defer
类似,都只适用于外部脚本,都会告诉浏览器立即开始下载。
但async
不能保证他们按照他们出现的次序执行
<head>
<script async src='example1.js'></script>
<script async src='example2.js'></script>
<!-- if XHTML
async="async"
-->
</head>
告诉浏览器,不必等脚本下载和执行完后再加载页面,同样也不必等到该异步脚本下载和执行后再加载其他脚本,所以异步脚本不应该在加载期间修改DOM。
异步脚本保证会在load事件前执行,但可能会在DOMContentLoaded之前/后;
使用其也会告诉页面,你不会使用document.write;
Firefox3.6、Safari5和Chrome7支持异步脚本;
2.1.4 动态加载脚本
因为JavaScript可以使用DOM API,所以通过向DOM中动态添加script
元素同样可以加载指定的脚本
let script = document.createElement('script'); //创建
script.src='gibbarish.js';
//默认以这种方式创建的`script`是异步加载的,相当于添加了async,但可能会有问题
//因为所有浏览器支持createElement()方法,但不是所有浏览器都支持async属性
//所以为了统一动态脚本的加载行为,可以明确将其设置为同步加载
script.async=false;
document.head.appendChild(script);//添加到页面上
以这种方式获取的资源对浏览器预加载器是不可见的,会影响它们在资源获取队列中的优先级。根据应用程序的工作方式以及怎么使用,这种方式可能会严重影响性能
要想让预加载器知道这些动态请求文件的存在,可以在文档头部显示声明它们:
<link rel='preload' href='gibberish.js'>
2.1.5 XHTML中的变化
可扩展性文本标记语言(XHTML,Extensible HyperText Markup Language)是将HTML作为XML的应用重新包装的结果。
在XTML中使用JavaScript必须指定type=text/javascript
,HTML则可以没有。
XHTML虽然已经退出历史舞台,但实践中偶尔可能也会遇到遗留代码
在XHTML中编写代码比较严格,
-
在编写JavaScript时,小于号
<
会被解释成一个标签的开始,并且由于作为开始标签的小于号后面不能有空格,所以if(a < b)
这样的代码会报错;以下是避免这种错误的方法:-
把所有小于号
<
都替换成对应的HTML实体形式<
,即if(a < b)
-
把所有代码都包含到一个CDATA块中
在XHTML/XML中,CDATA块表示文档中可以包含任意文本的区块,其内容不作为标签来解析,因此可以在其中包含任意字符
<script type='text/javascript'><![CDATA[ function compare(a , b){ if(a < b){ console.log("a < b"); }... } ]]></script>
在兼容XHTML的浏览器中,这样能解决问题,但在不支持CDATA块的非XHTML兼容浏览器中则不行,
所以CDATA比如使用JavaScript注释来抵消:
<script type='text/javascript'> //<![CDATA[ function compare(a , b){ if(a < b){ console.log("a < b"); }... } //]]> </script>
这种格式使用于所有现代浏览器,它不仅可以通过XHTML验证,而且对XHTML之前的浏览器也能优雅地降级
2.1.6 废弃的语法
-
type属性使用一个MIME类型字符串来标识
<script>
的内容,但MIME类型并没有跨浏览器标准化,即使浏览器默认使用JavaScript,在某些情况下某个无效或无法识别的MIME类型也可能导致浏览器跳过(不执行)相关代码。因此,除非你使用XHTML或<script>
标签要求或包含非JavaScript代码,最佳做法式不指定type类型。 -
在不支持JavaScript的浏览器(特别是Mosaic)中会把
<script>
元素的内容输出到页面上,解决方案是把脚本代码块包含在一个HTML注释中
<script><!-- function sayHi(){ console.log("Hi!"); } //--></script>
使用这种格式,Mosaic等浏览器就可以忽略
<script>
标签中的内容,而支持JavaScript的浏览器则必须识别这种模式,将其中的内容作为JavaScript来解析。但其实这种格式以及不再必要,而且不应该使用了。在XHTML模式下,这种格式会导致脚本被忽略,因为代码处于有效的XML注释当中。
-
2.2 行内代码和外部文件
推荐使用外部文件:
- 可维护性
- 缓存
- 浏览器会根据特定的设置缓存所有外部链接的JavaScript文件
- 若两个页面引用到同一个文件,则该文件只需下载一次,意味着页面加载更快
- 适应未来
- 不必考虑用XHTML或前面提到的注释
- 外部JavaScript的语法在HTML和XHTML中是一样的
在初次请求时,如果浏览器支持SPDY/HTTP2,就可以从一个地方取得一批文件,并将它们逐个放到浏览器缓存中。通过SPDY/HTTP2获取所有这些独立的资源与获取一个大JavaScript文件的延迟差不多。
在第二个页面请求时,由于已经把应用程序切换成了轻量可缓存的文件,第二个页面也依赖的某些组件此时已经存在浏览器缓存中了。
2.3 文档模式
IE5.5发明了文档模式,即可以使用doctype切换文档;
最初的文档模式有两种:混杂模式和标准模式
这两种模式的区别只体现在通过CSS渲染的内容方面,对JavaScript也有一些关联影响,或称为副作用。
后来又出现了第3种文档模式:准标准模式
这种模式下的浏览器支持很多标准的特性,但是没有标准规定的那么严格;
主要区别在如何对待图片元素周围的空白(在表格中使用图片时最明显)
-
混杂模式:在所有浏览器中都以省略文档开头的doctype声明作为开关,这种约定不合理,因为混杂模式在不同浏览器中的差异很大,不使用黑科技基本上就没有浏览器一致性可言。
-
标准模式:通过下列几种文档类型声明开启
-
<!-- HTML 4.01 Strict --> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
-
<!-- XHTML 1.0 Strict --> <!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml-strict.dtd">
-
<!-- HTML5 --> <!DOCTYPE html>
-
-
准标准模式:通过过滤性文档类型(Transitional)和框架集文档类型(Frameset)来触发
-
<!-- HTML 4.01 Transitional --> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
-
<!-- HTML 4.01 Frameset --> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">
-
<!-- XHTML 4.01 Transitional --> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml-transitional.dtd">
-
<!-- XHTML 1.0 Frameset --> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/xhtml1-frameset.dtd">
-
准标准模式和标准模式非常接近,很少需要区分,而本书后面所说的标准模式,指的是除混杂模式之外的模式
2.4 < noscript>
元素
针对早期浏览器不支持JavaScript的问题,需要一个页面优雅降级的处理方案。
<noscript>
元素即用于给不支持JavaScript的浏览器提供替代内容。
虽然先浏览器已经100%支持JavaScript,但对于禁用JavaScript的浏览器来说,这个元素仍然有它的用处
<noscript>
元素可以包含任何可以出现在<body>
中的html元素(除<script>
);
当满足如下任一条件时,浏览器将显示包含在<noscript>
的内容:
- 浏览器不支持脚本
- 浏览器对脚本的支持被关闭
2.5 小结
JavaScript是通过<script>
元素插入到html页面中的:
- 要包含外部JavaScript文件,必须将src属性设置为要包含文件的url。文件可以跟网页在同一台服务器上,也可以位于完全不同的域
- 所有
<script>
元素会依照它们在网页中出现的次序被解释,在不使用defer
和async
时,包含在<script>
的代码必须严格按次序解释 - 对不推迟执行的脚本,浏览器必须解释完位于
<script>
元素的代码,然后才能继续渲染页面的剩余部分。为此,通常把此元素放到页面末尾 - 可以使用
defer
属性把脚本推迟到文档渲染完毕后再执行。推迟脚本的原则上按照次序执行 - 可以使用
async
属性表示不需要等待其他脚本,同时也不阻塞文档渲染,即异步加载。异步脚本不能保证按照次序执行 - 通过使用
<noscript>
元素,可以指定在浏览器不支持脚本时显示的内容。如果浏览器支持,则<noscript>
元素中的任何内容都不会被渲染