红茶的个人站点

  • 首页
  • 专栏
  • 开发工具
  • 其它
  • 隐私政策
Awalon
Talk is cheap,show me the code.
  1. 首页
  2. 前端学习笔记
  3. 正文

JavaScript 学习笔记 9:模块

2026年4月15日 32点热度 0人点赞 0条评论

模块

在 JS 中,一个 JS 文件就是一个模块,可以使用export关键字暴露本模块的函数或变量给其它模块使用:

export function sayHello(name) {
  console.log(`hello ${name}`);
  return `hello ${name}`;
}

如果其他模块需要使用,可以使用import关键字导入:

import {sayHello} from './hello.js'
​
sayHello('Tom');

如果是浏览器加载的 Html 文件,需要在代码片段<script>标签的属性中标记这里是模块后才能使用导入:

<!doctype html>
<html>
  <head></head>
  <body></body>
</html>
<script type="module">
  import {sayHello} from './hello.js';
​
  document.body.innerHTML = sayHello('John');
</script>

可以使用 VSCode 的 live server 插件加载 Html 文件进行验证。

模块级范围

每个模块中的顶级变量都只能在模块自己的范围内使用(访问)。比如:

// \src\module\user.js
let userName = 'Tom';
// \src\module\hello.js
function sayHello(name) {
  console.log(`hello ${name}`);
}
​
sayHello(userName);
<!DOCTYPE html>
<script type="module" src="./user.js"></script>
<script type="module" src="./hello.js"></script>

这里有两个 JS 文件,都是以模块的方式加载到 Html 中,此时它们中的顶级变量都只存在于模块自己的作用域,因此hello.js中的sayHello(userName)会报错,因为找不到userName这个变量。

正确的方式是使用导入导出分享模块之间的变量(或函数):

// \src\module3\user.js
export let userName = 'Tom';
// \src\module3\hello.js
import { userName } from "./user.js";
function sayHello(name) {
  console.log(`hello ${name}`);
}
​
sayHello(userName);
<!DOCTYPE html>
<script type="module" src="./hello.js"></script>

模块只有在首次导入时才会执行

模块只有在首次导入时才会被评估和执行:

// \src\module4\random.js
export let randomNum = Math.random();
// \src\module4\a.js
import { randomNum } from './random.js'
console.log('a', randomNum);
// \src\module4\b.js
import { randomNum } from './random.js'
console.log('b', randomNum);
<!DOCTYPE html>
<script type="module" src="./a.js"></script>
<script type="module" src="./b.js"></script>

可以看到控制台输出的两个随机数是相等的,因为random.js只执行了一次。

这种行为和所有支持导入的脚本语言是一致的,比如 PHP 中的require_once。

可以利用这种行为实现一些特殊用途,比如预留模块中的一些变量,交给其他模块进行初始化:

// \src\module5\config.js
export let config = {
    userName: undefined
};
​
export function getUserName() {
    return config.userName;
}
// \src\module5\init.js
import {config} from './config.js'
​
config.userName = '张三';
// src\module5\index.js
import './init.js';
import {getUserName} from './config.js';
​
console.log(getUserName());

执行时机

以模块方式加载的 JS 脚本,会在 Html 被完全加载后加载,这和普通的 JS 脚本加载时机不同:

<!DOCTYPE html>
<html> 
    <body>
        <script type="module">
            console.log(typeof button);
            // object
        </script>
        <script>
            console.log(typeof button);
            // undefined
        </script>
        <button id="button">Button</button>
    </body>
</html>

这里先加载普通的 JS 脚本<script>,再加载 Html DOM 元素<button>,最后加载模块<script type="module">,因此控制台会先打印undefined再打印object。

Async

可以用async标记模块片段:

<!DOCTYPE html>
<html>
​
<head></head>
​
<body>
    <button onclick="showCount()">show count</button>
    <script async type="module">
        import { inc } from './counter.js';
        inc();
        console.log('第一个模块加载完成');
    </script>
    <script async type="module">
        import { inc } from './counter.js';
        inc();
        console.log('第二个模块加载完成');
    </script>
    <script type="module">
        import { get } from './counter.js';
        function showCount() {
            alert(get());
        }
        window.showCount = showCount;
    </script>
</body>
​
</html>

被async标记的模块片段会立即加载,不会像普通模块那样等待 HTML DOM 完全加载完毕后再加载。这通常会用在某些不依赖于 HTML DOM 或其他模块的模块。比如上面的示例中,两个异步加载的模块仅打印内容以及让模块加载计数器+1。

导出和导入

导出

可以导出变量、函数或类定义:

export let count = 0;
​
export function add(a, b){
    return a + b;
}
​
export class User{
    constructor(name, age){
        this.name = name;
        this.age = age;
    }
}

除了分别导出,还可以单独导出:

let count = 0;
​
function add(a, b){
    return a + b;
}
​
class User{
    constructor(name, age){
        this.name = name;
        this.age = age;
    }
}
​
export {add, User, count};

导入 *

可以按照需要分别导入:

import {count, User, add} from './export.js';
​
console.log(add(1, 2));
console.log(count);
console.log(new User('Bob', 20));

也可以使用import * as导入所有:

import * as util from './export.js';
​
console.log(util.add(1, 2));
console.log(util.count);
console.log(new util.User('Bob', 20));

如果导入的标识符与当前命名空间冲突,可以使用as重命名标识符:

import {count as myCount, User as MyUser, add as myAdd} from './export.js';
​
console.log(myAdd(1, 2));
console.log(myCount);
console.log(new MyUser('Bob', 20));

类似的,也可以在导出的时候使用as重命名标识符:

let count = 0;
​
function add(a, b){
    return a + b;
}
​
class User{
    constructor(name, age){
        this.name = name;
        this.age = age;
    }
}
​
export {add as myAdd, User as MyUser, count as myCount};
import * as export3 from './export3.js';
​
console.log(export3.myAdd(1, 2));
console.log(new export3.MyUser('张三', 18));
console.log(export3.myCount);

默认导出

有时候一个模块中只会定义一个类或函数,此时使用默认导出更为方便,比如:

export default class{
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
​
    toString() {
        return `${this.name} ${this.age}`;
    }
}

注意这里的类定义没有类名,因为在这个模块里不需要使用该类定义的名称,而其他模块导入类定义时可以指定类名:

import User from './user.js';
​
let user = new User('Mike', 20);
console.log(user.toString());

与之前的导入相比,导入默认导出时不需要使用大括号({...})。

理论上导入默认导出时,可以指定任意名称,但通常来说是导入模块的文件名,比如user.js就命名为User。

default

某些情况下可以使用default关键字指代默认导出,比如:

class User{
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
​
    toString() {
        return `${this.name} ${this.age}`;
    }
}
​
export {User as default}

这和export default class User{...}是等价的。

虽然不建议这么做,但默认导出和普通导出是可以同时存在的,比如:

export default class User{
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
​
    toString() {
        return `${this.name} ${this.age}`;
    }
}
​
export function compare(user1, user2){
    if(user1.age > user2.age) return 1;
    if(user1.age < user2.age) return -1;
    return 0;
}

如果要同时导入默认导出和普通导出:

import {default as User, compare} from './user.js';
​
let user1 = new User('Mike', 20);
let user2 = new User('Jane', 18);
console.log(compare(user1, user2));

也可以使用*导入所有内容:

import * as user from './user.js';
​
let User = user.default;
let compare = user.compare;
let user1 = new User('Mike', 20);
let user2 = new User('Jane', 18);
console.log(compare(user1, user2));

导入对象的default属性就是默认导出的内容。

重新导出

假设我们要开发一个 JS 工具包,我们希望所有客户端程序都通过一个统一入口index.js导入工具类/函数等:

// login.js
export function login(userName, password) {
    if (userName === 'admin' && password === '123') {
        return true;
    }
    return false;
}
​
export function logout() {
    return true;
}
// index.js
import {login, logout} from './login.js';
export {login, logout};

这里index.js导入需要提供给客户端程序的工具函数,并重新导出。客户端程序通过index.js即可完成导入:

import {login, logout} from './index.js';
​
console.log(login('admin', '123'));
console.log(logout());

对于这种重新导出,有一个更简便的写法:

// index.js
export {login, logout} from './login.js';

动态导入

可以使用import方法动态导入模块,该方法返回的是一个Promise对象:

// user1.js
export default class{
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
​
    toString() {
        return `Name:${this.name}, Age: ${this.age}`;
    }
}
// user2.js
export default class{
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
​
    toString() {
        return `姓名:${this.name},年龄: ${this.age}`;
    }
}
async function test(){
    let User;
    if(Math.random() > 0.5){
        let module = await import('./user1.js');
        User = module.default;
    }
    else{
        let module = await import('./user2.js');
        User = module.default;
    }
    let user = new User('张三', 18);
    console.log(user.toString());
}
​
for(let i = 0; i < 10; i++){
    test();
}
// Name:张三, Age: 18
// Name:张三, Age: 18
// Name:张三, Age: 18
// Name:张三, Age: 18
// Name:张三, Age: 18
// Name:张三, Age: 18
// 姓名:张三,年龄: 18
// 姓名:张三,年龄: 18
// 姓名:张三,年龄: 18
// 姓名:张三,年龄: 18

The End.

本文的完整示例可以从这里获取。

参考资料

  • 现代 JavaScript 教程

本作品采用 知识共享署名 4.0 国际许可协议 进行许可
标签: 暂无
最后更新:2026年4月15日

魔芋红茶

加一点PHP,加一点Go,加一点Python......

点赞
< 上一篇
下一篇 >

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复

COPYRIGHT © 2021 icexmoon.cn. ALL RIGHTS RESERVED.
本网站由提供CDN加速/云存储服务

Theme Kratos Made By Seaton Jiang

宁ICP备2021001508号

宁公网安备64040202000141号