likes
comments
collection
share

JS模块化,ESM模块规范的 导入、导出、引用、调用详解

作者站长头像
站长
· 阅读数 14

写在前面

在之前的 JS模块化的各种规范 之 CJS、AMD、CMD、UMD、ESM 文章中,介绍了关于JS模块化的各种规范的区别和简单使用。 由于ESM模块规范也是 ECMAScript2015(ES6)中的规范标准,在日常的Web项目开发、小程序开发、APP开发等都是很常用的,所以本文重点将 ESM模块规范中的 导出、导入、导出、引用、调用等,结合日常开发需求做进一步的介绍。

实例代码

这里分别以不同的实例代码,逐步介绍ESM模块规范的导出、 导入、引用、调用等使用方法。

1、模块导出 - export

模块导出 就是将js文件中的 变量、常量、函数、对象、类 等对象 向外导出(暴露)以方便外部的js文件所引用和调用。 在JS的ESM模块规范中,主要通过 export 关键字 来 向外导出想要导出(暴露)的对象。

导出之 - 独立导出

独立导出方式,是直接在要向外导出(暴露)变量、常量、函数、对象、类 等对象的对面加上 export 关键字即可!

export let count = 666;

export const PI = Math.PI;

const random = Math.random();
export { random as default };

export const sum = (n1, n2) => {
    return n1 + n2;
};

export function isOdd(n) {
    return n % 2 === 1 || n % 2 === -1;
};

export class Person {
    constructor(name) {
        this.name = name;
    }
    sayHello() {
        console.log(`Hello! 我是${this.name}`);
    }
};

导出之 - 集中多个导出

集中多个导出方式,一般是在代码的最底部,将要向外导出(暴露)变量、常量、函数、对象、类等对象,通过 export export default 以对象的形式导出!

let count = 666;

const PI = Math.PI;

const sum = (n1, n2) => {
    return n1 + n2;
};

function isOdd(n) {
    return n % 2 === 1 || n % 2 === -1;
};

class Person {
    constructor(name) {
        this.name = name;
    }
    sayHello() {
        console.log(`Hello! 我是${this.name}`);
    }
};

// 集中多个导出
export {
    count, // 注:如果对象中的 键key 和 值value 的命名完全相同 可省去 :random 后面这部分!!
    PI,
    sum,
    isOdd,
    Person
};

导出之 - 默认导出

默认导出方式,是使用 export default 关键字 直接导出,该导出方式只能导出单个内容。

// 可直接向外导出(暴露)变量、常量、函数、对象、类等对象
export default function sayHello(name) {
    console.log(`Hello! 我是${name}`);
};
  • 注意:export default 默认导出 在一个js模块文件中,不能同时存在多个默认导出(也就是说,在一个js模块文件中 export default 能只出现一次!!!)

导出之 - 集中默认导出

集中默认导出方式,是使用 export default 关键字 以对象形式导出,该导出方式可以同时导出多个内容。

const PI = Math.PI

function isOdd(n) {
    return n % 2 === 1 || n % 2 === -1;
};

class Person {
    constructor(name) {
        this.name = name;
    }
    sayHello() {
        console.log(`Hello! 我是${this.name}`);
    }
};

export default {
    count: 666,
    PI,
    sum: (n1, n2) => {
        return n1 + n2;
    },
    isOdd,
    Person,
    sayHello: function (name = '') {
        console.log(`Hello! 我是${name}`);
    },
};

导出之 - 混合导出

混合导出方式,就是 在一个JS模块文件中,既有独立导出、集中导出 又有 默认导出、集中默认导出等形式一起组合导出。

📢下面混合导出的代码内容 以 demo.js 文件命名,为接下来的模块 导入、引用、调用实例做演示用!!

/**
 * demo.js
 */

let count = 666;

// 独立导出 常量
export const PI = Math.PI;

const sum = (n1, n2) => {
    return n1 + n2;
};

// 独立导出 函数
export function isOdd(n) {
    return n % 2 === 1 || n % 2 === -1;
};

// 独立导出 类
export class Person {
    constructor(name = '') {
        this.name = name;
    }
    sayHello() {
        console.log(`Hello! 我是${this.name}`);
    }
};

// 默认导出
export default {
    // 默认导出 变量
    count: count,

    // 默认导出 函数
    sum,

    // 默认导出 类
    Person,

    // 默认导出 字符变量 (可直接创建 并 导出[ 变量、常量、函数、对象、类] 等对象)
    pi: '3.141592653589793',

    // 默认导出 函数 方式1
    is: function (val, type) {
        return toString.call(val) === `[object ${type}]`;
    },

    // 默认导出 函数 方式2
    isArray: (arr = []) => {
        return Object.prototype.toString.call(arr) === '[object Array]' || Array.isArray(arr);
    },
    // 默认导出 函数 方式3
    isObject(val) {
        // 注:在这里还可以通过 this关键字 来调用当前默认导出模块中的 其他导出属性内容
        // 如 this.pi; this.sum(1, 2);
        return val !== null && this.is(val, 'Object') || (typeof val === 'object' && val !== null);
    },
    // 默认导出 类
    AudioTrack: class {
        constructor(trackId = '', trackName = '', trackUrl = 'https://') {
            this.trackId = trackId;
            this.trackName = trackName;
            this.trackUrl = trackUrl;
        };
        start() {
            console.log(`开始播放${this.trackName}`);
        };
        stop() {
            console.log(`停止播放${this.trackName}`);
        };
    }
};

2、模块导入 - import

在JS的ESM模块规范中, 当通过 export 关键字 来 向外导出想要导出(暴露)对象以后, 就可以通 import 关键字 来 导入 对应的(注:要一一对应) 导出内容。

导入之 - 全部导入

全部导入方式,使用 ⁎星号 将模块文件中所有的import 独立导出、export default 默认导出 等导出的内容全部导入进来

import * as all from "./demo.js";
// 注:通过export default 关键 默认导出(暴露)的内容在default属性中,和export独立导出的在同一级上!!
console.log('demo.js模块 所有导出的内容:', all);

导入之 - 默认导入

默认导入方式,只会将导出文件中通过export default 关键字向外 导出(暴露)的内容导入进来。

import def from "./demo.js";
// 注:该导入方式只会 export default 关键字向外 导出(暴露)的内容导入进来!
console.log('demo.js模块 默认导出的内容:', def );

导入之 - 指定导入

指定导入方式,是将导出文件中通过export 关键字向外 导出(暴露)的内容 根据指定变量名(不分顺序)的导入进来。

// 指定将PI, isOdd, Person 导入进来
import { PI, isOdd, Person } from "./demo.js";

导入之 - 混合导入

混合导入方式,就是将导出文件中通过export default 关键字 和 export 关键字 向外 导出(暴露)的内容 根据指定变量名(不分顺序)的导入进来。

// 注:def 接收的就是在导出文件中通过export default 关键字向外导出内容,而在大括号{}中接收到在导出文件中通过export关键字向外导出内容。
// 注:def 这个变量名字是可能自定义的。
import def, { PI, isOdd, Person } from "./demo.js";

导入之 - 改名导入

改名导入,是在导入时的 变量名 和 当前文件中的 变量名 有冲突(相同)时,通过 as 关键字 将导入的变量 新更改一下名字,再导入进来。

// 将原大写的PI 更改为 小写的pi
import def, { PI as pi, isOdd, Person } from "./demo.js";

// 当前文件中的 变量名 有冲突
const PI = '3.141592653589793 ';
console.log(PI, pi);

3、模块导入即导出

模块导入即导出方式,一般主要是用于,为了尽可能的减少 在各个JS模块文件之间出到多次导入问题的。 例如: 在A.js模块文件中,同时需要用到B.js模块文件 和 C.js模块文件。 而B.js模块文件中也要用到C.js模块文件。 此时: 我们可以只需在A.js模块文件中 导入 B.js模块文件,然后B.js模块文件中 导入 C.js模块文件即可。

导入即导出之 - 所有导入导出

所有导入导出,是将模块所有的内容导入 又 全部导出。

  • 这里扮演的B.js模块文件 向A.js模块文件 导出C.js模块文件中的所有导出 。
export * from './demo.js';

导入即导出之 - 默认导入导出

默认导入导出,是将 demo.js模块文件中的 export default 默认模块向外导出。

export  { default } from './demo.js';

导入即导出之 - 默认改名导入导出

将 demo.js模块文件中的 export default 默认模块改名在MyDemo下向外导出。

export  { default as MyDemo } from './demo.js';

导入即导出之 - 指定多个导入导出

指定多个导入导出,是将导出文件中通过export 关键字向外 导出(暴露)的内容 根据指定变量名(不分顺序)的导入和导出

  • 这里扮演的B.js模块文件 向A.js模块文件导出PI, random, isOdd,【在A.js模块文件中,同时需要用到B.js模块文件 和 C.js模块文件】。
export  { PI, random, isOdd } from './demo.js';

导入即导出之 - 混合导入即导出

混合导入即导出,就是将 默认模块 和 指定多个变量一起导出。

// 注:default 是关键字,它装着在导出模块文件中通过export default默认导出的内容。
export { default PI, isOdd, Person } from './demo.js';

// 将 demo.js模块文件中的 export  default 默认模块改名在myDef后向外导出。
export { default as myDef, PI, isOdd, Person } from './demo.js';

4、模块引用与调用

在模块导入以后 引用与调用,就是在模块导入以后 就和 我们平时在一个js文件中写代码一样去引用和调用 变量、常量、函数、对象、类 等对象一样,直接使用即可。

引用与调用之 - 本地资源加载

在vite + vue3 项目开发过程,有时需要在 js 代码中去加载本地图片,由于vite默认不支持 require()方法,所以我们可以使用 import来加载本地资源,而且 在项目构建(打包)后也能正常的显示资源。

  • 注:在下面代码中, 资源前面的../assets/ 是根据当前这个getLocalFile()函数所在的js文件的相对路径,在vue项目中,一般这个文件是放在src目录下面的utils文件夹里面,而资产文件则一般是放在src目录下面的assets文件夹里面,utils 和 assets是同一级目录的(都在src目录下);

function getLocalFile(url) {
	return new URL(`../assets/${url}`, import.meta.url).href;
};
getLocalFile('img/logo.jpg');

// ts写法的简写
const getLocalFile = (url: string): string => new URL(`../assets/${url}`, import.meta.url).href;
getLocalFile('img/logo.jpg');

引用与调用之 - 异步(按需)加载模块

模块异步加载,在一些业务场景中是非常有用的,例如:聊天室,在用户进入聊天室之前需要先进行登录后才能进入,所以在用户没登录成功以前,WebSocket是不应该连接的,需要等到登录成功以后才连接。


function load() {
    import('./demo.js').then((esm) => {
        console.log('esm就是./demo.js模块文件中所有导出的内容:', esm);
        console.log(esm.PI);

        const def = esm.default;
        console.log('esm.default就是./demo.js模块文件中默认导出的内容:', def);
        def.sum(100, 200);
    });

    import('./demo.js').then(({ PI, isOdd, Person }) => {
        console.log('也可以直接解构./demo.js模块文件分别独立导出的内容:', PI, isOdd, Person);
        isOdd(666);
        const p = new Person('沐枫');
        p.sayHello();
    });
};

// 在用户点击以后再去加载 想要引用和调用的模块。
btn.onclick = function () {
    load();
};


// 注:如果是在html文件中直接使用时,需要在script标签中加上type="module"属性,并且要在http服务器环境中打开才能正常使用哦!!
<script type="module">
	import('./demo.js').then((esm) => {
 		...
 	});
</script>