【悄咪咪学Node.js】6. require/import 模块化

require 模块化

1. 前言

本节课将会引导大家学习了解:

  • 什么是模块化
  • require/exports 怎么实现模块化
  • import/export 怎么实现模块化

学习完本节课程后,应该具有:

  • 使用 require/exports 实现模块化和加载不同依赖库的能力
  • 用 import/export 实现模块化的能力

2. 模块化

模块化概念由来已久,随着前端 JavaScript 脚本代码越来越复杂、庞大,加之 Node.js 出现,支持 JavaScript 成为可用的服务端语言。JavaScript 适应模块化的呼声日益强大。

2.1 什么是模块化编程

模块化编程,就是将实现一个特定功能的代码,分解成若干个独立的、可替换的、具有预定功能的模块。通过调用不同的模块组合来实现不同的实际功能。

2.2 举例

在很多项目中,我们都能看见一个属于该项目自身的工具库,通常都叫 util.js 或者 utils.js。里面有很多不同的工具,例如:

  • 利用正则校验金额的方法
  • 利用正则校验手机号的方法
  • 利用正则校验邮箱地址的方法
  • 接口响应的格式规范方法

这种将一大堆工具放到某一个文件里集中管理,同时也供任何项目代码调用的思想,也是 模块化编程 思想的一种体现。

2.3 模块化编程有什么优势

模块化编程有以下优势:

  • 有利于完成设计:较大较复杂的问题不经过拆分很难找到实现功能的根本路径,将其利用模块化思想拆分成若干个小问题,我们就可以从抽象的模块功能而非复杂功能本身理解。使开发人员能专注于解决每一个小问题,而非被复杂的大问题困扰。
  • 提高开发效率:可以使有经验有能力的开发人员可以专注于核心模块开发,使得一个功能可以多人同时完成。
  • 有利于问题排查:代码出现异常时,能通过由后往前的代码截断来观察实际产生异常的代码段,再由相关人员修复。
  • 易维护:需求出现微调时,可以将微调涉及的模块挑出来调整,不需要修改其他函数,保证稳定性。
  • 可复用:抽象出来的方法,可以不需要任何修改就可以用于另一个需求的开发中。

下面我们来介绍 2 种目前主流的模块化实现方案:require/exportsimport/export

3. require/exports

3.1 require/exports 是什么

require/exports 是用作代码模块化的,采用的是 CommonJS 模块规范,也是 Node.js 10 目前仍主要支持的一种模块化方案。

JavaScript 在 ES6 发布了另一种模块化规范 ------ import/export

这是目前主流的两种模块化方案。

3.2 代码示例

3.2.1 引用

js 复制代码
const fs = require('fs');

3.2.2 导出

第一种:module.exports

js 复制代码
module.exports = {
    exportFn: function() {
        console.log('this is the export function');
    }
}

第二种:exports

js 复制代码
function exportFn() {
    console.log('this is the export function');
}
exports.exportFn = exportFn;

导出有两种写法,那么用 module.exportsexports 有什么区别呢?

先来看看能不能打印出来:

js 复制代码
console.log(module.exports);
console.log(exports);
shell 复制代码
{}
{}

这么看感觉这两个东西很像,再多打印一个判断条件观察下:

js 复制代码
console.log(module.exports === exports);
shell 复制代码
true

由于在 JavaScript 中,对象类型是 引用类型 ,参数实际指向的是存放变量的 内存地址 。操作符===只会对 内存地址 一致的变量返回true

再来看看赋值后的表现:

重新定义 module.exports

js 复制代码
module.exports = {
    fn: function() {
        console.log('this is a export function');
    }
}

console.log(module.exports);
console.log(exports);
console.log(module.exports === exports);
shell 复制代码
{ fn: [Function: fn] }
{}
false

插值到 module.exports

js 复制代码
module.exports.fn = function() {
    console.log('this is a export function');
}

console.log(module.exports);
console.log(exports);
console.log(module.exports === exports);
shell 复制代码
{ fn: [Function] }
{ fn: [Function] }
true

exports

js 复制代码
function fn() {
    console.log('this is a export function');
}
exports.fn = fn;

console.log(module.exports);
console.log(exports);
console.log(module.exports === exports);
shell 复制代码
{ fn: [Function] }
{ fn: [Function] }
true

重新定义 module.exportsexports 混合使用

js 复制代码
function fn() {
    console.log('this is a export function');
}
exports.fn = fn;

module.exports = {
    fn1: function() {
        console.log('this is a export function 1');
    }
}

console.log(module.exports);
console.log(exports);
console.log(module.exports === exports);
shell 复制代码
{ fn1: [Function] }
{ fn: [Function] }
false

插值到 module.exportsexports 混合使用

js 复制代码
function fn() {
    console.log('this is a export function');
}
exports.fn = fn;

module.exports.fn1 = function() {
    console.log('this is a export function 1');
}

console.log(module.exports);
console.log(exports);
console.log(module.exports === exports);
shell 复制代码
{ fn: [Function: fn], fn1: [Function] }
{ fn: [Function: fn], fn1: [Function] }
true

我们可以总结出以下规则:

  • exportsmodule.exports 的引用,它们指向相同的 内存地址
  • 使用 重新定义 module.exports 导出时,使用了新开辟的 内存空间 ,使得旧的引用 exports 失效,使用 exports 导出的模块 和在此之前使用 插值到 module.exports 导出的模块也会失效。

建议不要使用 重新定义 module.exports 的方式来导出,因为这样会丢失 exports 的正确引用,导致部分引用丢失的问题。

Tips:module.exportsexports 没有本质区别,exports 是 Node.js 为了方便导出而默认定义的引用。但是以 module.exports 为准,因为他所用的 内存空间 为实际导出模块的 内存空间

3.3 调用时机

require 是运行时调用,属于动态加载,所以可以用在代码的任何一个地方,如:

js 复制代码
if (1 + 1 == 2) {
    const fs = require('fs');
}

3.4 工作机制

require 实际上就是在赋值,或者说浅复制(引用类型只复制内存地址,而不新开辟内存存值)。

由于 require 实际上是在赋值,所以就算我们不需要使用该模块所有的导出函数,我们都需要加载整个模块的函数,再提取出需要的函数,如:

js 复制代码
const {readFileSync, writeFileSync} = require('fs');

这时,先会加载整个 fs 模块,生成 _fs 对象,再在这个对象中取出 readFileSyncwriteFileSync 函数。

4. import/export

上文提到 import/export 在 ES6 成为模块化标准,这证明了 require/exports 只是 Node.js 私有的方法。

而在 Node.js 9.0 及以上版本,Node.js 官方支持了用一种比较微妙的方法使用 import/export

但是在笔者现在撰写文档的时空中,Node.js 发布的最新开发版为 V 13.11.0 import/export 的支持仍在实验阶段。但 import/export 有可能成为若干版本后的 Node.js 使用的模块化工具,在这里也讲解一下 import/export 的内容。

4.1 import/export 怎么使用

由于这是实验阶段特性,本段内容有可能在日后的重大更新中失真

在 Node.js V 13.11.0 中,如果需要使用 import/export 作为模块化工具,则文件后缀名要改成 .mjs 来标识这是符合 ES 标准的模块。

4.2 代码示例

4.2.1 引入

js 复制代码
// 引入默认模块
import fs from 'fs';

// 引入命名模块
import { readFileSync, writeFileSync } from 'fs';

// 引入所有模块
import * as fs from 'fs';

Tips:根据 ES6 的规定,import 必须在文件开头引入,它前面不可以有任何逻辑代码。

但由于和 require 相比,失去了灵活性。所以 import 提供了 import() 函数来支持 动态加载

import() 函数返回一个 promise 对象

js 复制代码
if (1 + 1 == 2) {
    import('fs')
        .then(function(fs) {
            const str = fs.readFileSync('./none_js.txt', 'utf8');
            
            console.log(str);
        })
}

4.2.2 导出

default 导出默认模块

js 复制代码
export default {
    name: 'none_js',
    explainNode: function() {
        console.log('Node.js is a backend runtime of javaScript');
    }
}

导出命名模块

js 复制代码
export function fn1() {
    console.log('this is another function');
}
js 复制代码
function fn2() {
    // todo...
}

export { fn2 }

导出默认模块时如果没有命名,引入时能自定义一个名称。

4.3 调用时机

require 不同,import 是编译时调用,属于静态加载,如果引入的模块不存在,将在编译时报错。

理论上 import 只能用在文件头部,如:

js 复制代码
import fs from 'fs';

import 提供 import() 函数,来支持动态加载,如:

js 复制代码
if (true) {
    import('fs')
        .then(function(fs) {
            // todo...
        })
}

4.4 工作机制

import 实际上是在做解构操作,所以我们不需要使用该模块所有的导出函数,我们就不需要加载整个模块的函数,如:

js 复制代码
import { readFileSync, writeFileSync } from 'fs';

这时,会只从 fs 模块中 readFileSyncwriteFileSync 函数,其他函数不加载。

require 比较而言,import 对性能更加友好。

5. 小结

本节课程我们主要学习了 模块化编程require/exportsimport/export

重点如下:

  1. 重点1

    模块化编程在复杂系统中十分重要,其优点在于:有利于完成设计提高开发效率有利于问题排查易维护可复用

  2. 重点2

    require/exports属于动态加载,require 用于引入,exports 用于导出,是 module.exports 的引用,最终以 module.exports 实际指向的对象为准。

  3. 重点3

    import/export 属于静态加载,是 ES6 提出的标准,Node.js 正在实验其落地的可能性,理论上,其性能要强于 require/exports

相关推荐
李长渊哦4 小时前
深入理解 JavaScript 中的全局对象与 JSON 序列化
开发语言·javascript·json
Senar6 小时前
如何判断浏览器是否开启硬件加速
前端·javascript·数据可视化
codingandsleeping6 小时前
一个简易版无缝轮播图的实现思路
前端·javascript·css
拉不动的猪7 小时前
简单回顾下插槽透传
前端·javascript·面试
爱吃鱼的锅包肉8 小时前
Flutter路由模块化管理方案
前端·javascript·flutter
风清扬雨8 小时前
Vue3具名插槽用法全解——从零到一的详细指南
前端·javascript·vue.js
海盗强8 小时前
Vue 3 常见的通信方式
javascript·vue.js·ecmascript
oscar9999 小时前
JavaScript与TypeScript
开发语言·javascript·typescript
橘子味的冰淇淋~9 小时前
【解决】Vue + Vite + TS 配置路径别名成功仍爆红
前端·javascript·vue.js
leluckys10 小时前
flutter 专题 六十三 Flutter入门与实战作者:xiangzhihong8Fluter 应用调试
前端·javascript·flutter