橙子

html, css, js 加载顺序

我们看到的页面是已经绘制完成的,html,css, js 这些文件当然是已经加载完成了的,但这个加载过程中总有个加载的先后顺序的,嗯,还是很重要的,这里就是总结这个加载过程及顺序

html css 加载

假设现在有个页面 timeLine.html :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<title>时间线</title>
<style type="text/css">
div {
width: 100px;
height: 100px;
background: blue;
}
</style>
</head>

<body>
<div>
<strong></strong>
</div>
<span></span>
<p></p>

</body>

</html>

这个页面很简单,它绘制的过程是这样的:

​ 在页面绘制过程中,遵守的是 深度优先原则,即一条道走到黑。首先,会形成一个 domTree ,它会把遇到的所有 dom 节点都挂到这个树上,这叫 dom 节点的解析。在这个 timeLine.html 中,它会解析到 head 标签,挂到 domTree ,然后再将它下面的 meta title 标签挂到树上,当把head里的标签都解析完成后,再去解析body 标签,这个解析过程就是符合 深度优先 的原则。cssTree 同样是这样解析的。当 domTree 解析完毕后 等待 cssTree 的解析,当 cssTree 也解析完毕后,这两个合在一起就形成了 renderTree,这个时候才开始按照 renderTree 的规则来绘制页面

​ 注意:domTree 解析完毕的过程只是说把 dom 节点都挂到树上了,但不代表它加载完成,假设现在有个 img 标签,只会挂上这个 img 节点,至于它是什么,并不影响 domTree 的形成,这个是异步的过程

异步加载js

上面的这个页面是没有 js 的,js 的加载和html css 不一样,它会阻断 当前的加载过程,等它js加载完后,才可以继续下面的过程,并不能和html css 并行加载,因为 js 能修改html css。

1
<script type="text/javascript"></script>

但有些 js 是用来初始化数据或是工具,这些js不会修改 html css ,而这些js 就可以异步加载,甚至是按需加载,那方法有三种:

1 添加 defer,加上后就变成异步加载了,但是:只有 ie 能用(ie9及以下),且代码还可以写到内部,哪怕已经下载完成了也要等页面解析完毕(形成renderTree后)才执行,异步加载

1
<script type="text/javascript" src="tool.js" defer="defer"></script>

2 async W3C标准,ie9以上,这个是加载完就使用,不能在标签里写代码,异步加载

1
<script type="text/javascript" src="tool.js" async="async"></script>

3 创建script标签,插入到DOM中,加载完毕后 callback

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<body>
<div>
<strong></strong>
</div>
<span></span>
<p></p>

<script type="text/javascript">
var script = document.createElement('script')
script.type = 'text/javascript'
script.src = './src/index.js'

document.head.appendChild(script)
</script>

</body>

// ./src/index.js

function test () {
console.log('时间线')
}

如上:创建 script 标签,给它设置 src 属性,当这句执行完毕后就开始异步下载这个js文件了,但是它只是下载了并没有什么用,只有执行 document.head.appendChild(script) 后才可以用这里面的方法

但此时若是直接在插入语句之后执行 test 方法的话,是报错的,因为src这句是在异步加载文件,所以要执行这个方法需要js文件加载完成后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<script type="text/javascript">
var script = document.createElement('script')
script.type = 'text/javascript'

document.head.appendChild(script)

if (script.readyState) {
script.onreadystatechange = function () {
if (script.readyState === 'complete' || script.readyState === 'loaded') {
test()
}
}
} else {
script.onload = function () {
test()
}
}

script.src = './src/index.js'

</script>

注意:

1 由于 ie10及以下 的 script 标签没有 onload ,但它有自己的 readyState ,所以两者这样兼容一下

2 把src这句移到绑定事件之后 ,目的是以防文件以加载完毕了,但是还没执行到 script.readyState这句

把上面这个方法封装一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<script type="text/javascript">

function loadScript(url, callback) {
var script = document.createElement('script')
script.type = url
document.head.appendChild(script)
if (script.readyState) {
script.onreadystatechange = function () {
if (script.readyState === 'complete' || script.readyState === 'loaded') {
callback()
}
}
} else {
script.onload = function () {
callback()
}
}
script.src = './src/index.js'
}
loadScript('text/javascript', test)
</script>

但是:报错了,报错原因很简单,执行 loadScript() 这句时压根不知道test是谁啊,解决办法有很多,这里写三个(目前能想到这三个哈哈哈)

1 传字符串进去:

loadScript('text/javascript', 'test()')

调用:eval(callback)

尽量不要使用!

2 传匿名函数进去:

loadScript('text/javascript', function () {test()})

调用:callback()

3 直接传字符串进去,并修改 js 文件:

loadScript('text/javascript', 'test')

src/index.js:

1
2
3
4
5
var obj = {
test: function test() {
console.log('时间线')
}
}

调用:obj[callback]()

当然,若不需要回调,只需要异步加载js文件,那就灵活使用

js加载时间线

1、创建Document对象,开始解析web页面。解析HTML元素和他们的文本内容后添加Element对象和Text节点到文档中。这个阶段document.readyState = ‘loading’。

2、遇到link外部css,创建线程加载,并继续解析文档。

3、遇到script外部js,并且没有设置async、defer,浏览器加载,并阻塞,等待js加载完成并执行该脚本,然后继续解析文档。

4、遇到script外部js,并且设置有async、defer,浏览器创建线程加载,并继续解析文档。
对于async属性的脚本,脚本加载完成后立即执行。(异步禁止使用document.write())

5、遇到img等,先正常解析dom结构,然后浏览器异步加载src,并继续解析文档。

6、当文档解析完成,document.readyState = ‘interactive’。

7、文档解析完成后,所有设置有defer的脚本会按照顺序执行。(注意与async的不同,但同样禁止使用
document.write());

8、document对象触发DOMContentLoaded事件,这也标志着程序执行从同步脚本执行阶段,转化为事件驱动阶段,即开始识别事件了。DOMContentLoaded这个事件在dom解析完就可以触发,不用等到加载完再去执行

9、当所有async的脚本加载完成并执行后、img等加载完成后,document.readyState = ‘complete’,window对象触发load事件。

10、从此,以异步响应方式处理用户输入、网络事件等。