6

前端最基础的就是 HTML+CSS+Javascript。掌握了这三门技术就算入门,但也仅仅是入门,现在前端开发的定义已经远远不止这些。前端小课堂(HTML/CSS/JS),本着提升技术水平,打牢基础知识的中心思想,我们开课啦(每周四)。

Node 应用由模块组成,采用 CommonJS 模块规范

每个文件就是一个模块,有自己的作用域。在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见

// 导出 addX
module.exports.addX = 1;

// 导入模块
example = require('./example.js');
console.log(example.addX)

commonjs 模块规范

特点

  • 所有代码运行在模块作用域,不会污染全局作用域。
  • 模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。
  • 模块加载的顺序,按照其在代码中出现的顺序。
  • CommonJS 规范加载模块是同步的,也就是说,只有加载完成,才能执行后面的操作。

module对象

Node内部提供一个 Module 构建函数。所有模块都是Module的实例。每个模块内部,都有一个module对象,代表当前模块。它有以下属性。

  • module.id 模块的识别符,通常是带有绝对路径的模块文件名。
  • module.filename 模块的文件名,带有绝对路径。
  • module.loaded 返回一个布尔值,表示模块是否已经完成加载。
  • module.parent 返回一个对象,表示调用该模块的模块。

    if (!module.parent) {
        // ran with `node something.js`
        app.listen(8088, function() {
            console.log('app listening on port 8088');
        })
    } else {
        // used with `require('/.something.js')`
        module.exports = app;
    }
  • module.children 返回一个数组,表示该模块要用到的其他模块。
  • module.exports 表示模块对外输出的值。

AMD 模块规范

CommonJS规范加载模块是同步的,也就是说,只有加载完成,才能执行后面的操作。AMD规范则是非同步加载模块,允许指定回调函数。由于Node.js主要用于服务器编程,模块文件一般都已经存在于本地硬盘,所以加载起来比较快,不用考虑非同步加载的方式,所以CommonJS规范比较适用。但是,如果是浏览器环境,要从服务器端加载模块,这时就必须采用非同步模式,因此浏览器端一般采用AMD规范。

AMD规范使用define方法定义模块,下面就是一个例子:

define(['package/lib'], function(lib){
  function foo(){
    lib.log('hello world!');
  }

  return {
    foo: foo
  };
});

AMD规范允许输出的模块兼容CommonJS规范,这时define方法需要写成下面这样:

define(function (require, exports, module){
  var someModule = require("someModule");
  var anotherModule = require("anotherModule");

  someModule.doTehAwesome();
  anotherModule.doMoarAwesome();

  exports.asplode = function (){
    someModule.doTehAwesome();
    anotherModule.doMoarAwesome();
  };
});

ESM 模块规范

「 ESM 」 全称 ECMAScript modules,基本主流的浏览器版本都以已经支持。主要用于浏览器原生支持模块化<script src="index.js" type="module"></script>

ESM 使用实时绑定逻辑,两个模块都指向内存中的相同位置。这意味着,当导出模块更改值时,该更改将显示在导入模块中。
这不同于 CommonJS 模块,在 CommonJS 中,整个导出对象在导出时被复制。这意味着 CommonJS 模块导出的任何值(例如数字)都是副本,如果 CommonJS 模块导出模块以后更改了该值,则导入模块将看不到该更改。

ESM 如何工作

  1. 浏览器会构建一个依赖关系图
  2. 通过指定一个入口文件<script src="index.js" type="module"></script>),然后从这个文件开始,通过其中的 import 语句,查找其他代码。
  3. 通过指定的文件路径, 浏览器就找到了目标代码文件。 但是浏览器并不能直接使用这些文件,它需要解析所有这些文件,以将它们转换为称为模块记录的数据结构。
  4. 模块记录 转换为 模块实例模块实例, 实际上是 「 代码 」(指令列表)与「 状态」(所有变量的值)的组合。

ESM 动态路径

因为 ESM 的特性,正常情况下是不能使用变量作为路径

// commonjs 其实是同步的,会挂起主线程去加载文件,
// 因为 commonjs 主要用于 node,从本地磁盘加载资源是很快的。
require(`${publicPath}/count.js`);

// 因为 ESM 规范是先静默分析资源去下载,所以其实在这里时变量还没有执行
import count from `${publicPath}/count.js`;

// 但是有时候又希望有这样功能,这个时候我们可以使用import动态导入功能,会启动一个新的加载过程。
import(`${path}/count.js`)

node 中使用

  1. 碰到这种兼容性的,上 babel 就 OK
  2. 社区(Node 14)尝试解决此问题的一种方法是使用 .mjs 扩展。使用该扩展名告诉Node,“此文件是一个模块”。

总结

名称导入关键词导出关键词规范来源&备注
CommonJS、CJSrequire('path')module.exportsNodeJS、同步、加载本地资源同步很快的然后马上执行
ESM、MJSimport a from 'path'import()exportexport default浏览器、异步、因为网络因素不可靠会在所有资源都加载完成才执行
  1. module.exports.foo = 'bar' 命名导出
    module.exports = 'baz' 默认导出。
  2. export const sum = (x, y) => x + y; 命名导出
    export default (x, y) => x + y; 默认导出

微信公众号:前端linong

clipboard.png

参考文献

  1. 前端培训目录、前端培训规划、前端培训计划
  2. https://javascript.ruanyifeng.com/nodejs/module.html
  3. 聊聊 ESM、Bundle 、Bundleless 、Vite 、Snowpack
  4. Node 模块化之争:为什么 CommonJS 和 ES Modules 无法相互协调

linong
29.2k 声望9.5k 粉丝

Read-Search-Ask