【悄咪咪学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

相关推荐
NiNg_1_2342 分钟前
Vue3 Pinia持久化存储
开发语言·javascript·ecmascript
读心悦3 分钟前
如何在 Axios 中封装事件中心EventEmitter
javascript·http
神之王楠36 分钟前
如何通过js加载css和html
javascript·css·html
余生H41 分钟前
前端的全栈混合之路Meteor篇:关于前后端分离及与各框架的对比
前端·javascript·node.js·全栈
流烟默1 小时前
Vue中watch监听属性的一些应用总结
前端·javascript·vue.js·watch
茶卡盐佑星_1 小时前
meta标签作用/SEO优化
前端·javascript·html
与衫1 小时前
掌握嵌套子查询:复杂 SQL 中 * 列的准确表列关系
android·javascript·sql
Ink1 小时前
从底层看 path.resolve 实现
前端·node.js
金灰2 小时前
HTML5--裸体回顾
java·开发语言·前端·javascript·html·html5
Promise5202 小时前
总结汇总小工具
前端·javascript