加载顺序
JavaScript 的加载顺序在浏览器中是从上到下、从左到右的,也就是按照 HTML 文档中<script>
标签的出现的顺序进行逐个加载和执行。此外,当遇到<script>
标签中的defer
或async
属性时,也会影响 JavaScript 脚本的加载与执行顺序。
defer
:表示该脚本在 HTML 文档中的其他元素加载完毕之后再进行加载,也就是等到整个页面被浏览器解析完毕之后才会执行。该属性只对外部脚本文件有效,内部脚本无效。多个带有defer
属性的脚本按照在 HTML 文档中出现的顺序进行加载和执行。async
:表示该脚本是异步加载的,也就是在 HTML 文档解析过程中不会对其他元素的加载和解析进行阻塞。当该脚本加载完成之后,会立刻被执行,不保证按照在 HTML 文档中的顺序来执行。多个带有async
属性的脚本也不保证按照在 HTML 文档中出现的顺序进行加载和执行。
在实际开发中,我们应该根据页面的实际情况,遵循以下原则来进行 JavaScript 脚本的加载:
- 通常将脚本文件的
<script>
标签放在页面</body>
标签前,可以避免阻塞页面的渲染,提高页面的加载速度; - 尽可能使脚本文件较小,减少加载时间;
- 对于不影响页面渲染或交互的功能,可以采用异步加载的方式,提高页面响应速度和交互体验;
- 对于同时需要执行的多个 JavaScript 脚本文件,可以将它们合并为一个较大的文件,减少 HTTP 请求次数,提高页面加载速度。
执行原理
JavaScript 代码的执行过程分为两个阶段:解析阶段和执行阶段。
-
解析阶段:该阶段主要完成的工作是将代码转换成抽象语法树(AST),即将代码解析成可执行的指令序列。在解析阶段中,JavaScript 引擎负责对变量、函数声明等进行预处理,将其进行存储,以供之后的执行阶段调用。同时在解析阶段中,也会对脚本文件进行语法检查,确保代码的正确性。
-
执行阶段:该阶段主要完成的工作是按照解析阶段生成的指令序列进行逐条执行,并生成最终的运行结果。在执行阶段中,JavaScript 引擎会从函数调用栈(call stack)中获取下一条指令并执行,同时还会通过作用域链(scope chain)等内部机制进行变量访问和函数调用等操作。
在实际开发中,我们需要优化 JavaScript 代码的执行效率,可以采用以下方式:
- 减少全局变量的使用,避免变量名和函数名的重复,以提高变量和函数的查找速度;
- 将多次使用的变量和函数进行缓存,避免重复计算和查找;
- 避免在 JavaScript 循环中频繁地操作 DOM 元素,避免页面的重绘,以提高页面渲染效率;
- 对于与用户交互相关的 JavaScript 代码,可以采用事件委托(event delegation)的方式来减少 DOM 操作次数,提高页面响应速度和交互体验。
示例一:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>defer示例</title>
</head>
<body>
<script src="a.js" defer></script>
<script src="b.js" defer></script>
</body>
</html>
在上述代码中,分别引入了a.js
和b.js
文件,并设置了defer
属性。由于在 HTML 文档中出现的顺序是a.js
、b.js
,因此这两个文件会按照这个顺序分别进行异步加载。当 HTML 文档加载完成之后,这两个文件的执行顺序是先执行a.js
,再执行b.js
。
示例二:
// 定义一个全局变量
var COUNT = 0;
// 循环一亿次计算全局变量COUNT的值
for (var i = 0; i < 100000000; i++) {
COUNT++;
}
console.log(COUNT);
上述代码中,定义了一个名为COUNT
的全局变量,并使用循环计算COUNT
的值。在循环中,每次对COUNT
进行自增操作,会对全局作用域链进行访问和修改,因此会较慢。如果需要优化执行效率,可以采用将COUNT
变量进行缓存的方式,减少对全局作用域链的访问次数。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:高性能的javascript之加载顺序与执行原理篇 - Python技术站