第二节: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混合使用

 二者可以相互引用,但需要安装一下webpack环境,这里不再演示

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 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
 
posted @ 2022-04-13 16:13  Yaopengfei  阅读(1935)  评论(2编辑  收藏  举报