【记一忘三二】模块基础

84 阅读3分钟

起因

起初没有模块化概念之前,编写多个js文件时,必须按照顺序引入所需要的js文件

<script src="jqery.js"></script>
<script src="./a.js"></script>
<script src="./b.js"></script>

这样的缺点是

  • 全局污染,每个js文件都会向全局作用域外暴露自己的变量和函数。比如:jqery.js会在全局环境暴露$jqery函数,a.js也在全局中暴露同样名称的函数,覆盖了jqery.js全局函数,那么b.js就无法在访问到jqery.js暴露的函数
  • 加载顺序,因为每个模块的执行存在关联性,并且执行和加载js模块都会阻塞js执行线程,所以js的加载顺序必须固定,比如:a.js需要访问jqery.js的方法,那么jqery.js一定要在a.js前面加载运行

闭包

js中,不止全局作用域,还有函数作用域,通过闭包的方式减少向外暴露的变量和方法数量

// a.js
(function (win) {
  win.aModule = {
    data: [1, 2, 3],
  };
})(window);

// b.js
(function (win, aModule) {
  win.bModule = {
    data: aModule.data.concat(5, 6),
  };
})(window, window.aModule);

全局污染得到缓解,但是加载顺序的问题还是无法解决

commonJS

node提供的一套模块化方案,这里需要注意require加载是同步的,也就是在运行到require代码时,才会去加载其他包,并且执行也是同步的,但是这里需要注意每个模块式会被加载一次,以后加载运行都是直接使用缓存

// a.js
module.exports = {
  data: [1, 2, 3],
};

// b.js
const { data } = require("./a.js");

module.exports = {
  data: data.concat([5, 6]),
};

// index.js
const aModule = require("./a.js");
const bModule = require("./b.js");

console.log(aModule.data); // [1, 2, 3]
console.log(bModule.data); // [1, 2, 3, 5, 6]

解决了全局污染和加载顺序的问题,那commonJS可以使用在浏览器端吗?

显然时不能的,在使用require记载模块时会阻塞整个程序,在node端完全不是问题,这是因为运行在后端,但是在浏览器端肯定时不行的,如果浏览器加载很多模块,那白屏或者浏览器卡死的时间就会非常的长,而且网速还是不可预知的

AMD

AMD规范其实是仿照commonJS来的,上面说过之所以在浏览器不能直接使用commonJS规范,是因为commonJS是同步加载同步运行的,但是在浏览器端实现同步加载时不现实的

那么AMD案件出现了,他提出了异步加载并,已经是先加载好每个模块并且执行回去模块值之后,再去运行业务代码

// a.js 
define(function () {
  return {
    data: [1, 2, 3],
  };
});

// b.js
define(["a"], function (aModule) {
  return {
    data: aModule.data.concat([5, 6]),
  };
});


// index.js
require.config({
  paths: {
    a: "./a",
    b: "./b",
  },
});

require(["a", "b"], function (aModule, bModule) {
  console.log(aModule.data); // [1, 2, 3]
  console.log(bModule.data); // [1, 2, 3, 5, 6]
});


<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.6/require.min.js"></script>
    <script src="index.js"></script>
</body>
</html>

实现一个AMD其实也很简单,就是先根据配置加载模块并且运行(require.config.paths就是预先加载模块的配置),在去执行业务代码的回调函数

CMD

上面在讲AMD提到一个现象,就是AMD在加载模块时,还会先运行先运行模块里面的代码,得到模块值,也就是提前执行、依赖前置

CMD觉得可以提前加载模块,但是不应该提前运行模块代码,而应该在使用到这个模块的时候才运行模块代码,也就是延迟执行、依赖就近

但是这里需要注意,无论是AMD还是CMD,都需要提前加载模块,区别在于什么时候执行模块

// a.js
define(function () {
  return {
    data: [1, 2, 3],
  };
});

// b.js
define(function (require) {
  const aModule = require("./a.js");
  return {
    data: aModule.data.concat([5, 6]),
  };
});

// index.js
define(function (require) {
  const aModule = require("./a.js");
  const bModule = require("./b.js");
  console.log(aModule.data); // [1, 2, 3]
  console.log(bModule.data); // [1, 2, 3, 5, 6]
});

<!-- index.html -->

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script src="https://cdn.bootcdn.net/ajax/libs/seajs/3.0.3/sea.js"></script>
    <script type="text/javascript">
        seajs.use('./index.js');
    </script>
</body>

</html>

esModel

上面介绍的都是民间开发自己研究出来的,并不具有系统性、规范性

所以在Es6时,Es规范提出了esModel规范

esModelcommonJS一样使用同步加载依赖包,和commonJS不同的是返回值一个引用地址

// a.js
const data = [1, 2, 3];

export default {
  data,
};

//b.js
import aModule from "./a.js";

const data = aModule.data.concat([4, 5]);

export default {
  data,
};

// index.js
import aModule from "./a.js";
import bModule from "./b.js";

console.log(aModule.data); // [1, 2, 3]
console.log(bModule.data); // [1, 2, 3, 5, 6]
<!-- index.html -->

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script type="module" src="index.js"></script>
</body>

</html>