第二节:ESModule简介、按需导出导入、默认导出导入、动态加载、内部原理等
一. 前言
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 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
2021-04-13 第五节:ES6用法之Reflect、Promise、Iterator
2021-04-13 第一节:RabbitMq简介、Win下安装、卸载、常用指令、图形化界面剖析