本文主要理理js模块化相关知识。
涉及到内联脚本、外联脚本、动态脚本、阻塞、defer、async、CommonJS、AMD、CMD、UMD、ES Module。顺带探究下Vite。
假设你是一个前端新手,现在入门,那么我们创建一个html页面,需要新建一个index.html文件:
<!DOCTYPE html>
<html>
<head>
<title>test</title>
</head>
<body>
<p id="content">hello world</p>
</body>
</html>
如果需要在页面中执行javascript代码,我们就需要在 HTML 页面中插入 <script> 标签。
有2种插入方式:
1、放在<head>中
2、放在<body>中
比如,点击hello world之后,在hello world后面加3个感叹号的功能,我们在head中加入script标签,并给hello world绑定点击事件:
<!DOCTYPE html>
<html>
<head>
<title>test</title>
<script>
function myFunction() {
document.getElementById('content').innerHTML = 'hello world!!!'
}
</script>
</head>
<body>
<p id="content" onclick="myFunction()">hello world</p>
</body>
</html>
如果加在body中,一般放在body的最后面:
<!DOCTYPE html>
<html>
<head>
<title>test</title>
</head>
<body>
<p id="content" onclick="myFunction()">hello world</p>
<script>
function myFunction() {
document.getElementById('content').innerHTML = 'hello world!!!'
}
</script>
</body>
</html>
简单的逻辑我们可以用这2种方式写,这种方式叫做内联脚本。
当逻辑复杂时,我们可以把上面的script标签中的代码抽取出来,比如在html的同级目录创建一个js文件夹,里面新建一个a.js的文件。
a.js中写上面script标签中的代码:
function myFunction() {
document.getElementById('content').innerHTML = 'hello world!!!'
}
上面的script标签则可以改成:
<script src="./js/a.js"></script>
上面的2种写法,浏览器在加载html时,遇到script标签,会停止解析html。
内联脚本会立刻执行;外联脚本会先下载再立刻执行。
等脚本执行完毕才会继续解析html。
(html解析到哪里,页面就能显示到哪里,用户也能看到哪里)
比如下面的代码:
<p>...content before script...</p>
<script src="./js/a.js"></script>
<p>...content after script...</p>
解析到第一个p标签,我们能看到...content before script...显示在了页面中,然后浏览器遇到script标签,会停止解析html,而去下载a.js并执行,执行完a.js才会继续解析html,然后页面中才会出现...content after script...。
我们可以通过Chrome的Developer Tools分析一下index.html加载的时间线:
这会导致2个问题:
1、脚本无法访问它下面的dom;
2、如果页面顶部有个笨重的脚本,在它执行完之前,用户都看不到完整的页面。
对于问题2,我们可以把脚本放在页面底部,这样它可以访问到上面的dom,且不会阻塞页面的显示:
<body>
...all content is above the script...
<script src="./js/a.js"></script>
</body>
但这不是最好的办法,我们接着往下看。
我们给script标签加defer属性,就像下面这样:
<p>...content before script...</p>
<script defer src="./js/a.js"></script>
<p>...content after script...</p>
defer 特性告诉浏览器不要等待脚本。于是,浏览器将继续解析html,脚本会并行下载,然后等 DOM 构建完成后,脚本才会执行。
这样script标签不再阻塞html的解析。
这时再看时间线:
需要注意的是,具有 defer 特性的脚本保持其相对顺序。
比如:
<script defer src="./js/a.js"></script>
<script defer src="./js/b.js"></script>
上面的2个脚本会并行下载,但是不论哪个先下载完成,都是先执行a.js,a.js执行完才会执行b.js。
这时,如果b.js依赖a.js,这种写法将很有用。
另外需要注意的是,defer 特性仅适用于外联脚本,即如果 script标签没有 src属性,则会忽略 defer 特性。
我们可以给script标签加async属性,就像下面这样:
<script async src="./js/a.js"></script>
这会告诉浏览器,该脚本完全独立。
独立的意思是,DOM 和其他脚本不会等待它,它也不会等待其它东西。async 脚本就是一个会在加载完成时立即执行的完全独立的脚本。
这时再看时间线:
可以看到,虽然下载a.js不阻塞html的解析,但是执行a.js会阻塞。
还需要注意多个async时的执行顺序,比如下面这段代码:
<p>...content before script...</p>
<script async src="./js/a.js"></script>
<script async src="./js/b.js"></script>
<p>...content after script...</p>
两个p标签的内容会立刻显示出来,a.js和b.js则并行下载,且下载成功后立刻执行,所以多个async时的执行顺序是谁先下载成功谁先执行。
一些比较独立的脚本,比如性能监控,就很适合用这种方式加载。
另外,和defer一样,async 特性也仅适用于外联脚本。
我们可以动态地创建一个script标签并append到文档中。
let script = document.createElement('script')
script.src = '/js/a.js'
document.body.append(script)
append后脚本就会立刻开始加载,表现默认和加了async属性一致。
我们可以显示的设置script.async = false来改变这个默认行为,那么这时表现就和加了defer属性一致。
上面的这些写法,当script标签变多时,容易导致全局作用域污染,还要维护书写顺序,要解决这个问题,需要一种将 JavaScript 程序拆分为可按需导入的单独模块的机制,即js模块化,我们接着往下看。
很长一段时间 JavaScript 没有模块化的概念,直到 Node.js 的诞生,把 JavaScript 带到服务端,这时,CommonJS诞生了。
CommonJS定义了三个全局变量:
require,exports,module
require 读入并执行一个 js 文件,然后返回其 exports 对象;
exports 对外暴露模块的接口,可以是任何类型,指向 module.exports;
module 是当前模块,exports 是 module 上的一个属性。
Node.js 使用了CommonJS规范。
比如:
// a.js
let name = 'Lily'
export.name = name
// b.js
let a = require('a.js')
console.log(a.name) // Lily
由于CommonJS不适合浏览器端,于是出现了AMD和CMD规范。
AMD(Asynchronous Module Definition) 是 RequireJS 在推广过程中对模块定义的规范化产出。
基本思想是,通过 define 方法,将代码定义为模块。当这个模块被 require 时,开始加载依赖的模块,当所有依赖的模块加载完成后,开始执行回调函数,返回该模块导出的值。
使用时,需要先引入require.js:
<script src="require.js"></script>
<script src="a.js"></script>
然后可以这样写:
// a.js
define(function() {
let name = 'Lily'
return {
name
}
})
// b.js
define(['a.js'], function(a) {
let name = 'Bob'
console.log(a.name) // Lily
return {
name
}
})
CMD(Common Module Definition) 是 Sea.js 在推广过程中对模块定义的规范化产出。
使用时,需要先引入sea.js:
<script src="sea.js"></script>
<script src="a.js"></script>
然后可以这样写:
// a.js
define(function(require, exports, module) {
var name = 'Lily'
exports.name = name
})
// b.js
define(function(require, exports, module) {
var name = 'Bob'
var a = require('a.js')
console.log(a.name) // 'Lily'
exports.name = name
})
UMD (Universal Module Definition) 目的是提供一个前后端跨平台的解决方案(兼容全局变量、AMD、CMD和CommonJS)。
实现很简单,判断不同的环境,然后以不同的方式导出模块:
(function (root, factory) {
if (typeof define === 'function' && (define.amd || define.cmd)) {
// AMD、CMD
define([], factory);
} else if (typeof module !== 'undefined' && typeof exports === 'object') {
// Node、CommonJS
module.exports = factory();
} else {
// 浏览器全局变量
root.moduleName = factory();
}
}(this, function () {
// 只需要返回一个值作为模块的export
// 这里我们返回了一个空对象
// 你也可以返回一个函数
return {};
}));
AMD 和 CMD 是社区的开发者们制定的模块加载方案,并不是语言层面的标准。从 ES6 开始,在语言标准的层面上,实现了模块化功能,而且实现得相当简单,完全可以取代上文的规范,成为浏览器和服务器通用的模块解决方案。
ES6 的模块自动采用严格模式。模块功能主要由两个命令构成:export和import。
export命令用于规定模块的对外接口;
import命令用于输入其他模块提供的功能。
比如上面的代码,我们可以这样写:
// a.js
const name = 'Lily'
export {
name
}
// 等价于
export const name = 'Lily'
// b.js
import { name } from 'a.js'
console.log(name) // Lily
// b.js
import * as a from 'a.js'
console.log(a.name) // Lily
此外,还可以用export default默认导出的写法:
// a.js
const name = 'Lily'
export default {
name
}
// b.js
import a from 'a.js'
console.log(a.name) // Lily
如果只想运行a.js,可以只import:
// b.js
import 'a.js'
我们可以给script标签加type=module让浏览器以 ES Module 的方式加载脚本:
<script type="module" src="./js/b.js"></script>
这时,script标签会默认有defer属性(也可以设置成async),支持内联和外联脚本。
这时我们运行打开index.html,会发现浏览器报错了:
这是因为 type=module 的 script 标签加强了安全策略,浏览器加载不同域的脚本资源时,如果服务器未返回有效的 Allow-Origin 相关 CORS 头,会禁止加载改脚本。而这里启动的index.html是一个本地文件(地址是file://路径),将会遇到 CORS 错误,需要通过一个服务器来启动 HTML 文件。
在浏览器支持 ES Module 之前,我们用工具实现JavaScript模块化的开发,比如webpack、Rollup 和 Parcel 。但是当项目越来越大后,本地热更新越来越慢,而 Vite 旨在利用ESM解决上述问题。
Vite使用简单,可以去官网(https://cn.vitejs.dev/)看看。
老的规范了解即可,未来是ES Module的,用Vite可以极大的提升开发时的体验,生产环境用Rollup打包。
Image 对象
Image 对象代表嵌入的图像。
<img> 标签每出现一次,一个 Image 对象就会被创建。
Image 对象属性
W3C: W3C 标准。
属性 | 描述 | W3C |
---|---|---|
align | 设置或返回与内联内容的对齐方式。 | Yes |
alt | 设置或返回无法显示图像时的替代文本。 | Yes |
border | 设置或返回图像周围的边框。 | Yes |
complete | 返回浏览器是否已完成对图像的加载。 | No |
height | 设置或返回图像的高度。 | Yes |
hspace | 设置或返回图像左侧和右侧的空白。 | Yes |
longDesc | 设置或返回指向包含图像描述的文档的 URL。 | Yes |
lowsrc | 设置或返回指向图像的低分辨率版本的 URL。 | No |
name | 设置或返回图像的名称。 | Yes |
src | 设置或返回图像的 URL。 | Yes |
useMap | 设置或返回客户端图像映射的 usemap 属性的值。 | Yes |
vspace | 设置或返回图像的顶部和底部的空白。 | Yes |
width | 设置或返回图像的宽度。 | Yes |
Image 对象事件
事件 | 描述 | W3C |
---|---|---|
onabort | 当用户放弃图像的装载时调用的事件句柄。 | Yes |
onerror | 在装载图像的过程中发生错误时调用的事件句柄。 | Yes |
onload | 当图像装载完毕时调用的事件句柄。 | Yes |
标准属性和事件
Image 对象同样支持标准的 属性 和 事件。
如您还有不明白的可以在下面与我留言或是与我探讨QQ群308855039,我们一起飞!
昨天我们在《使用HTML添加表格3(间距与颜色)——零基础自学网页制作》(目录在结尾)中学习了设置单元格以及其中内容的空间间距和背景颜色。
其中添加列向单元格背景颜色只需要修改对应的<tr>标签中的style属性,而修改行向标签需要修改不同<tr></tr>标签中的<td>标签的style属性,这样操作起来就非常的麻烦,那有没有简便的修改行向单元格背景颜色的方法呢?
当然有!
开发团队给出了<colgroup><col></col></colgroup>这样的组合来解决这个问题,下面让我们详细学习。
<colgroup></colgroup>标签是一个给行向单元格打组的标签,在页面中不会显示。
<col></col>标签是来具体设置行向单元格数量和颜色的标签。
示例代码如下:
<colgroup><col span = "1" style="background-color:#ff0000;"></col></colgroup>
这段代码添加到"第一个页面.html"当中就可以,具体代码如下:
<!DOCTYPE HTML>
<html>
<head>
<title>第一个网页</title>
</head>
<body>
<h1>第一个网页</h1><hr>
<h2>表格元素</h2><hr>
<table border="1" width="100%">
<thead>
<tr>
<td colspan="2">表格的头部信息</td>
</tr>
</thead>
<tfoot>
<tr>
<td colspan="2">表格的脚部信息</td>
<tr>
</tfoot>
<tbody>
<caption>表格标题</caption>
<colgroup>
<col span = "1" style="background-color:#ff0000;"></col>
</colgroup>
<tr>
<th>姓名</th>
<th>年龄</th>
</tr>
<tr>
<td>一列一行</td>
<td>一列二行</td>
</tr>
<tr>
<td>二列一行</td>
<td>二列二行</td>
</tr>
</tbody>
</table>
</body>
</html>
页面效果如图:
因为第一列和最后一列只有一行所以,也都变红了。
其中span的数量代表行数。
如果把span等号后面的数改成2,因为表格只有两行,所以整个表格都红了。
表格嵌套
我们可以通过向表格中添加表格实现表格嵌套。表格嵌套可以把一个单元格分成行向或列向分割单元格。
代码示例如下:我们把"一列一行"分割成列向两个单元格。
<tr><td><table border = "1" width="100%"><tr><td>1</td><td>2</td></tr></table></td>
使用
<table border = "1" width="100%">
<tr>
<td>1</td>
<td>2</td>
</tr>
</table>
这段代码替换文字"一列一行"即可。
页面效果如图所示:
留个思考题,大家可以思考一下行向分割单元格怎么写。
今天的内容结束了。
全部示例代码如下:
<!DOCTYPE HTML>
<html>
<head>
<title>第一个网页</title>
</head>
<body>
<h1>第一个网页</h1><hr>
<h2>表格元素</h2><hr>
<table border="1" width="100%">
<thead>
<tr>
<td colspan="2">表格的头部信息</td>
</tr>
</thead>
<tfoot>
<tr>
<td colspan="2">表格的脚部信息</td>
<tr>
</tfoot>
<tbody>
<caption>表格标题</caption>
<colgroup>
<col span = "1" style="background-color:#ff0000;"></col>
</colgroup>
<tr>
<th>姓名</th>
<th>年龄</th>
</tr>
<tr>
<td>
<table border = "1" width="100%">
<tr>
<td>1</td>
<td>2</td>
</tr>
</table>
</td>
<td>一列二行</td>
</tr>
<tr>
<td>二列一行</td>
<td>二列二行</td>
</tr>
</tbody>
</table>
</body>
</html>
喜欢的小伙伴请关注我,阅读中遇到任何问题请给我留言,如有疏漏或错误欢迎大家斧正,不胜感激!
学到这里,相信大家已经有独立读懂HTML代码说明的能力了,明天我会为大家讲解16进制颜色表示方法。之后会给大家推荐html代码参考手册的链接。如果您是零基础的话,学完16进制颜色表示方法后,基本上就可以无障碍的阅读html代码参考手册了,如果阅读起来还是有困难,请继续看后面我为大家讲解一些常用元素及属性的文章,已及html中特殊符号的输入方法,全部做完后再结束这套教程。
如果您有任何疑问或不解欢迎关注并私信我。
HTML序章(学习目的、对象、基本概念)——零基础自学网页制作
HTML是什么?——零基础自学网页制作
第一个HTML页面如何写?——零基础自学网页制作
HTML页面中head标签有啥用?——零基础自学网页制作
初识meta标签与SEO——零基础自学网页制作
HTML中的元素使用方法1——零基础自学网页制作
HTML中的元素使用方法2——零基础自学网页制作
HTML元素中的属性1——零基础自学网页制作
HTML元素中的属性2(路径详解)——零基础自学网页制作
使用HTML添加表格1(基本元素)——零基础自学网页制作
使用HTML添加表格2(表格头部与脚部)——零基础自学网页制作
使用HTML添加表格3(间距与颜色)——零基础自学网页制作
使用HTML添加表格4(行颜色与表格嵌套)——零基础自学网页制作
16进制颜色表示与RGB色彩模型——零基础自学网页制作
HTML中的块级元素与内联元素——零基础自学网页制作
初识HTML中的<div>块元素——零基础自学网页制作
在HTML页面中嵌入其他页面的方法——零基础自学网页制作
封闭在家学网页制作!为页面嵌入PDF文件——零基础自学网页制作
HTML表单元素初识1——零基础自学网页制作
HTML表单元素初识2——零基础自学网页制作
HTML表单3(下拉列表、多行文字输入)——零基础自学网页制作
HTML表单4(form的action、method属性)——零基础自学网页制作
HTML列表制作讲解——零基础自学网页制作
为HTML页面添加视频、音频的方法——零基础自学网页制作
音视频格式转换神器与html视频元素加字幕——零基础自学网页制作
HTML中使用<a>标签实现文本内链接——零基础自学网页制作
*请认真填写需求信息,我们会在24小时内与您取得联系。