JavaScript技术

45.CommonJS详解

2022-03-12  本文已影响0人  静昕妈妈芦培培

什么是模块化?

到底什么是模块化、模块化开发呢?

上面说提到的结构,就是模块按照这种结构划分开发程序的过程,就是模块化开发的过程
无论你多么喜欢JavaScript,以及它现在发展的有多好,它都有很多的缺陷:

Brendan Eich本人也多次承认过JavaScript设计之初的缺陷,但是随着JavaScript的发展以及标准化,存在的缺陷问题基
本都得到了完善。

无论是web、移动端、小程序端、服务器端、桌面应用都被广泛的使用;

模块化的历史

在网页开发的早期,Brendan Eich开发JavaScript仅仅作为一种脚本语言,做一些简单的表单验证或动画实现等,那个时候代码还是很少的:

但是随着前端和JavaScript的快速发展,JavaScript代码变得越来越复杂了:

所以,模块化已经是JavaScript一个非常迫切的需求:

没有模块化带来的问题

早期没有模块化带来了很多的问题:比如命名冲突的问题
当然,我们有办法可以解决上面的问题:立即函数调用表达式(IIFE)

var moduleA = (function () {
  var name = "why";
  var isFlag = true;
  return {
    name,
    isFlag,
  };
})();

./coby/index.js

var moduleB = (function () {
  var name = "coby";
  var age = 40;
  var isFlag = false;
  return {
    name,
    age,
    isFlag,
  };
})();

./index.js

if (moduleA.isFlag) {
  console.log("name is", moduleA.name);
}

if (moduleB.isFlag) {
  console.log("name is", moduleB.name);
}


./index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <script src="./why/index.js"></script>
    <script src="./coby/index.js"></script>
    <script src="./index.js"></script>
  </body>
</html>

但是,我们其实带来了新的问题:

所以,我们会发现,虽然实现了模块化,但是我们的实现过于简单,并且是没有规范的。

CommonJS规范和Node关系

我们需要知道CommonJS是一个规范,最初提出来是在浏览器以外的地方使用,并且当时被命名为ServerJS,后来为了体现它的广泛性,修改为CommonJS,平时我们也会简称为CJS。

所以,Node中对CommonJS进行了支持和实现,让我们在开发node的过程中可以方便的进行模块化开发:

前面我们提到过模块化的核心是导出和导入,Node中对其进行了实现:

CommonJS的基本使用

./foo.js

const name = "why";
const age = 18;
const height = 1.88;

//导出方式一:
module.exports = {
  name,
  age,
  height,
};

//导出方式二:
// exports.name = name;
// exports.age = age;
// exports.height = height;


./index.js

const { name, age, height } = require("./foo.js");
console.log(name, age, height); //why 18 1.88

exports导出

注意:exports是一个对象,我们可以在这个对象中添加很多个属性,添加的属性会导出

exports.name = "why";
exports.age = 18;
exports.height = 1.88;

另外一个文件main.js中可以导入:

const bar = require("./bar");

上面这行完成了什么操作呢?理解下面这句话,Node中的模块化一目了然

module.exports

但是Node中我们经常导出东西的时候,又是通过module.exports导出的:

但是,为什么exports也可以导出呢?

原理:

node中的每一个文件都是一个模块,每个模块都有私有的module.export和exports变量和require函数
在源码中进行了如下操作:

module.exports = {}
exports = modules.exports

也就是说,默认modules.exports和exports指向同一个引用地址,
但是实际上require(x)方法导出的时候,是找到匹配x的文件,导出当前文件中的modules.exports对象
源码大致如下:

function require(id) {
  return modules.exports;
}

require查找规则

我们现在已经知道,require是一个函数,可以帮助我们引入一个文件(模块)中导出的对象。
那么,require的查找规则是怎么样的呢?

情况一:X是一个Node核心模块,比如path、http

情况二:X是以 ./ 或 ../ 或 /(根目录)开头的
第一步:将X当做一个文件在对应的目录下查找;

第二步:没有找到对应的文件,将X作为一个目录

如果没有找到,那么报错:not found

情况三:直接是一个X(没有路径),并且X不是一个核心模块
/Users/coderwhy/Desktop/Node/TestCode/04_learn_node/05_javascript-module/02_commonjs/main.js中编写 require('why’)

image.png

如果上面的路径中都没有找到,那么报错:not found

模块的加载过程

结论一:模块在被第一次引入时,模块中的js代码会被运行一次
结论二:模块被多次引入时,会缓存,最终只加载(运行)一次

./index.js
在index.js文件中多次引用bar.js

const bar = require("./bar");
require("./bar");
require("./bar");
require("./bar");
require("./bar");
require("./bar");
console.log(bar.name, bar.age);

./bar.js

console.log(module.loaded)
exports.name = "why";
exports.age = 18;
image.png

结论三:如果有循环引入,那么加载顺序是什么?
如果出现右图模块的引用关系,那么加载顺序是什么呢?

image.png

CommonJS规范缺点

CommonJS加载模块是同步的:

如果将它应用于浏览器呢?

所以在浏览器中,我们通常不使用CommonJS规范:

在早期为了可以在浏览器中使用模块化,通常会采用AMD或CMD:

AMD规范

AMD主要是应用于浏览器的一种模块化规范:

我们提到过,规范只是定义代码的应该如何去编写,只有有了具体的实现才能被应用:

require.js的使用

第一步:下载require.js

第二步:定义HTML的script标签引入require.js和定义入口文件:*

require.js的使用

<script src="./lib/require.js" data-main="./index.js"></script>
image.png

./index..html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <!-- data-main属性的作用是在加载完src的文件后会加载执行该文件 -->
    <script src="./lib/require.js" data-main="./src/index.js"></script>
  </body>
</html>

./src/index.js

//配置模块及其路径
require.config({
  baseUrl: "./src", //默认为当前文件所在目录
  paths: {
    foo: "./modules/foo",
    bar: "./modules/bar",
  },
});

//使用模块
require(["foo", "bar"], function (foo) {
  console.log(foo.name, foo.age);
});

./src/modules/foo.js

//定义模块
define(function () {
  const name = "why";
  const age = 18;

  //导出模块中的变量
  return {
    name,
    age,
  };
});

./src/modules/bar.js

//定义模块,并且在当前模块中导入foo模块,并使用foo模块中导出的变量
define(["foo"], function (foo) {
  console.log("bar.js", foo.name, foo.age);
});

//写法二:
// define(function (foo) {
//   require(["foo"], function (foo) {
//     console.log("bar.js", foo.name, foo.age);
//   });
// });

CMD规范

CMD规范也是应用于浏览器的一种模块化规范:

第一步:下载SeaJS

第二步:引入sea.js和使用主入口文件

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <script src="./lib/sea.js" ></script>
    <script>
      seajs.use('./src/index.js')
    </script>
  </body>
</html>

./src/index.js

//定义模块
define(function(require, exports, module) {
  //导入foo模块
  const foo = require('./modules/foo')
})

./src/modules/foo.js

//定义模块
define(function (require, exports, module) {
  //导入bar模块
  const bar = require("./bar");
  console.log(bar.name, bar.age);
});

./src/modules/bar.js

//定义模块
define(function (require, exports, module) {
  const name = "why";
  const age = 18;

  //导出变量
  module.exports = {
    name,
    age,
  };
});

上一篇下一篇

猜你喜欢

热点阅读