JS 的代码并没有强制规定放在 HTML 中的某个位置,如果您有使用过开发者工具查看过网页源码,那么您会看到很多 JS 代码都以 .js 文件的形式存放,并且放在了 HTML 文件最后,也就是 </body> 结束标签之前。

但如果仔细观察,在 <head> 标签中,也会找到很多 script 标签引入的 JS 代码。

那么您是否好奇过他们都有哪些区别??

3 种书写位置

与 CSS 一样,JS 的脚本算起来也有三种书写方式,分别为行间 JS 代码、内联脚本、外部脚本。

1、事件处理

直接在 HTML 元素的事件属性中写代码,此方式一般多用于编写 demo 测试程序,正常的项目开发不推荐这种写法。

原因:onclick 中的方法名必须全局声明,导致污染全局变量,并且混合了 HTML 结构和 JS 事件行为代码,不利于项目维护。

<p onclick="alert('Hello World!')">点击我</p>

<a href="javascript:alert('Hello World!')">点击我</a>

2、内联脚本

<script>
console.log('Hello World!')
</script>

3、外部脚本

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

ES6 模块化引入,此方式必须要有一个服务器环境!比如:本地安装一个 nginx

<script type="module" src="module.js"></script>

内联脚本应用场景

内联脚本一般多用于页面初始化、临时代码调试、首屏渲染必需的初始化逻辑等场景,比如三方插件初始化:

<script src="https://cdn.bootcdn.net/ajax/libs/vConsole/3.15.1/vconsole.min.js"></script>
<script>
new VConsole();
</script>

外部脚本应用场景

外部脚本适合复杂、复用性高的场景,是现代 Web 开发的主流选择,使用外部脚本可以降低 HTML 代码的复杂度,有利于项目的维护。

<!-- 所有页面引入公共工具代码 -->
<script src="utils.js"></script>
<!-- 所有页面引入公共代码 -->
<script src="common.js"></script>
<!-- 页面独立的 JS 代码 -->
<script src="index.js"></script>

模块化引入方式:

<script type="module" src="index.js"></script>

index.js:

import { a } from './utils.js';
import { b } from './common.js';
a();
b(); document.querySelect('#button').addEventListener("click", async () => {
// 按需加载模块
const module = await import("./test-module.js"); // test-module.js 中导出 run 方法
module.run();
console.log('Hello World!');
});

script 标签属性

script 标签除了常见的 type 和 src 外,还有两个控制脚本异步加载的属性,分别为 async 和 defer,区别如下:

1、无 async/defer

HTML 解析 → 遇到 <script> → 停止解析 → 下载脚本 → 执行脚本 → 继续解析 HTML

2、async

HTML 解析(并行下载脚本) → 脚本下载完成 → 立即执行(可能中断 HTML 解析)

有多个 async 无法保证执行顺序,谁先下载完、谁先执行,所以 async 不适合于依赖顺序的脚本。脚本执行可能发生在 DOMContentLoaded 事件之前或之后,取决于下载速度。

<!-- 无法保证执行顺序,谁先下载完谁先执行 -->
<script async src="script1.js"></script>
<script async src="script2.js"></script>

应用场景:脚本完全独立,不依赖其他脚本或 DOM,比如:统计代码、广告代码等。

3、defer

HTML 解析(并行下载脚本) → HTML 解析完成 → 按顺序执行所有 `defer` 脚本

多个 defer 脚本严格按文档顺序执行,无论下载速度。所有 defer 脚本执行完毕后,才会触发 DOMContentLoaded 事件。

<!-- script1.js 一定在 script2.js 前执行。 -->
<script defer src="script1.js"></script>
<script defer src="script2.js"></script>

应用场景:脚本需要访问完整的 DOM 或依赖其他脚本,比如:页面初始化逻辑。

script 标签位置

  1. 首先要明白,存放在 head 中的 script 标签,会阻塞页面加载,如果这个文件超大,那么页面白屏时间就会很长。

  2. 如果是内联脚本,放在哪儿其实影响不大,主要看其内容中有没有耗时的操作。

  3. 如果您的脚本需要尽早执行,那么建议放在 head 中。比如:vconsole调试工具,尽早加载有利于捕获代码错误。

  4. 如果您的脚本需要访问完整的 DOM,那么建议放在 body 的最后,这样可以确保 DOM 已解析完成。

  5. 虽然可以使用 defer 控制脚本延迟加载,但某些兼容原因,还是建议 JS 脚本后置兼容旧版本浏览器。

写在最后

JS 代码应该首先考虑放在外部文件中,HTML 结构应该永远保持简洁。

除非您真的有需求,才建议将 JS 代码放在 head 中,否则 JS 代码应该永远放在 </body> 结束标签之前。

Web前端入门第 54 问:JavaScript 3 种书写位置及 script 标签的正确存放位置的更多相关文章

  1. web前端入坑第五篇:秒懂Vuejs、Angular、React原理和前端发展历史

    秒懂Vuejs.Angular.React原理和前端发展历史 2017-04-07 小北哥哥 前端你别闹 今天来说说 "前端发展历史和框架" 「前端程序发展的历史」 「 不学自知, ...

  2. Android零基础入门第54节:视图切换组件ViewSwitcher

    原文:Android零基础入门第54节:视图切换组件ViewSwitcher 前面三期学习了ProgressBar系列组件,那本期开始一起来学习ViewAnimator组件. 一.ViewAnimat ...

  3. web前端入坑第二篇:web前端到底怎么学?干货资料! 【转】

    http://blog.csdn.net/xllily_11/article/details/52145172 版权声明:本文为博主[小北]原创文章,如要转载请评论回复.个人前端公众号:前端你别闹,J ...

  4. web前端(13)—— 了解JavaScript,JavaScript的引入方式

    从本篇博文开始,将进入web前端方便最关键最重要的部分——javascript,学到后面你就知道它真的太重要了 什么是JavaScript JavaScript一种直译式的脚本语言,是一种动态类型.弱 ...

  5. WEB前端工程师整理的原生JavaScript经典百例

    一.原生JavaScript实现字符串长度截取 二.原生JavaScript获取域名主机 三.原生JavaScript转义html标签 四.原生JavaScript时间日期格式替换 Date.prot ...

  6. Web前端基础怎么学? JavaScript、html、css知识架构图

    以前开发者只要掌握 HTML.CSS.JavaScript 三驾马车就能胜任一份前端的工作了.而现在除了普通的编码以外,还要考虑如何性能优化,如何跨端.跨平台实现功能,尤其是 AI.5G 技术的来临, ...

  7. web前端学习之HTML CSS/javascript之一

    前端编码之路之坎坷,web前端应该一直是个战场吧,各种浏览器的不兼容,各种小细节的修改,要往一个好的产品经理方向走,实在是难,昨天听了一位十年经验的产品经理讲座,最重要的恐怕就是协调资源的能力,而协调 ...

  8. web前端学习(四)JavaScript学习笔记部分(1)-- JavaScript基础教程

    1.JavaScript基础教程 1.1.Javascript基础-介绍.实现.输出 1.1.1.JavaScript是互联网上最流行的脚本语言,这门语言可用于web和HTML,更可广泛用于服务端.p ...

  9. 【Web前端】清除css、javascript及背景图在浏览器中的缓存

    在实际项目开发过过程中,页面是上传到服务器上的.而为了减少服务器的压力,让用户少加载,浏览器会将图片.css.js缓存到本地中,以便下次访问网站时使用.这样做不仅减少了服务器的压力,并且也减少了用户的 ...

  10. web前端学习(四)JavaScript学习笔记部分(3)-- JavaScript函数+异常处理+事件处理

    1.Javascript函数-了解函数的用途 1.1.函数: 函数是由事件驱动的或者当它被调用时执行的可重复使用的代码块 2.Javascript函数-定义函数 2.1.function必须小写 3. ...

随机推荐

  1. 项目愿景 (Product Vision)、产品目标 (Product Goal) 、Sprint目标 (Sprint Goal) 及 示例

    愿景(Vision) 是制定业务目标(Business Goal)的基础,后者为确定正确的产品目标 (Product Goal) 创造了环境.同样,每个产品目标作为识别有用的冲刺目标的基础.换句话说, ...

  2. Flink学习(十七) Emitting to Side Outputs(侧输出)

    我们在生产实践中经常会遇到这样的场景,需把输入源按照需要进行拆分,比如我期望把订单流按照金额大小进行拆分,或者把用户访问日志按照访问者的地理位置进行拆分等.面对这样的需求该如何操作呢? 大部分的Dat ...

  3. c# virtual 关键字 虚方法

    1.简单的说,虚方法就是可以被子类重写的方法,如果子类重写了虚方法,那么运行时将使用重写后的逻辑,如果没有重写,则使用父类中虚方法的逻辑 class Program { static void Mai ...

  4. ABAQUS弹塑性分析

    1. 弹塑性分析的主要问题 1.1 elastic-plastic deform behavior abaqus 默认的塑性表现行为是金属材料经典塑性理论,采用mises屈服面定义各向同性屈服. 一般 ...

  5. SpringBoot整合Dubbox(无XML配置)

    简介 Dubbox是当当网对阿里的Dubbo进行增强的一个分支.在使用springboot之后,我们发现很多配置并不一定要使用xml.这篇文章的目的是让你使用Dubbox时能像使用springboot ...

  6. mongodb 数据库操作——备份 还原 导出 导入

    mongodump备份数据库 命令格式 mongodump -h IP --port 端口 -u 用户名 -p 密码 -d 数据库 -o 文件存在路径 如果没有用户,可以去掉-u和-p. 如果导出本机 ...

  7. protobuf优缺点及编码原理

    什么是protobuf protobuf(Google Protocol Buffers),官方文档对 protobuf 的定义:protocol buffers 是一种语言无关.平台无关.可扩展的序 ...

  8. 执行Django 的迁移命令报错[1193, Unknown system variable default_storage_engine]

    在学习""编写你的第一个 Django 应用程序,第2部分"时候,遇到一个问题. 执行迁移命令 python manage.py makemigrations polls ...

  9. 基础指令:sudo提权、通配符、特殊符号、stat命令、id命令、正则表达式

    目录 4.4 sudo提权 授权的两种方法 4.4 通配符-查文件 4.5 特殊符号 4.6 stat输出文件的详细内容 4.7 id命令查看用户基本信息 4.8 正则表达式 4.8.1 符号 ^ 4 ...

  10. 卧槽!C 语言宏定义原来可以玩出这些花样?高手必看!

    大家好啊!我是小康. 今天我们来聊一个听起来枯燥但实际上暗藏玄机的话题 -- C 语言的宏定义. 啥?宏定义?那不就是个简单的替换工具吗? 兄dei,如果你也是这么想的,那可就大错特错了!宏定义在 C ...