• 第二十四节: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 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
     
  • 相关阅读:
    css伪类函数 :is() 和 :where()
    卷积层 / 全连接层 的空间和计算复杂度
    mysql中分组获取前三条记录的方法
    sql server数据去重复的四种方法
    C#获取dynamic(动态)实体的属性值
    Sqlserver使用游标循环插入,把上一个select语句的结果当成value值的一部分insert到一张表
    红玫瑰与白玫瑰
    解决vue3中通过params传值以后,刷新页面,params丢失问题。
    mybatis 注解的方式查询
    了解Android已发布的各种版本(即1.0、2.0、3.0、4.0、5.0、6.0、7.0、8.0)
  • 原文地址:https://www.cnblogs.com/yaopengfei/p/16140812.html
Copyright © 2020-2023  润新知