blog icon indicating copy to clipboard operation
blog copied to clipboard

快速理解JS模块化

Open thinkerchan opened this issue 6 years ago • 0 comments

为什么需要模块化

随着网站逐渐变成”互联网应用程序”,嵌入网页的Javascript代码越来越庞大,越来越复杂。一单产生了js依赖关系, 那么完全靠手工去对js文件做顺序加载就是不太现实的方案, 所以. Javascript模块化编程,已经成为一个迫切的需求。理想情况下,开发者只需要实现核心的业务逻辑,其他都可以加载别人已经写好的模块。

ES6之前, JS是没有模块化的明确实现的 ,全靠开发人员的聪明才智来实现模块化.

原始方案

函数

最初实现模块化的思路就是一个简单的函数封装, 以区别于不同的功能, 类似这样:

function m1(){
  //...
}
function m2(){
  //...
}

这也有很明显的缺点: ”污染”了全局变量,无法保证不与其他模块发生变量名冲突,而且模块成员之间看不出直接关系. 不过总聊胜于无.

对象封装

为了优化上述的问题, 开发人员有提出了另一种思路:

var module1 = {
  _count : 0,
  m1 : function (){
    //...
  },
  m2 : function (){
    //...
  }
}

比起第一种方法, 这种对象封装的方式就更加简洁明了, 以至于函数之间可以拆分得更细, 功能更加单一. 但是同样地有缺点: 所有的属性都能被外部调用和修改. 所以开发人员用下划线开头的属性表明它不希望被外部访问或者修改.

立即执行函数

IIFE(立即执行函数)的出现, 彻底解决了上述私有变量的问题. 所以在各种模块加载器或者ES6之前, 它都是最广泛的JS模块化方案:

var module1 = (function(){
  var _count = 0;
  var m1 = function(){
    //...
  };
  var m2 = function(){
    //...
  };
  return {
    m1 : m1,
    m2 : m2
  };
})();

社区方案

为了实现模块化, CommonJS/AMD/CMD等对应社区也做了贡献.

Commonjs

该规范最初是用在服务器端的node的,它有四个重要的环境变量为模块化的实现提供支持:module、exports、require、global。实际使用时,用module.exports定义当前模块对外输出的接口(不推荐直接用exports,exports==module.exports),用require加载模块(同步)

// a-commonJs.js
var a = 5;
var add = function(param){
  return a + param
}

module.exports.a = a;
module.exports.add = add;

// module.exports本身是一个对象, 所以也可以这么写:
module.exports = {
	_a: a,
	_add: add
}

// b-commonJs.js
var addFn = require('./a-commonJs');
console.log(addFn.add(3)) //8
console.log(addFn.a) //5

**优点:**解决了依赖、全局变量污染的问题 缺点: CommonJS用同步的方式加载模块。在服务端,模块文件都存在本地磁盘,读取非常快,所以这样做不会有问题(所以非常适合nodejs)。但是在浏览器端,限于网络原因,CommonJS不适合浏览器端模块加载,更合理的方案是使用异步加载

AMD

AMD自然而然就是为了解决上面的问题而存在的. AMD的具体实现也就是我们常见的require.js

AMD标准中,定义了下面三个API:

  1. define(id, [depends], callback)
  2. require([module], callback)
  3. require.config()

即通过define来定义一个模块,然后使用require来加载一个模块, 使用require.config()指定引用路径。

先到 https://requirejs.org/ 下载最新版本,然后引入到页面,如下:

// data-main属性不能省
<script data-main="app" src="./require.js"></script>
//app.js
define(function () {
    function sayName(name){
      console.log("I'm ",name);
    }
    function sayAge(age){
      console.log("I'm ",age );
    }

    return {
        sayName: sayName,
        sayAge: sayAge
    }
});


// 调用
require(['app'],function(app) {
      app.sayName('testdog')
      app.sayAge(18)
    })

在使用require.js的时候,我们必须要提前加载所有的依赖,然后才可以使用,而不是需要使用时再加载。

更多教程点击: https://www.html.cn/doc/requirejs/

**优点:**适合在浏览器环境中异步加载模块、并行加载多个模块 **缺点:**不能按需加载、开发成本大

CMD

AMD 推崇依赖前置、提前执行,CMD推崇依赖就近、延迟执行, 也就是做到了按需加载. CMD 是 SeaJS 在推广过程中对模块定义的规范化产出。

教程可查看: https://seajs.github.io/seajs/docs/

不过早在2015年, 它的作者已经表明态度, 迟早会被淘汰.

玉伯评论seajs

ES6模块

ES6 模块化方案是最规范的方案, 未来也是主流,不过现在的浏览器还不兼容, 使用需要 babel 转码.

它有以下功能模块:

  • export 导出模块
  • import 导入模块
//app.js
function sayName (str) {
  console.log("I'm ",str);
}
export default sayName = sayName;
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>es6模块</title>
</head>
<body>
  <script type="module">
    import sayName from './es6/app.js'
    sayName('testdog')
  </script>
</body>
</html>

其他

我相信刚接触AMD/CMD/Node/ES6模块的同学们看到一大堆关键字会感到混乱和难以区分, 只要基础 import/export(记住是不带s的), 就是ES6(浏览器)的.

thinkerchan avatar Apr 28 '19 13:04 thinkerchan