之前学习的JavaScript知识总体来说还是太浅了,一些代码甚至看不太懂,更别提去写出质量高的JavaScript代码。这次的笔记主要是根据《JavaScript高级程序设计》和《JavaScript Dom编程艺术》中的知识来阐述最近学到的知识和自己的见解。
第一章首先讲述的是JavaScript的发展,在《JavaScript高级程序设计》第三版出版时还没有诞生ES6的语法,现在ES6却已经是使用非常普遍。
文档对象模型(DOM)将 HTML 文档表达为树结构,每一个页面的组成部分都是一个DOM节点,开发者通过DOM可以访问甚至是操作HTML 元素的对象和属性。
书上有这样一段话:
包含在元素内部的JavaScript代码将从上至下依次解释…解释器会解释一个函数的定义,然后将该定义保存在自己的环境中。在解释器对
<script>
元素内部的所有代码求值完毕之前,页面中其余内容都不会被浏览器加载或显示。
先看看代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>1</title>
</head>
<script>
let changeToRed = function() {
console.log("33333333");
document.getElementById("red").style.color = "red";
};
let changeToGreen = function() {
console.log("44444444");
document.getElementById("green").style.color = "green";
};
console.log("1111111");
setTimeout(changeToRed, 5000);
setTimeout(changeToGreen,3000)
console.log("222222");
</script>
<body>
<p>这是正常文字</p>
<p id="red">这是红色的文字</p>
<p id="green">这是绿色的文字</p>
<p>这是最后文字</p>
</body>
</html>
上面的代码,我对它预期的执行顺序是:
界面: 这是正常文字(黑色) --> 这是红色的文字(先是黑色,5秒后变红色) --> 这是绿色的文字(先是黑色,再过3秒后变绿色)–> 这是最后文字
控制台:1111111 --> 33333333 --> 44444444 --> 22222222
实际上:
界面: 这是正常文字(黑色) , 这是红色的文字(黑色) 、 这是绿色的文字(黑色)、这是最后文字(黑色)
–> 这是绿色的文字 约3秒后变绿色 --> 这是红色的文字再过约2秒后变红色;
控制台:1111111 、2222222 --(约3秒后)–> 44444444 --(约2秒后)–> 3333333
可能是我对前面那段话还有JavaScript的定时机制和页面解析顺序有点误解。
带着问题继续探索。
为了进行对比验证,不妨再运行如下代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>2</title>
<script type="text/javascript">
let changeToRed = function() {
console.log("33333333");
document.getElementById("red").style.color = "red";
};
let changeToGreen = function() {
console.log("44444444");
document.getElementById("green").style.color = "green";
};
setTimeout(changeToRed, 5000);
console.log("55555555");
</script>
</head>
<script>
console.log("1111111");
setTimeout(changeToGreen,3000)
console.log("222222");
</script>
<body>
<p>这是正常文字</p>
<p id="red">这是红色的文字</p>
<p id="green">这是绿色的文字</p>
<p>这是最后文字</p>
</body>
</html>
这段代码的运行结果为:
界面: 这是正常文字(黑色) , 这是红色的文字(黑色) 、 这是绿色的文字(黑色)、这是最后文字(黑色)
–> 这是绿色的文字3秒后变绿色 --> 这是红色的文字再过约2秒后变红色;
控制台:55555555、1111111 、2222222 --(约3秒后)–> 44444444 --(约2秒后)–> 3333333
这个时候我们可以明确的是,对于<html>
标签内的代码,会从上到下解析。对于<script>
标签内的代码,也是按顺序从上到下进行解释。那么页面渲染和JavaScript代码的执行的顺序又是怎么样的呢?
再看下面的代码:
<!DOCTYPE html>
<html lang="en">
<head onload="console.log('66666666');">
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>3</title>
</head>
<script>
let changeToRed = function() {
console.log("33333333");
document.getElementById("red").style.color = "red";
};
let changeToGreen = function() {
console.log("44444444");
document.getElementById("green").style.color = "green";
};
console.log("1111111");
setTimeout(changeToGreen,3000)
console.log("222222");
setTimeout(changeToRed, 5000);
console.log("55555555");
</script>
<body onload="console.log('77777777');">
<p>这是正常文字</p>
<p id="red">这是红色的文字</p>
<p id="green">这是绿色的文字</p>
<p>这是最后文字</p>
</body>
</html>
这段代码的运行结果为:
界面: 这是正常文字(黑色) , 这是红色的文字(黑色) 、 这是绿色的文字(黑色)、这是最后文字(黑色)
–> 这是绿色的文字3秒后变绿色 --> 这是红色的文字再过约2秒后变红色;
控制台:1111111 、2222222 、55555555、77777777 --(约3秒后)–> 44444444 --(约2秒后)–> 3333333
可以看到,JavaScript代码中直接输出在控制台的语句总是最先执行。你也可能会说:“当然啊!因为你的<script>
放在了<body>
前面呀!…”
…
我测试了一下,把<script>
代码换到<body>
标签之后,或者是<body>
标签内部,甚至是<head>
,结果都是一样的,所以,浏览器加载界面时,总是会先去解释<script>
中的代码。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>4</title>
</head>
<body onload="let temp = '22222222'">
</body>
<script>
console.log(temp);
</script>
</html>
运行:Uncaught ReferenceError: temp is not defined at 4.html:12
上面这两个例子则更直接地证实了之前的结论。
但是,对于之前那一段代码,为什么同样在<script>
中输出到控制台的33333333和44444444却在加载<body>
之后才执行呢?为什么执行完变绿的样式之后,只隔了2秒而不是5秒就执行变红样式了呢?
不妨先来看看JavaScript的定时器机制。
JavaScript定时器与执行
JavaScript可以通过setTimeout()
来实现定时操作。
setTimeout(code,millisec,lang)
:在指定的毫秒数之后执行函数或计算表达式。
参数 | 描述 |
---|---|
code | 必需。等待执行的 JavaScript 代码。 |
millisec | 必需。在执行代码前需等待的毫秒数。 |
lang | 可选。脚本语言可以是:JScript /VBScript/ JavaScript。 |
JavaScript的定时器不是一个线程,因为JavaScript是运行于单线程环境中的,而定时器只是计划代码在未来的某个时间执行。可以这么理解:浏览器负责进行排序,指派某段代码在某个时间点应该按照什么样的顺序执行。对于指定的时间间隔,它表示的是何时将定时器的代码添加到执行队列,而不是何时执行代码。
在其他地方看到了很多解释关于浏览器进程的一些理解,其中包括浏览器进程是多线程,包括了GUI渲染线程、JS引擎线程、事件触发线程、定时触发器线程、异步http请求线程等主要线程。但我更倾向于《JavaScript高级程序设计》这本书中的说法(或许是比较权威吧)。
再结合前面的代码,可以这么理解:浏览器首先把解析JavaScript中的代码放到任务列表,另外,渲染界面(即解析`` 等其中的代码)也被放入队列。队列中的任务都要等待JavaScript进程空闲之后才能执行,并且是按照顺序执行。 首先是解析JavaScript代码,当解析到setTimeout(changeToGreen,3000);
这一行时,本应等待3秒之后执行这行代码,但是JavaScript进程这个时候是空闲状态,因此会继续向下执行解析,解析完之后,如果还是空闲,就执行完全部的JavaScript代码,并且开始渲染页面。而在3秒钟之后,将这个任务放入任务队列。当解析到setTimeout(changeToRed, 5000);
也一样,在5秒后将这个任务放入队列。解析JavaScript代码和渲染页面的速度很快,所以几乎是从加载HTML页面开始,第3秒后放入任务队列,此时任务队列早已为空,JavaScript进程会立刻执行这一任务。
总结
- 对于
<html>
标签内的代码,会从上到下解析。
首先是<header>
标签中的代码,head标签中可能会包含对外部文件的引用,从开始运行就会下载这些被引用的外部文件。然后解析<body>
标签中的代码,如果此时``中引用的外部文件没有下载完,将会继续下载。
- 对于
<script>
标签内的代码,其按照从上之下的顺序解释。`` 标签可以出现在HTML页面的任何位置。浏览器将控制权交给JavaScript的解释器,如果<script>
标签引用了外部脚本文件,就下载该脚本,否则就直接执行,执行完毕后将控制权返回,浏览器继续渲染页面。由于是单线程,在渲染界面的时候不会执行JS代码,在执行JS代码的时候也不会渲染界面。
欢迎批评指正!