JavaScript 模块化
当项目越来越大时,会遇到一些问题:
1.命名冲突
2.文件依赖
所有就有了javascript模块化开发概念。
模块化开发的演变:
1.函数块:最开始用全局函数将代码块包括在函数体内,然后把很多函数写在一个js文件,然后引入js文件,这种方式会导致:全局变量污染和命令冲突,模块之间的关系也不明确。
2.命名空间:把函数和变量封装在对象里,可以较好的避免命名冲突问题。但是这方式会导致:多层级嵌套(命名空间越来越长),暴露了所有的模块成员,内部状态可以被外部改写,不安全。
3.私有共有成员分离:将函数包裹在立即执行函数(达到函数外部无法访问内部变量的效果)里,以返回的方式选择性对外暴露内部成员。在立即执行函数里形成了一个私有作用域,私有空间的变量和函数不会影响到全局作用域。从某种意义上来说,解决了变量命名冲突的问题。
var calculator = (function () { // 这里形成一个单独的私有的空间 // 私有成员的作用: // 1、将一个成员私有化 // 2、抽象公共方法(其他成员中会用到的) // 私有的转换逻辑 function convert(input){ return parseInt(input); } function add(a, b) { return convert(a) + convert(b); } function subtract(a, b) {} function multiply(a, b) {} function divide(a, b) {} return { add : add, subtract : subtract, multiply : multiply, divide : divide } })();
4.模块的拓展与维护
// 计算模块 (function (calculator) { function convert(input) { return parseInt(input); } calculator.add = function(a, b) { return convert(a) + convert(b); } window.calculator = calculator; })(window.calculator || {}); // 新增需求 (function (calculator) { calculator.remain = function (a , b) { return a % b; } window.calculator = calculator; })(window.calculator || {}); alert(calculator.remain(4,3));
这种方式有利于对庞大的模块的子模块划分。
5.第三方依赖的管理
(function (calculator , $) { // 依赖函数的参数,是属于模块内部 // console.log($); calculator.remain = function (a , b) { return a % b; } window.calculator = calculator; })(window.calculator || {} , jQuery);
传入依赖模块jQuery, 立即执行函数空间里就可以使用第三方依赖了。
创建模块的原则:高内聚低耦合,模块内相关性高,模块间关联低。单一职责
模块使用场景:
1.业务复杂
2.重复逻辑多
3.可拓展性要求高
最后演变到模块化规范:
服务端主要有CommonJS(Node.js 就是基于CommonJS规范实现的模块化 ,webpack也实现了对CommonJS原生支持)
CommonJS的核心思想就是通过 require 方法来同步加载所要依赖的其他模块,然后通过 exports 或者 module.exports 来导出需要暴露的接口,这个规范里,每个文件就是一个模块
其内部定义的变量是属于这个模块的,不会对外暴露,也就是说不会污染全局变量。例如
// a.js var x = 5; var addX = function (value) { return value + x; }; module.exports.x = x; module.exports.addX = addX;
这里的module代表了这个模块,module的exports属性就是对外暴露提供的接口,可以对外导出外部可以访问的变量,如x, addX
这样就可以在其他模块中引入这个模块了
var example = require('./a.js'); console.log(example.x); // 5
CommonJS规范是同步加载模块的,在服务器端,文件都是保存在硬盘上,所以同步加载没有问题,但是对于浏览器端,需要将文件从服务器端请求过来,那么同步加载就不适用了,所以,CommonJS是不适用于浏览器端的。
客户端主要有:AMD( 实现有RequireJS ) / CMD(实现有SeaJS),随着ES6模块化规范的实现和普及,第三方的模块化实现将会慢慢的淘汰。
AMD是非同步加载模块,允许函数回调,如Require.js,需提前加载所有模块。
CMD是按需加载模块的(可以依赖就近),不需要在模块开始就加载所有依赖。
AMD和CMD 规范 模块加载区别:前者是对于依赖的模块提前执行,后者是延迟执行。前者提倡依赖前置,后者提倡依赖就近,只需要在用到某个模块时再require.
// AMD define(['./a', './b'], function(a, b) { // 依赖必须一开始就写好 注意标红的,通过函数回调的方式将引入的依赖赋值给标红的变量,然后整个块就可以利用这个引入的依赖了 a.doSomething() // 此处略去 100 行 b.doSomething() ... }); // CMD define(function(require, exports, module) { var a = require('./a') a.doSomething() // 此处略去 100 行 var b = require('./b') // 依赖可以就近书写 b.doSomething() // ... });
JavaScript 模块化主要有三种方案:
1.CommonJS
// module math.js module.exports = function add (a, b) { return a + b; } // main.js var {add} = require('./math'); console.log('1 + 2 = ' + add(1,2);
2.AMD / CMD
// module add.js define(function () { return { add: function (a, b) add { return a + b; } }; }); // main.js require(['add'], function (add) { console.log('1 + 2 = ' + add(1,2); });
3.ES6
// module add.js export function add (a, b) { return a + b; } // main.js import {add} from 'add.js';
之前的几种模块化方案都是前端社区自己实现的,只是得到了大家的认可和广泛使用,而ES6的模块化方案是真正的规范。 在ES6中,我们可以使用 import
关键字引入
模块,通过 export
关键字导出
模块,功能较之于前几个方案更为强大,也是我们所推崇的,但是由于ES6目前无法在浏览器中执行,所以,我们只能通过babel
将不被支持的import编译为当前受到广泛支持的 require。
之前在想这个问题之前,一直有个疑问,浏览器怎么支持的require,exports, module, define 这些字段的? 。。。
很二逼的疑问,因为 require.js sea.js 就是用来支持浏览器提供这些字段的,也就是说在我们自己编写的模块之前需要先引入这些规范实现的库文件
<script src="./require.js"></script>
<script src="./otherModule.js"></script>
参考链接:https://www.jianshu.com/p/3832c00a44a7
http://www.ruanyifeng.com/blog/2015/05/commonjs-in-browser.html