JS通用模块写法

邱秋 • 2018年07月31日 • 阅读:1828 • javascript

模块化这个问题并非一开始就存在,WWW 刚刚问世的时候,html,JavaScript,CSS(JSCSS 都是后来在网景被引进浏览器的)都是极其简单的存在,不需要模块化。

模块化的需求是规模的产物,当 web page 进化到 web application,浏览器端处理的逻辑越来越复杂,展现的样式和动画越来多,对于工程的要求也就越来越高。于是模块化的需求也就产生了。模块化的意义:

  • 组件的复用,降低开发成本和维护成本
  • 组件单独开发,方便分工合作
  • 模块化遵循标准,方便自动化依赖管理,代码优化,部署

JavaScript 长久以来被认为是简单的脚本语言,实际上情况早就发生来变化,在最新版的 ECMA-262(ES6)文档中强调 JavaScript 是通用编程语言而不是脚本语言。脚本语言,比如 shell 并不是用来完成复杂功能的,只是用来做一些自动化控制,是不需要模块化的。而用于构建复杂系统通用编程语言(比如 Java)一般都有模块的实现。

ES6 以前,JS 语言没有模块化,如何让 JS 不止运行在浏览器,且能更有效的管理代码,

于是应运而生 CommonJS 这种规范,定义了三个全局变量:

require,exports,module
  • require 用于引入一个模块
  • exports 对外暴露模块的接口,可以是任何类型
  • module 是这个模块本身的对象

require 引入时获取的是这个模块对外暴露的接口(exports

Node.js 使用了 CommonJS 规范:

var foo = require("foo");
var out = foo.sayName();
module.exports = out;

在浏览器端,不像 Node.js 内部支持 CommonJS,如何进行模块化,

于是出现了 CMDAMD 两种方式,其主要代表是 seajsrequirejs

他们都定义了一个全局函数 define 来创建一个模块:

//CMD
define(function (require, exports, module) {
  var foo = require("foo");
  var out = foo.sayName();
  module.exports = out;
});

//AMD
define(["foo"], function (foo) {
  var out = foo.sayName();
  return out;
});

可以看出 CMD 完好的保留了 CommonJS 的风格,

AMD 用了一种更简洁的依赖注入和函数返回的方式实现模块化。

两者除风格不同外最大区别在于加载依赖模块的方式,

CMD 是懒加载,在 require 时才会加载依赖,

AMD 是预加载,在定义模块时就提前加载好所有依赖。

我们要实现一个模块,让它既能在 seajs(CMD)环境里引入,又能在 requirejs(AMD)环境中引入,当然也能在 Node.jsCommonJS)中使用,另外还可以在没有模块化的环境中用 script 标签全局引入.

首先搞一个模块

var factory = function () {
  var moduleName = {};
  return moduleName;
};

当然 return 输出的可以是任何值,对象,类,其他都可以

首先满足 Node.js 或者 ES6,我们可以通过全局变量 moduleexports 来判断

var factory = function () {
  var moduleName = {};
  return moduleName;
};
if (typeof module !== "undefined" && typeof exports === "object") {
  module.exports = factory;
}

CMDAMD 中,我们需要提供一个工厂函数传入 define 来定义模块,当没有上述全局变量,且有 define 全局变量时,我们认为是 AMDCMD,可以直接将 factory 传入 define:

var factory = function () {
  var moduleName = {};
  return moduleName;
};
if (typeof module !== "undefined" && typeof exports === "object") {
  module.exports = factory;
} else if (typeof define === "function" && (define.cmd || define.amd)) {
  define(factory);
}

注意:CMD 其实也支持 return 返回模块接口,所以两者可以通用。

然后还要满足 script 标签全局引入,我们可以将模块放在 window 上,为了模块内部在浏览器和 Node.js 中都能使用全局对象,我们可以做此判断:

var global = typeof window !== "undefined" ? window : global;

我们用一个立刻执行的闭包函数将所有代码包含,来避免污染全局空间,并将 global 对象传入闭包函数,最终变成这样:

(function (global) {
  var factory = function () {
    var moduleName = {};
    return moduleName;
  };
  if (typeof module !== "undefined" && typeof exports === "object") {
    module.exports = factory;
  } else if (typeof define === "function" && (define.cmd || define.amd)) {
    define(factory);
  } else {
    global.factory = factory;
  }
})(typeof window !== "undefined" ? window : global);

注意:闭包前加上分号是为了给前一个模块填坑,分号多了没问题,少了则语句可能发生变化。

然后,就能愉快的调用了

//Node.js
var myModule = require('moduleName')

//Seajs
define(function(require,exports,module){
  var myModule = require('moduleName')
})

// Browser global
<script src='moduleName.js'></script>

[完]

我,秦始皇,打钱!