JavaScript学习笔记-模块儿和导入导出语法

JavaScript 开发业务的过程中肯定会引入很多第三方库,和别的语言类似,JS 也有一套引入第三方库的方式,即「模块」。

将代码拆分成独立的块,然后再把这些块连接起来可以通过模块模式来实现。这种模式背后的思想 很简单:把逻辑分块,各自封装,相互独立,每个块自行决定对外暴露什么,同时自行决定引入执行哪 些外部代码。不同的实现和特性让这些基本的概念变得有点复杂,但这个基本的思想是所有 JavaScript 模块系统的基础。

# ES Module VS CommnJS Module

如果项目是 Node,那默认使用的是 CommonJS Module。 JS(ECMAScript) Module 就是官方标准的模块儿化系统。我最开始甚至都没有意识到这两种 Module System 的区别。

Node.js 都支持这两种模块系统:CommonJS 和 ES modules。

# CommonJS 介绍

是 Node.js 最初采用的模块系统,它使用 require() 函数来加载模块,使用 module.exportsexports 对象来导出模块。CommonJS 模块是同步加载的,每个模块都有一个单独的作用域,模块中定义的变量、函数、对象都不会对其他模块造成影响。

# ES modules 介绍

是 ECMAScript 6 中引入的模块系统,它使用 import 关键字来加载模块,使用 export 关键字来导出模块。ES modules 是异步加载的,每个模块都有一个单独的作用域,模块中定义的变量、函数、对象也不会对其他模块造成影响。与 CommonJS 不同的是,ES modules 支持 Tree Shaking 和静态分析等高级特性,能够更好地优化代码和性能。

Node.js 从版本 13 开始支持原生 ES modules,但默认情况下仍然采用 CommonJS 模块系统。开发者可以在 Node.js 中通过文件扩展名来指定要使用的模块系统,如 .mjs 扩展名表示该模块是 ES module,.js 扩展名表示该模块是 CommonJS 模块。另外,Node.js 也提供了 –experimental-modules 命令行选项来启用原生 ES modules 支持。

# ES Module 导入导出语法

导入导出语法介绍如下

# 默认导出/导出

默认导出即 export default

export default 是 JavaScript 中用于导出模块内容的语法,允许在一个模块中指定默认导出。通过 export default,可以将任何类型的值(函数、类、对象、原始数据类型等)作为模块的默认导出。 使用注意

  1. 模块的默认导出只能有一个,默认导出的值可以是任意类型

    export let name = 'fanthus';  
    export let age = 'fanthus';  
    export default name;
    export default age;
    SyntaxError: Identifier '.default' has already been declared
    
  2. 在导入时可以自定义其名称(例如上面例子中的 defaultExport 可以被赋予任意合法的标识符名)。

    //b.js
    export let name = 'fanthus';  
    export default name;
    //a.js
    import hello from './b.js'   //这儿把 name 改为 hello
    console.log(hello)  
    
  3. export default const time = new Date().getTime(); 这种导出方式是有问题的,只有表达式、函数或类才允许被 export default 导出,而代码里出现的是变量声明。

  4. 还有一种默认导出的方式如下,其实本质上是导出对象,只不过是省去了对象定义的过程。

    export default {
      name:'banana',
      age:18,
      eat: () => {
    	  console.log('I like eating bananas')
      }
    }
    
  5. export default 如果导入时使用匿名重命名函数,则会报错找不到,代码如下

    //b.js
    const handle = 'John Doe';
    export default handle;
    //a.js
    import * as handle from './b.js'
    console.log(`xx ${handle}`) 
    **#编译标错 TypeError: Cannot convert object to primitive value**
    

# 命名导出

命名导出(Named Export)是 JavaScript 中模块化的一种方式,它允许在一个模块中将多个变量、函数、类或对象导出,并为每个导出项指定一个名称。

举个例子

// math.js
export const PI = 3.14159;
export function add(a, b) {
  return a + b;
}
export const stu = {
  "name": "fanthus",
  "stores": 19
}
// app.js
import { PI, add, stu } from './math.js';
console.log(PI); // 输出: 3.14159
console.log(add(2, 3)); // 输出: 5
console.log(JSON.stringify(stu)); //{"name":"fanthus","stores":19}

使用注意

  1. 命名导出允许我们以明确的方式导出模块的特定功能,比如上面这种导入可以去掉 stu,直接写 import { PI, add } from './math.js';
  2. 命名导出对应的导入,即这里 import 是需要加大括号的。这是因为在一个模块中可以同时有多个命名导出,为了准确指定我们需要导入哪些内容,我们需要使用大括号进行明确的标识。
  3. 命名导出对应的命名导入,导入的名字应该是和导出时候的名字是对应的,比如 export const add 对应的导入就是 import { add } from ’./xx.js’。add 变量名字得对的上。

# 变量全部导出

这种导出语法即可以写作是默认导出,也能作为命名导出。具体语法如下

//b.js
const name = 'John Doe';
const age = 30;
export { name, age };
//a.js
import * as handle from './b.js'   
console.log(handle.name) // banana

这里 import 使用 * 因为 export 并没有使用默认导出的方式,而命名导出又不知道具体的名字,所以使用 * 符号表示匿名对象,并使用 as 重命名来访问匿名导出对象。

当然上面的导出也可以直接写成默认导出:

//b.js
export default { name, age }
//a.js
import  handle from './b.js'
console.log(handle.name) // banana

# 默认导出和命名导出混合使用

默认导出可以与命名导出(named exports)同时存在。在导入时,可以通过 import 语句同时导入默认导出和命名导出。

举个例子

// b.js
const name = 'John Doe';
export default name;
export const age = 30;
// a.js
import defaultExport, { age } from './b.js';
console.log(defaultExport); // 输出: John Doe
console.log(age); // 输出: 30

# CommonJS 导入导出语法

CommonJS 导出语法 module.exportsexports。导入语法主要是 require

//NodeExport.js
module.exports = new Date().getTime();
//file.js
const nodetime = require("./NodeExport.js”)
console.log(nodetime);

# 工程实践

前面说 Node.js 项目默认使用的是 CommonJS 模块,如果想要改为 ESModule,则需要在 package.json 中添加一个配置 {"type": “module”}

添加配置后,工程里的所有 JS 文件里可以使用 ES Module 的语法(import, export),同时工程将不再识别 CommonJS 的语法(require,module.exports)。

#

感觉 JavaScript 各种导入导出语法搞的有点太灵活了,之前学 Objective-C 和 Swift 哪用学这么多导入导出的知识啊,导包非常方便。为啥 JS 要这么设计啊?可能是为了轻量应用?

参考地址:

  1. JavaScript 高级程序设计-Chapter26 Module
  2. MDN-JavaScript 模块 (opens new window)
  3. When should I use curly braces for ES6 import? (opens new window)
  4. module.exports vs. export default in Node.js and ES6 (opens new window)
  5. ES6-Web 开发技术>JavaScript>JavaScript 参考> 语句和声明>export (opens new window)
  6. 前端的导入导出:「CommonJS」「ES Module」模块化规范 (opens new window)