• 12【module的语法】


    module的语法

    1.初识Module

    (1)什么是模块

    模块:一个一个的局部作用域的代码块。

    (2)什么是模块系统

    模块系统:系统的解决了模块化一系列问题。

    1. 模块化的写法(之前我们用立即执行函数模拟模块化,ES6 则实现了针对模块化的语法)
    2. 消除全局变量(模块中的变量都是局部的,不同模块之间不会相互干扰,可以通过特定语法暴露指定内容)
    3. 管理加载顺序(之前我们将一个总的 JavaScript 程序分几个文件写,但在最终合并调用时,js 的引入需要满足前后依赖关系。比如:被引用的 js 文件就一定要在引用它的 js 文件之前加载)

    2.Module的基本用法

    注意:Module 要生效,必须在服务器环境下才能执行。

    普通的 HTML、JS 是本地文件环境,地址以 file 协议开头,服务器则以 http 或 https 开头。

    方法:VSCode 中使用 Live Server 拓展,WebStorm 默认就是服务器环境。

    • 一个 JS 文件就是一个模块
    • 用 import 关键字导入模块
    • 用 export 关键字导出模块需要暴露的部分
    • 在使用 script 标签加载的时候,需要加上 type=“module”,否则就以普通 JS 文件的形式引入了,就不是模块了

    3.Module的导入导出

    导出的东西可以被导入(import),并访问到!

    对于导入和导出有两种方法:

    • export default 导出,import 导入
    • export 导出,import 导入

    3.1 export default 导出和对应的 import 导入

    (1)一个模块没有导出,是否可以将其导入?

    
    
    DOCTYPE html>
    <html lang="zh-CN">
    <head>
        <meta charset="UTF-8">
        <title>Moduletitle>
    head>
    <body>
    
    <script type="module">
        import "./test.js";		// 浏览器控制台打印:test
        import "./test.js";		// 不执行
        import "./test.js";		// 不执行
        import "./test02.js";	// 浏览器控制台打印:test02
        import "./test.js";		// 不执行
        import "./test02.js";	// 不执行
    script>
    body>
    html>
    
    ----------------------------------------------------
    
    
    console.log("test");
    
    ----------------------------------------------------
    
    
    console.log("test02");
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    (2)一个模块中只能有一个 export default。

    【module.js】

    // 模块中的变量都是局部的
    const age = 18;
    const sex = "male";
    
    export default age;			// 通过 export default 导出(暴露)一个值
    // export default sex;		// 报错!因为 export default 只能在一个文件中导出一次!!!
    
    /*
    export default 24;				// 可以导出值
    export default {};				// 可以导出对象
    export default function(){};	 // 可以导出函数
    export default class{};			 // 可以导出class
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    【index.html】

    DOCTYPE html>
    <html lang="zh-CN">
    <head>
        <meta charset="UTF-8">
        <title>Moduletitle>
    head>
    <body>
    
    <script type="module">
        // import 之后跟一个模块的别名,推荐别名与导出时的名字相同,比如这里就用 age
        import age from "./module.js";
        console.log(age);	// 18
    script>
    body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    3.2 export 导出和对应的 import 导入

    (1)基本用法

    【module.js】

    /*
    const age = 18;
    export age;		// 报错
    */
    
    // export 后面只能跟声明或语句!
    export const age = 18;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    【index.html】

    DOCTYPE html>
    <html lang="zh-CN">
    <head>
        <meta charset="UTF-8">
        <title>Moduletitle>
    head>
    <body>
    
    <script type="module">
        // import aaa from "./module.js";	// 报错! 
        // export 导出的模块,在导入时不能随意取别名,名称必须与模块导出时相同!并且要使用类似于解构赋值的{}形式!
        
        import {age} from "./module.js";	// 注意:名称不能随意取,一定要与模块相同
        console.log(age);	// 18;
    script>
    body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    注意:在用 export 导出时,也可以用对象简写形式形式!

    【module.js】

    const age = 18;
    
    export {age};
    
    • 1
    • 2
    • 3

    (2)多个导入

    【module.js】

    // 1、采用声明或语句的形式
    /*
    export funciton fn() {};
    export class className {};
    export const age = 18;
    */
    
    // 2、采用解构赋值的形式
    function fn() {};
    class className {};
    const age = 18;
    
    /* 方式 1:
    export {fn};
    export {className};
    export {age};
    */
    
    // 方式 2:
    export {fn, className, age};
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    【index.html】

    DOCTYPE html>
    <html lang="zh-CN">
    <head>
        <meta charset="UTF-8">
        <title>Moduletitle>
    head>
    <body>
    
    <script type="module">
        /* 方式 1:
        import {fn} from "./module.js";
        import {className} from "./module.js";
        import {age} from "./module.js";
        */
        // 方式 2:
        import {fn, className, age} from "./module.js";
        console.log(fn);			// ƒ fn() {}
        console.log(className);		// class className {}
        console.log(age);	    	// 18
    script>
    body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    (3)导出导入时起别名

    export {fn as func, className as cN, age};
    
    • 1
    import {func, cN, age as nl} from "./module.js";
    console.log(func);			// ƒ fn() {}
    console.log(cN);			// class className {}
    console.log(nl);	    	// 18
    
    • 1
    • 2
    • 3
    • 4

    (4)整体导入

    // 之前的导入方式,如果导入的模块不多那么还好,但是一但模块数量多了起来,那么就特别费劲
    // import {fn, className, age} from "./module.js";
    
    // 整体导入
    // 将同一文件里的所有模块导入到一个对象中
    // 不仅对 export 有效,同时对 export default 也同样有效
    import * as imObj from "./module.js";
    console.log(imObj);					// 见图片
    console.log(imObj.fn);				// ƒ fn() {}
    console.log(imObj.className);		// class className {}
    console.log(imObj.age);				// 18
    // export default 也同样有效:imObj.default
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    image-20220602154710981

    (5)同时导入

    当我们需要分别导入 export default 和 export 时,可以使用同时导入的方式。

    // 我们可以分开实现
    import {fn, className, age} from "./module.js";
    import sex from "./module.js";
    
    • 1
    • 2
    • 3
    // 更推荐使用同时导入的方式
    import sex, {fn, className, age} from "./module.js";
    // 注意:export default 必须在 export 之前
    
    • 1
    • 2
    • 3

    4.export 与 import 的复合写法

    如果在一个模块之中,先输入后输出同一个模块,import语句可以与export语句写在一起。

    export { foo, bar } from 'my_module';
    
    // 可以简单理解为
    import { foo, bar } from 'my_module';
    export { foo, bar };
    
    • 1
    • 2
    • 3
    • 4
    • 5

    上面代码中,exportimport语句可以结合在一起,写成一行。但需要注意的是,写成一行以后,foobar实际上并没有被导入当前模块,只是相当于对外转发了这两个接口,导致当前模块不能直接使用foobar

    模块的接口改名和整体输出,也可以采用这种写法。

    // 接口改名
    export { foo as myFoo } from 'my_module';
    
    // 整体输出
    export * from 'my_module';
    
    • 1
    • 2
    • 3
    • 4
    • 5

    默认接口的写法如下。

    export { default } from 'foo';
    
    • 1

    具名接口改为默认接口的写法如下。

    export { es6 as default } from './someModule';
    
    // 等同于
    import { es6 } from './someModule';
    export default es6;
    
    • 1
    • 2
    • 3
    • 4
    • 5

    同样地,默认接口也可以改名为具名接口。

    export { default as es6 } from './someModule';
    
    • 1

    ES2020 之前,有一种import语句,没有对应的复合写法。

    import * as someIdentifier from "someModule";
    
    • 1

    ES2020补上了这个写法。

    export * as ns from "mod";
    
    // 等同于
    import * as ns from "mod";
    export {ns};
    
    • 1
    • 2
    • 3
    • 4
    • 5

    5.import()

    5.1 简介

    前面介绍过,import命令会被 JavaScript 引擎静态分析,先于模块内的其他语句执行(import命令叫做“连接” binding 其实更合适)。所以,下面的代码会报错。

    // 报错
    if (x === 2) {
      import MyModual from './myModual';
    }
    
    • 1
    • 2
    • 3
    • 4

    上面代码中,引擎处理import语句是在编译时,这时不会去分析或执行if语句,所以import语句放在if代码块之中毫无意义,因此会报句法错误,而不是执行时错误。也就是说,importexport命令只能在模块的顶层,不能在代码块之中(比如,在if代码块之中,或在函数之中)。

    这样的设计,固然有利于编译器提高效率,但也导致无法在运行时加载模块。在语法上,条件加载就不可能实现。如果import命令要取代 Node 的require方法,这就形成了一个障碍。因为require是运行时加载模块,import命令无法取代require的动态加载功能。

    const path = './' + fileName;
    const myModual = require(path);
    
    • 1
    • 2

    上面的语句就是动态加载,require到底加载哪一个模块,只有运行时才知道。import命令做不到这一点。

    ES2020提案 引入import()函数,支持动态加载模块。

    import(specifier)
    
    • 1

    上面代码中,import函数的参数specifier,指定所要加载的模块的位置。import命令能够接受什么参数,import()函数就能接受什么参数,两者区别主要是后者为动态加载。

    import()返回一个 Promise 对象。下面是一个例子。

    const main = document.querySelector('main');
    
    import(`./section-modules/${someVariable}.js`)
      .then(module => {
        module.loadPageInto(main);
      })
      .catch(err => {
        main.textContent = err.message;
      });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    import()函数可以用在任何地方,不仅仅是模块,非模块的脚本也可以使用。它是运行时执行,也就是说,什么时候运行到这一句,就会加载指定的模块。另外,import()函数与所加载的模块没有静态连接关系,这点也是与import语句不相同。import()类似于 Node.js 的require()方法,区别主要是前者是异步加载,后者是同步加载。

    由于import()返回 Promise对象,所以需要使用then()方法指定处理函数。考虑到代码的清晰,更推荐使用await命令。

    async function renderWidget() {
      const container = document.getElementById('widget');
      if (container !== null) {
        // 等同于
        // import("./widget").then(widget => {
        //   widget.render(container);
        // });
        const widget = await import('./widget.js');
        widget.render(container);
      }
    }
    
    renderWidget();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    上面示例中,await命令后面就是使用import(),对比then()的写法明显更简洁易读。

    5.2 适用场合

    下面是import()的一些适用场合。

    (1)按需加载。

    import()可以在需要的时候,再加载某个模块。

    button.addEventListener('click', event => {
      import('./dialogBox.js')
      .then(dialogBox => {
        dialogBox.open();
      })
      .catch(error => {
        /* Error handling */
      })
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    上面代码中,import()方法放在click事件的监听函数之中,只有用户点击了按钮,才会加载这个模块。

    (2)条件加载

    import()可以放在if代码块,根据不同的情况,加载不同的模块。

    if (condition) {
      import('moduleA').then(...);
    } else {
      import('moduleB').then(...);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    上面代码中,如果满足条件,就加载模块 A,否则加载模块 B。

    (3)动态的模块路径

    import()允许模块路径动态生成。

    import(f())
    .then(...);
    
    • 1
    • 2

    上面代码中,根据函数f的返回结果,加载不同的模块。

    5.3 注意点

    import()加载模块成功以后,这个模块会作为一个对象,当作then方法的参数。因此,可以使用对象解构赋值的语法,获取输出接口。

    import('./myModule.js')
    .then(({export1, export2}) => {
      // ...·
    });
    
    • 1
    • 2
    • 3
    • 4

    上面代码中,export1export2都是myModule.js的输出接口,可以解构获得。

    如果模块有default输出接口,可以用参数直接获得。

    import('./myModule.js')
    .then(myModule => {
      console.log(myModule.default);
    });
    
    • 1
    • 2
    • 3
    • 4

    上面的代码也可以使用具名输入的形式。

    import('./myModule.js')
    .then(({default: theDefault}) => {
      console.log(theDefault);
    });
    
    • 1
    • 2
    • 3
    • 4

    如果想同时加载多个模块,可以采用下面的写法。

    Promise.all([
      import('./module1.js'),
      import('./module2.js'),
      import('./module3.js'),
    ])
    .then(([module1, module2, module3]) => {
       ···
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    import()也可以用在 async 函数之中。

    async function main() {
      const myModule = await import('./myModule.js');
      const {export1, export2} = await import('./myModule.js');
      const [module1, module2, module3] =
        await Promise.all([
          import('./module1.js'),
          import('./module2.js'),
          import('./module3.js'),
        ]);
    }
    main();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
  • 相关阅读:
    Leetcode 40. 组合总和 II
    如何设置微信自动回复?教你快速上手!
    【Python 千题 —— 基础篇】读取字符串
    生活旅游数据恢复:全国违章查询
    php内核基础说明
    Nginx之静态文件服务器的搭建
    Python工程化管理:package包层次结构组织以及module模块详解
    第九章 常用服务器的搭建
    SpringBoot实践(三十三):Maven使用及POM详解
    走进上海交大丨用 DolphinDB 开启你的量化交易职业生涯
  • 原文地址:https://blog.csdn.net/DSelegent/article/details/126558676