前端模块化开发

Author Avatar
carvenzhang 3月 11, 2016
  • 在其它设备中阅读本文章

其实对前端模块化开发的接触时间并不多,很多见解都是别人的,或者是偏的, 还是乐意记录下来,谁让我一天一个念头
说到前端模块化开发,其实是说 javascript 模块化开发。
目前,大众讲到的 javascript模块化规范 有3种,CommonJSAMD(异步模块定义)、CMD(通用模块定义)

CommonJS

CommonJS在 node 端模块采用的规范。
根据CommonJS规范,每一个文件都是一个模块,每一个模块都有一个独立的作用域,文件内的变量都是私有的,其他文件不可使用(除非 赋值到 global上)
每个文件对外的接口是 module.exports 对象。其他文件通过使用这个对象的属性和方法,实现对本文件的使用。

require用于引用其他模块,实际获得的是其他模块的module.exports对象。

例子

example.js

var x = 5;
var addX = function(value) {
  return value + x;
};
module.exports.x = x;
module.exports.addX = addX;

index.js

var example = require('./example.js');

console.log(example.x); // 5
console.log(example.addX(1)); // 6

CommonJS模块的特点:

所有代码都运行在模块作用域,不会污染全局作用域。

模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。

模块加载的顺序,按照其在代码中出现的顺序。

require内部处理流程

require 实际是 指向当前模块的 module.require, module.require 又调用Node的 Module._load(此Module非彼module)

Module._load = function(request, parent, isMain) {
  // 1. 检查 Module._cache,是否缓存之中有指定模块
  // 2. 如果缓存之中没有,就创建一个新的Module实例
  // 3. 将它保存到缓存
  // 4. 使用 module.load() 加载指定的模块文件,
  //    读取文件内容之后,使用 module.compile() 执行文件代码
  // 5. 如果加载/解析过程报错,就从缓存删除该模块
  // 6. 返回该模块的 module.exports
};

其中 module.compile()执行如下:

Module.prototype._compile = function(content, filename) {
  // 1. 生成一个require函数,指向module.require
  // 2. 加载其他辅助方法到require
  // 3. 将文件内容放到一个函数之中,该函数可调用 require
  // 4. 执行该函数
};

这是大概的CommonJS 流程
详见CommonJS规范

AMD(异步模块定义)

AMD 是 RequireJS 在推广过程中对模块定义的规范化产出。

CommonJS 采用的是同步加载机制,如果用于 客户端,必定受到网络的限制。所以,CommonJS不 适用于客户端。
而 AMD 采用的是 模块异步加载 方式,在需要执行到模块文件的时候,实现异步加载,回调执行。
看例子:

//...
require(['math'], function (math) {
    math.add(2, 3);
  });
//...

当执行到这一段代码的时候, 浏览器会先 加载 math 模块,在math模块加载成功后, 再执行后面的回调函数 math.add(2,3)

require.js

说道AMD 就不得不提 require.js了。
因为目前要实现 AMD , 不要按照require要求的写法。
首先下载最新require.js ,然后在 html 底部写上如下代码:

<script src="js/require.js" data-main="js/main"></script>

data-main用于指定网页程序的主模块

main.js

require(['moduleA', 'moduleB', 'moduleC'], function (moduleA, moduleB, moduleC){
    // some code here
  });

要加载的模块也必须使用特定的写法,使用define()
moduleA.js

define(['moduleD'],function (){
    var add = function (x,y){
      return x+y;
    };
    return {
      add: add
    };
  });

CMD(通用模块定义)

CMD 是 SeaJS 在推广过程中对模块定义的规范化产出。

CMDAMD 在使用方面非常相似。(其实我没用过)

知呼上的比较

对于依赖的模块,AMD 是提前执行,CMD 是延迟执行。不过 RequireJS 从 2.0 开始,也改成可以延迟执行(根据写法不同,处理方式不同)。CMD 推崇 as lazy as possible.

CMD 推崇依赖就近,AMD 推崇依赖前置。看代码:

作者:玉伯
链接:https://www.zhihu.com/question/20351507/answer/14859415
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

// CMD
define(function(require, exports, module) {
var a = require('./a')
a.doSomething()
// 此处略去 100 行
var b = require('./b') // 依赖可以就近书写
b.doSomething()
// ... 
})

// AMD 默认推荐的是
define(['./a', './b'], function(a, b) { // 依赖必须一开始就写好
a.doSomething()
// 此处略去 100 行
b.doSomething()
...
})

就是说, 模块加载完成后, AMD 是立刻执行的,而 CMD是在需要用到的时候才执行的
针对这些不同, 在体现上:
AMD 速度会相对快, 但是会浪费资源
CMD 节省资源, 性能会差一点(反应时间)

webpack

react.jswebpack 流行起来。(至少我是通过 react.js 认识到 webpack 的)
react.js 可以说是前端(浏览器)项目,可是在编程风格上,确实不折不扣的 CommonJS 风格。
webpack 兼容了 CommonJSAMD

webpack 是一个模块管理工具。 对于 CommonJS 的模块, 对将其 最终打包在一个js文件里面, 对于不写不需要立刻执行的文件,也可以拆分出来,在运行时异步加载。

当然, 这里并不打算 说明怎么使用 webpack , google上有很多丰富的教程。


巨人的肩膀上,加自己的一点感悟