本文主要理理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小時內與您取得聯系。