浏览器模型
浏览器环境概述
JavaScript 是浏览器的内置脚本语言。也就是说,浏览器内置了 JavaScript 引擎,并且提供各种接口,让 JavaScript 脚本可以控制浏览器的各种功能。一旦网页内嵌了 JavaScript 脚本,浏览器加载网页,就会去执行脚本,从而达到操作浏览器的目的,实现网页的各种动态效果。
这里开始介绍浏览器提供的各种 JavaScript 接口。首先,介绍 JavaScript 代码嵌入网页的方法
代码嵌入网页的方法
网页中嵌入 JavaScript 代码,主要有四种方法:
script 元素嵌入代码
<script>
var x = 1 + 5;
console.log(x);
</script>
1.text/javascript:这是默认值,也是历史上一贯设定的值。如果你省略type属性,默认就是这个值。对于老式浏览器,设为这个值比较好
2.application/javascript:对于较新的浏览器,建议设为这个值
由于
<script id="mydata" type="x-custom-data">
console.log('Hello World');
</script>
浏览器不会执行也不会显示内容,因为不认识它的type属性;但是,这个
document.getElementById('mydata').text // console.log('Hello World');
script 元素加载外部脚本
<script src="https://www.example.com/script.js"></script>
如果脚本文件使用了非英语字符,还应该注明字符的编码
<script charset="utf-8" src="https://www.example.com/script.js"></script>
所加载的脚本必须是纯的 JavaScript 代码,不能有HTML代码和
<script charset="utf-8" src="example.js">
console.log('Hello World!');
</script>
为了防止攻击者篡改外部脚本,script标签允许设置一个integrity属性,写入该外部脚本的 Hash 签名,用来验证脚本的一致性
<script src="/assets/application.js" integrity="sha256-TvVUHzSfftWg1rcfL6TIJ0XKEGrgLyEq6lEpcmrG9qs="></script>
上面代码中,script标签有一个integrity属性,指定了外部脚本/assets/application.js的 SHA256 签名。一旦有人改了这个脚本,导致 SHA256 签名不匹配,浏览器就会拒绝加载
事件属性
网页元素的事件属性(比如onclick和onmouseover),可以写入 JavaScript 代码。当指定事件发生时,就会调用这些代码
<button id="myBtn" onclick="console.log(this.id)">点击</button> //如果有多个语句,使用分号分隔即可
URL 协议
URL 支持javascript:协议,即在 URL 的位置写入代码,使用这个 URL 的时候就会执行 JavaScript 代码
<a href="javascript:console.log('Hello')">点击</a>
浏览器的地址栏也可以执行javascript:协议。将javascript:console.log('Hello')放入地址栏,按回车键也会执行这段代码;如果 JavaScript 代码返回一个字符串,浏览器就会新建一个文档,展示这个字符串的内容,原有文档的内容都会消失
<a href="javascript: new Date().toLocaleTimeString();">点击</a>
上面代码中,用户点击链接以后,会打开一个新文档,里面有当前时间;如果返回的不是字符串,那么浏览器不会新建文档,也不会跳转
<a href="javascript: console.log(new Date().toLocaleTimeString())">点击</a>
javascript:协议的常见用途是书签脚本 Bookmarklet。由于浏览器的书签保存的是一个网址,所以javascript:网址也可以保存在里面,用户选择这个书签的时候,就会在当前页面执行这个脚本。为了防止书签替换掉当前文档,可以在脚本前加上void,或者在脚本最后加上void 0
<a href="javascript: void new Date().toLocaleTimeString();">点击</a>
<a href="javascript: new Date().toLocaleTimeString();void 0;">点击</a>
script 元素
工作原理
浏览器加载 JavaScript 脚本,主要通过
1.浏览器一边下载 HTML 网页,一边开始解析;也就是说,不等到下载完,就开始解析
2.解析过程中,浏览器发现
3.如果
4.JavaScript 引擎执行完毕,控制权交还渲染引擎,恢复往下解析 HTML 网页
加载外部脚本时,浏览器会暂停页面渲染,等待脚本下载并执行完成后再继续渲染;原因是 JavaScript 代码可以修改 DOM,所以必须把控制权让给它,否则会导致复杂的线程竞赛的问题。如果外部脚本加载时间很长(一直无法完成下载),那么浏览器就会一直等待脚本下载完成,造成网页长时间失去响应,浏览器就会呈现“假死”状态,这被称为“阻塞效应”。为了避免这种情况,较好的做法是将
<head>
<script>
console.log(document.body.innerHTML);
</script>
</head>
<body></body>
上面代码执行时会报错,因为此时document.body元素还未生成
一种解决方法是设定DOMContentLoaded事件的回调函数
<head>
<script>
document.addEventListener(
'DOMContentLoaded',
function (event) {
console.log(document.body.innerHTML);
}
);
</script>
</head>
DOMContentLoaded事件只有在 DOM 结构生成之后才会触发
另一种解决方法是使用
<script src="jquery.min.js" onload="console.log(document.body.innerHTML)"></script>
但是,如果将脚本放在页面底部,就可以完全按照正常的方式写,上面两种方式都不需要。如果有多个script标签,比如下面这样:
<script src="a.js"></script>
<script src="b.js"></script>
浏览器会同时并行下载a.js和b.js,但是,执行时会保证先执行a.js,然后再执行b.js,即使后者先下载完成,也是如此。也就是说,脚本的执行顺序由它们在页面中的出现顺序决定,这是为了保证脚本之间的依赖关系不受到破坏。当然,加载这两个脚本都会产生“阻塞效应”,必须等到它们都加载完成,浏览器才会继续页面渲染。解析和执行 CSS,也会产生阻塞。Firefox 浏览器会等到脚本前面的所有样式表,都下载并解析完,再执行脚本;Webkit则是一旦发现脚本引用了样式,就会暂停执行脚本,等到样式表下载并解析完,再恢复执行。
此外,对于来自同一个域名的资源,比如脚本文件、样式表文件、图片文件等,浏览器一般有限制,同时最多下载6~20个资源,即最多同时打开的 TCP 连接有限制,这是为了防止对服务器造成太大压力。如果是来自不同域名的资源,就没有这个限制。所以,通常把静态文件放在不同的域名之下,以加快下载速度
defer 属性
为了解决脚本文件下载阻塞网页渲染的问题,一个方法是对
<script src="a.js" defer></script>
<script src="b.js" defer></script>
上面代码中,只有等到 DOM 加载完成后,才会执行a.js和b.js
defer属性的运行流程如下:
1.浏览器开始解析 HTML 网页
2.解析过程中,发现带有defer属性的
3.浏览器继续往下解析 HTML 网页,同时并行下载
4.浏览器完成解析 HTML 网页,此时再回过头执行已经下载完成的脚本
有了defer属性,浏览器下载脚本文件的时候,不会阻塞页面渲染。下载的脚本文件在DOMContentLoaded事件触发前执行(即刚刚读取完