一. 前言
1. 背景
因為AMD,CMD局限使用與瀏覽器端,而CommonJS在服務器端使用。 ESModule才是瀏覽器端和服務器端通用的規范
2. 關鍵字
(1). 使用export、 export default進行導出
(2). 使用import關鍵字進行導入
3. import的匹配規則
這是在Vue項目中的匹配規則哦,如果是node環境下,必須寫全路徑哦!!!
(1). 如果是完整路徑,則直接引入 。eg:import moudleA from "./find.js";
(2). 如果不是完整路徑,比如:import mA from './find'
A. 先找同名的js文件,即找 find.js
B. 如果找不到,再找find文件夾,找到后,再匹配find文件夾中的index.js文件。
C. 如果找不到index.js文件,會去當前文件夾中的package.json文件中查找main選項中的入口文件。
D. 全都沒有的話,則報錯。
4. 使用環境
(1). 在vue項目中,可以直接使用
(2). 在瀏覽器中,需要
如:<script src="./modules/foo.js" type="module"></script>
注意:運行的時候, (比如一個 file:// 路徑的文件 的運行模式), 你將會遇到 CORS 錯誤,因為Javascript 模塊安全性需要。可以使用VSCode中有一個插件:Live Server
(3). 在node環境中,需要npm init一下,然后在package.json中,加上一句話: "type": "module", 詳見package.json
{
"type": "module",
"name": "05_esmodule",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
二. 按需導出/導入
1. export按需導出
export關鍵字將一個模塊中的變量、函數、類等導出,有以下三種寫法:
(1) 在語句聲明的前面直接加上export關鍵字
(2) 聲明和導出分開,將所有需要導出的標識符,放到export后面的 {}中
注意:這里的 {}里面不是ES6的對象字面量的增強寫法,{}也不是表示一個對象的; 所以: export {name: name},是錯誤的寫法!!!
(3) 導出時給標識符起一個別名
總結:上述三種寫法,可以在一個js模塊中共存的!!!
代碼分享--代碼中只是為了演示三種寫法
/*
export按需導出
*/
// 方式1: export + 聲明語句 【推薦】
export const myName = "ypf";
export const myAge = 18;
export function foo() {
console.log("foo start");
}
export class Person {
GetMsg() {
console.log("Person GetMsg start");
}
}
// 方式2: 聲明和export導出分開 【推薦】
const myName = "ypf";
const myAge = 18;
function foo() {
console.log("foo start");
}
class Person {
GetMsg() {
console.log("Person GetMsg start");
}
}
export { myName, myAge, foo, Person };
// 方式3: 導出的時候起別名
const myName1 = "ypf";
const myAge1 = 18;
function foo1() {
console.log("foo start");
}
class Person1 {
GetMsg() {
console.log("Person GetMsg start");
}
}
export { myName1 as myName, myAge1 as myAge, foo1 as foo, Person1 as Person };
2. import按需導入
import關鍵字負責從另外一個模塊中導入內容
(1). import {標識符列表} from '模塊';
注意:這里的{}也不是一個對象,里面只是存放導入的標識符列表內容;
(2).導入時給標識符起別名
(3).通過 * 將模塊功能放到一個模塊功能對象(a module object)上
總結:只有使用上面export按需導出的方式,才能使用下面import按需導入的方式進行接收
代碼分享:
// 導入方式1:直接原名輸出
console.log("-----------導入方式1:直接原名輸出---------------");
import { myName, myAge, foo, Person } from "./111.js";
console.log(myName, myAge);
foo();
new Person().GetMsg();
// 導入方式2:導入的時候起別名
console.log("-----------導入方式2:導入的時候起別名---------------");
import {
myName as myName2,
myAge as myAge2,
foo as foo2,
Person as Person2,
} from "./111.js";
console.log(myName2, myAge2);
foo2();
new Person2().GetMsg();
// 導入方式3:將導出的所有內容放到一個標識符中
console.log(
"-----------導入方式3:將導出的所有內容放到一個標識符中---------------"
);
import * as myData from "./111.js";
console.log(myData.myName, myData, myAge);
myData.foo();
new myData.Person().GetMsg();
3. 封裝結合使用【重】
比如在實際開發,有很多工具類,每個工具類js文件中,都有很多方法,但在一個vue頁面中,需要使用很多工具類文件中的某一個 或 某幾個方法,如果每個文件都導入,顯得很繁瑣。
這里我們通常采用一種統一出口的思想解決這個問題:
如下:
utils文件夾中有很多工具類文件: 001.js 002.js 003.js, 我們新建一個 index.js文件,在該文件中引入001 002 003 三個js文件,然后對外export所有方法,那么在vue頁面使用的 時候,我們只需引入index.js文件即可。
index.js統一出口文件,有以下3種寫法:
寫法1:先import進來需要的,然后再export
寫法2:直接export導出需要的【推薦】
寫法3: 直接全部導出 【推薦】
總結:寫法1和2,都可以按照需要對外暴露,寫法3直接全部暴露
utils/001.js
export const myName = "ypf";
export const myAge = 18;
utils/002.js
function foo1() {
console.log("foo1 start");
}
function foo2() {
console.log("foo2 start");
}
export { foo1, foo2 };
utils/003.js
export class Person {
GetMsg() {
console.log("Person GetMsg");
}
}
utils/index.js 【重點】
// 寫法1:先import進來需要的,然后再export
/*
import { myName, myAge } from "./001.js";
import { foo1, foo2 } from "./002.js";
import { Person } from "./003.js";
export { myName, myAge, foo1, foo2, Person };
*/
// 寫法2:直接export導出需要的【推薦】
/*
export { myName, myAge } from "./001.js";
export { foo1, foo2 } from "./002.js";
export { Person } from "./003.js";
*/
// 寫法3:直接全部導出 【推薦】
export * from "./001.js";
export * from "./002.js";
export * from "./003.js";
最外層引用--引入的是index.js文件
import { myName, myAge, foo1, foo2, Person } from "./utils/index.js";
console.log(myName, myAge);
foo1();
foo2();
new Person().GetMsg();
三. 默認導出/導入
1. default默認導出
默認導出export時不需要指定名字,有以下幾種寫法:
(1). 最常見的寫法1:先聲明,然后 exprot default {} 導出
(2). 寫法2: 直接在某個方法、變量、類前,加 export default
(3). 寫法3: 使用 export {}, 在里面的某個內容上 + as default, 注意,也是只能加1個 【了解即可】
特別注意:在一個js模塊中,只能有一個默認導出,也就是說 export default只能出現一次!!!!
寫法1
/*
export default默認導出-寫法1
*/
// 最常見的寫法為:先聲明,然后 exprot default {} 導出
const myName = "ypf";
const myAge = 18;
function foo() {
console.log("foo start");
}
class Person {
GetMsg() {
console.log("Person GetMsg start");
}
}
export default {
myName,
myAge,
foo,
Person,
};
寫法2
/*
export default默認導出-寫法2
*/
// 寫法2: 直接在某個方法、變量、類前,加 export default
export default function foo2() {
console.log("foo2 start");
}
// 總結:因為 一個模塊中export default 只能出現一次,所以這種寫法也就只能導出去一樣東西哦!!!
寫法3
// 寫法3: 使用 export {}, 在里面的某個內容上 + as default, 注意,也是只能加1個 【了解即可】
const myName2 = "ypf";
const myAge2 = 18;
function foo2() {
console.log("foo start");
}
export { myName2 as default, myAge2, foo2 };
2. 默認導入
默認導入時不能使用 {} 接收,必須使用一個變量來接收,這個變量可以自己命名,根據默認導出形式的不同,這個變量可能是對象、函數、類、普通變量等
(1). 針對默認導出寫法1的---默認導入, 此時接收的這個變量是一個對象
(2). 針對默認導出寫法2的---默認導入, 此時接收的這個變量是一個函數
(3). 針對默認導出寫法3的---默認導入, 此時接收的這個變量是一個普通string類型的變量
//1. 針對默認導出寫法1的---默認導入
console.log("--1. 針對默認導出寫法1的---默認導入--");
import myModel from "./111.js";
console.log(myModel.myName, myModel.myAge);
myModel.foo();
new myModel.Person().GetMsg();
// 特別注意:這里不支持{}解構寫法哦,必須寫一個對象接受
// import { myName } from "./111.js"; //報錯!!
//2. 針對默認導出寫法2的---默認導入
console.log("--2. 針對默認導出寫法2的---默認導入--");
import myFun from "./222.js"; //這里導入的myFun就是函數foo2
myFun();
//3. 針對默認導出寫法3的---默認導入
console.log("--3. 針對默認導出寫法3的---默認導入--");
import myData from "./333.js"; //這里導入的myData就是變量myName2
console.log(myData);
四. 其它用法
1. 動態加載
我們可以利用 import() 函數實現在js業務代碼中動態加載模塊
導出代碼
const myName = "ypf";
const myAge = 18;
function foo() {
console.log("foo start");
}
class Person {
GetMsg() {
console.log("Person GetMsg start");
}
}
export { myName, myAge, foo, Person };
導入代碼
//1. import函數返回的結果是一個Promise
import("./111.js").then(res => {
console.log(res);
console.log(res.myName, res.myAge);
console.log(res.foo());
});
2. import meta
import.meta是一個給JavaScript模塊暴露特定上下文的元數據屬性的對象。
它包含了這個模塊的信息,比如說這個模塊的URL;它是ES11(ES2020)中新增的特性;
//2. ES11新增的特性
// meta屬性本身也是一個對象: { url: "當前模塊所在的路徑" }
console.log(import.meta);
五. ESModule內部原理
Module的解析過程可以划分為三個階段:
階段一:構建(Construction),根據地址查找js文件,並且下載,將其解析成模塊記錄(Module Record);
階段二:實例化(Instantiation),對模塊記錄進行實例化,並且分配內存空間,解析模塊的導入和導出語句,把模塊指向對應的內存地址。
階段三:運行(Evaluation),運行代碼,計算值,並且將值填充到內存地址中;
六. ESModule和CommonJs混合使用
ESModule導出
const name = "bar"
const age = 100
// es module導出
export {
name,
age
}
CommonJs導出
const name = "foo"
const age = 18
// commonjs導出
module.exports = {
name,
age
}
導入
// es module導入
import { name, age } from "./foo";
console.log(name, age);
// commonjs導入
// const bar = require("./bar.js");
// console.log(bar.name, bar.age);
!
- 作 者 : Yaopengfei(姚鵬飛)
- 博客地址 : http://www.cnblogs.com/yaopengfei/
- 聲 明1 : 如有錯誤,歡迎討論,請勿謾罵^_^。
- 聲 明2 : 原創博客請在轉載時保留原文鏈接或在文章開頭加上本人博客地址,否則保留追究法律責任的權利。