整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

Javascript模块化

本文主要理理js模块化相关知识。
涉及到内联脚本、外联脚本、动态脚本、阻塞、
deferasyncCommonJSAMDCMDUMDES 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>

但这不是最好的办法,我们接着往下看。

defer

我们给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 特性。

async

我们可以给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模块化,我们接着往下看。

CommonJS

很长一段时间 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

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

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

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 {};
}));

ES Module

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 文件。

Vite

在浏览器支持 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页面如何写?——零基础自学网页制作

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>标签实现文本内链接——零基础自学网页制作