<script> 和 <noscript>

87 阅读9分钟

1. 元素

HTML 中的 <script>元素是网页添加动态行为和交互功能的主要工具,<script>允许嵌入或引用JavaScript代码。

1.1 元素的用法

使用<script>的方式有两种:通过它直接在网页中嵌入JavaScript代码,以及通过它在网页中包含外部JavaScript文件

1. 内联脚本:将 JavaScript 代码直接写在 <script>标签内。

<script>中的代码会从上到下解释,在<script>元素中的代码被计算完成之前,页面的其余内容不会被加载,也不会被显示。

<script> 
 function sayScript() { 
     console.log("script"); 
 } 
</script>

2. 外部脚本:通过 src属性引用外部的 .js文件。

与解释行内代码一样,在解释外部 JavaScript 文件时,页面也会阻塞,阻塞时间也包含下载文件的时间。

<script src="example.js"></script>

使用了 src 属性的 <script>元素不应该在 <script>和标签中再包含其他 JavaScript 代码。

如果行内代码和src属性同时存在,浏览器会忽略行内代码,只下载并执行src属性中的脚本文件。

1.2 元素属性

属性说明
src指定外部脚本文件的URL。
type指定脚本语言的内容类型(MIME类型)。通常是"text/javascript"(HTML5中可省略)。如果值为modele,代码会被当成ES6模块处理,这时代码中可允许使用import和export关键字。
async异步加载(只对外部脚本有效) 。浏览器会立即开始下载脚本,但不会阻止其他页面动作,比如下载资源或等待其他脚本加载,即并行下载脚本,下载完成后立即暂停HTML解析并执行该脚本。多个async脚本不保证能按照它们出现的次序执行。
defer延迟执行(只对外部脚本有效), 最好只包含一个defer脚本。表示浏览器会并行下载脚本,defer脚本会延迟到HTML文档完全被解析和显示之后再按顺序执行,通常在DOMContentLoaded事件之前执行 ****。
integrity子资源完整性(SRI)校验。 比对接收到的资源的哈希值和指定的加密签名是否匹配,如果不匹配,则页面会报错,脚本不会执行。这个属性可以用于确保内容分发网络(CDN)不会被篡改而提供恶意内容。
crossorigin配置相关跨域请求的CORS(跨源资源共享)设置,经常用于处理跨域脚本的错误日志记录。默认不使用CORS。属性值"anonymous" 配置文件请求不必设置凭据标志。属性值“use-credentials"设置凭据标志,意味着出站请求会包含凭据。
nomodule布尔值,用于在不支持ES6模块的旧浏览器中提供回退脚本。

1.2.1 defer 推迟执行脚本


**defer**属性表示脚本会被延迟到整个页面都解析完毕后再运行。因此,在<script>元素上设置 defer 属性,相当于告诉浏览器立即下载,但延迟执行。


<!DOCTYPE html>  
<html>  
 <head>  
 <title>Example HTML Page</title>  
 
<script defer src="example1.js"></script>  
<script defer src="example2.js"></script>  
 </head>  
 <body>  
 <!-- 这里是页面内容 -->  
 </body>  
</html> 

虽然HTML5规范中要求脚本按顺序执行,但实际上,推迟执行的脚本不一定按顺序执行或在DOMContentLoaded事件之后执行,所以建议最好只对一个脚本设置**defer**属性。

defer 属性只对外部脚本文件才有效。这是 HTML5 中明确规定的,因此支持 HTML5 的浏览器会忽略行内脚本的 defer 属性。

早期的浏览器可能不支持defer属性, 还是把要推迟执行的脚本放在页面底部比较好。

1.2.2 async 异步执行脚本


从改变脚本处理方式上看,async 属性与 defer 类似。同样,async属性也只适用于外部脚本,它们都会告诉浏览器立即开始下载。不过,不同的是, async属性并不保证能按照它们出现的次序执行,所以重点在于它们之间没有依赖关系

异步脚本保证会在页面的 load 事件前执行,但可能会在 DOMContentLoaded之前或之后。


<!DOCTYPE html>  
<html>  
 <head>  
 <title>Example HTML Page</title>  
<script async src="example1.js"></script>  
<script async src="example2.js"></script>  
 </head>  
 <body>  
 <!-- 这里是页面内容 -->  
 </body>  
</html> 

给脚本添加 async 属性的目的是,告诉浏览器不必等脚本下载和执行完后再加载页面,同样也不必等到异步脚本下载和执行后再加载其他脚本。因此异步脚本不应该在加载期间修改 DOM。

1.3 动态加载脚本

使用DOM API,通过向DOM中动态添加script元素同样可以加载指定的脚本。以这种方式创建的

let script = document.createElement('script'); 
script.src = 'gibberish.js'; 
script.async = false; //不是所有浏览器都支持 async 属性。因此,如果要统一动态脚本的加载行为,可以明确将其设置为同步加载
document.head.appendChild(script);

以这种方式获取的资源对浏览器预加载器是不可见的,这会严重影响它们在资源获取队列中的优先级,可能会严重影响性能。要想让预加载器知道这些动态请求文件的存在,可以在文档头部显式声明它们:

<link rel="preload" href="gibberish.js">

注意事项:

1. 优先异步加载:使用 async 或 defer 属性避免阻塞渲染。

script.async = true; // 异步加载,不阻塞页面

2. 错误处理与重试:监听 onerror 事件并实现重试机制:

script.onerror = () => {
  console.error('加载失败,尝试重试...');
  setTimeout(() => loadScript(url), 2000);
};

3. 缓存策略:添加时间戳或版本号避免缓存旧脚本:

script.src = `${url}?v=${Date.now()}`;

4. 结合现代工具链: 使用 Webpack 的动态导入(Dynamic Import)或 ES Modules 实现标准化动态加载

import('module.js').then(module => {
  module.init();
});
  • 推荐使用场景:按需加载、模块化架构、性能敏感型页面。

  • 不推荐场景:简单页面、无明确性能需求的小型项目。

  • 关键权衡:在灵活性与维护成本之间取得平衡,优先使用框架(如 Vue/React)的官方动态加载方案

1.4 浏览器如何加载

浏览器会按照<script>在页面中出现的顺序依次解释它们,前提是它们没有使用 defer  async 属性。第二个<script>元素的代码必须在第一个<script>元素的代码解释完毕才能开始解释,第三个则必须等第二个解释完,以此类推。

  1. 解析 HTML → 遇到 <script> → 暂停解析。
  2. 下载脚本 → 执行脚本 → 恢复解析。
  3. 重复上述过程,直到所有同步脚本处理完毕。
  4. HTML 完全解析后触发 DOMContentLoaded
脚本类型加载行为执行时机对 DOMContentLoaded 的影响
同步脚本阻塞解析,顺序加载执行立即执行必须等待所有同步脚本执行完成才触发
async 脚本异步加载,不阻塞解析下载完成后立即执行(无序)不阻塞触发,但可能在事件前后执行
defer 脚本异步加载,不阻塞解析DOM 解析完成后按顺序执行等待其执行完成才触发(但比同步脚本更晚)

扩展: load 事件与 DOMContentLoaded 的区别?

页面执行顺序:DOM 树解析完成->DOMContentLoaded->所有资源(图片、js、css等)加载完成-> load``。 可以发现,**DOMContentLoaded更早,而load**更晚。

从性能优化角度建议:

  • 优先使用 DOMContentLoaded : 尽早执行 DOM 操作(比如表单验证、动态绑定事件等),减少用户等待时间。
  • 避免同步脚本阻塞解析 :和<style>不同,<script>默认阻塞DOM的解析,所以使用 async 或 defer 加载脚本,能减少对 DOMContentLoaded 的延迟。
  • 按需使用 load :仅在必须操作完整资源时使用,避免不必要的性能损耗。
// 监听 DOMContentLoaded
document.addEventListener("DOMContentLoaded", () => {
  console.log("DOM 已就绪,可操作元素");
});

// 监听 load
window.addEventListener("load", () => {
  console.log("所有资源加载完成");
});

1.5标签位置

过去,所有的<scirpt>元素都放在页面的<head>标签中,目的是把外部CSS文件和JavaScript文件集中在一起

JavaScript解析阻塞资源,会暂停 DOM 构建和渲染。上面这种做法也就意味着必须等到所有 JavaScript代码都下载、解析和解释完成之后,才能开始渲染(浏览器解析到<body>标签时开始渲染)页面,这就会导致页面渲染明显延迟,这个过程中浏览器窗口会处于空白。

所以,现代Web应用程序通常会把所有JavaScript引用放在<body>元素内容的最后面以减少阻塞。这样,页面会在处理JavaScript代码前完成渲染,用户会感觉页面加载的更快。

<!DOCTYPE html>  
<html>  
 <head>  
 <title>Example HTML Page</title>  
 </head>  
 <body>  
 <!-- 这里是页面内容 -->  
 
<script src="example1.js"></script>  
<script src="example2.js"></script>  
 </body>  
</html> 

扩展:

CSS

的位置?

页面资源的加载顺序:HTML → CSS(头部) → DOM 构建 → Script(底部或异步)。

与JavaScrip不同,CSS 是渲染阻塞资源,但不会阻塞 DOM 树构建(仅阻塞渲染树生成)。浏览器需要等到 CSS 解析完成才能绘制页面,所以需要尽早加载。

因此,把CSS放在中是最推荐的做法。

  • 避免渲染阻塞:浏览器在解析 HTML 时优先加载 CSS,确保页面内容首次渲染(FCP)时已应用正确样式,避免布局抖动(Layout Shift)。
  • 并行加载优化:现代浏览器支持并行下载 CSS 和解析 DOM,减少整体加载时间。
  • 样式优先级控制:<head> 中的 CSS 优先级高于 <body> 中的内联样式,确保样式一致性。

这里有一个误区,会认为“将 <style> 放在 .vue 文件底部会导致样式加载延迟”。

在 .vue 文件中,构建工具会提取所有样式并注入 <head>,因此最终加载顺序与文件内位置无关,建议按标准的代码顺序:<template> -><script> -> <style>

1.6 行内代码与外部文件

尽可能把 JavaScript 代码放在外部文件。推荐使用外部文件主要有2个理由:

  • 可维护性。JavaScript 代码如果分散到很多页面,会导致维护困难。而用一个目录保存所有 JavaScript 文件,更容易维护,这样可以独立于 HTML页面来编辑代码。

  • 缓存

2.元素

针对早期浏览器不支持JavaScript的问题,<noscript>元素被用来给不支持JavaScript的浏览器提供替代内容。

虽然现在的浏览器已经100%支持JavaScript,但对于禁用JavaScript的浏览器来说,<noscript>仍然有它的用处。

<noscript>元素可以包含任何可以出现在中的HTML元素,

当出现下面这两种情况时,浏览器会显示的内容:

  • 浏览器不支持脚本;
  • 浏览器对脚本的支持被关闭。
<!DOCTYPE html> 
<html> 
 <head> 
 <title>Example HTML Page</title> 
 <script defer="defer" src="example1.js"></script> 
 <script defer="defer" src="example2.js"></script> 
 </head> 
 <body> 
 <noscript> 
 <p>This page requires a JavaScript-enabled browser.</p> 
 </noscript> 
 </body> 
</html>