一、模块化的由来

1、最早我们这么写代码

全部方法写在一起,容易命名冲突,并且污染global全局

function foo(){}
function bar(){}

2、简单封装(Namespace模式)

减少了全局的变量,但是仍然可以通过myFunc.foo去操作数据,不安全

var myFunc = {
    _private:'no safe',
    foo: function(){
        console.log(this._private)
    }
}
myFunc._private = 5;
myFunc.foo();

3、匿名闭包(IIFE模式)

函数时javascript中唯一的localScope, 无法操作里面的数据

var module = (function(){
    var _private = 'safe now';
    var foo = function(){
        console.log(_private);
    }
    return {
        foo:foo
    }
})();

或者

(function(){
    var _private = 'safe now';
    var foo = function(){
        console.log(_private);
    }
    // 暴露模块
    window.module = {
        foo:foo
    }
})()

module._private; // undefined 
module.foo();

4、增强,引入依赖

有时候,我们的功能需要依赖模块才能完成,此时需要蒋模块注入进来

// 这就是模块模式的基础
var module = (function($){
    var _private = $('body');
    var foo = function(){
        console.log(_private);
    }
    // 暴露模块
    return {
        foo:foo
    }
    // 引入模块
})(JQuery);

或者

(function(global){
    var _private = 'safe';
    var foo = function(){
        console.log(_private);
    }
    // 暴露模块
    global.module = {
        foo:foo
    }
     // 引入模块
})(window)

module.foo();

二、模块化规范

1、commonjs

暴露模块:默认exports是{}, 第一种相当于把exports覆盖了,第二种相当于时往exports中添加属性。

  • module.exports = value;
  • exports.xxx = value;

引入模块:

  • 引入第三方模块: require(包的名称)
  • 引入自定义模块: require(模块的文件路径)

实现:

  • 服务器端:node环境直接使用
  • 浏览器端:使用browserify打包工具 否则浏览器不能识别

    browserify src/app.js -o bundle.js

代码演示:

// module1.js
module.exports = {
    msg: 'module1',
    foo: function () {
        console.log(this.msg);
    }
};
// module2.js
module.exports = function () {
    console.log('module2');
};
// module3.js
exports.foo = function () {
    console.log('foo module3');
};
exports.bar = function () {
    console.log('bar module3');
};
exports.arr = [1, 224, 2, 4, 2, 4784, 3];
// app.js
const uniq =require('uniq')
const module1 = require('./module1');
const module2 = require('./module2');
const module3 = require('./module3');
module1.foo();
module2();
module3.foo();
module3.bar();
console.log(uniq(module3.arr));

三、commonjs详解

1、模块载入策略

Node.js的模块分为两类:

  • 原生(核心)模块:原生模块在Node.js源代码编译的时候编译进了二进制执行文件,加载的速度最快。
  • 文件模块:文件模块是动态加载的, 加载速度比原生模块慢。

Tips:Node.js对原生模块和文件模块都进行了缓存,于是在第二次require时,是不会有重复开销的。其中原生模块都被定义在lib这个目录下面,文件模块则不定性。

2、require文件查找策略

clipboard.png

要点:

  • 优先从文件模块的缓存中加载
  • 原生模块的优先级仅次于文件模块缓存的优先级
  • 原生模块也有一个缓存区,同样也是优先从缓存区加载

require方法接受以下几种参数的传递:

  • http、fs、path等,原生模块。(node自带的模块)
  • /mod或../mod,相对路径的文件模块。
  • /pathtomodule/mod,绝对路径的文件模块。
  • mod, 非原生模块的文件模块。(npm包)

module path的生成规则为

// 从当前文件目录开始查找node_modules目录
// 然后依次进入父目录,查找父目录下的node_modules目录
// 依次迭代, 直到根目录下的node_modules目录
[
    '/home/jackson/research/node_modules',
    '/home/jackson/node_modules',
    '/home/node_modules',
    '/node_modules'
]
// 除此之外还有一个全局module path,是当前node执行文件的相对目录 (../../lib/node)。
// 如果在环境变量中设置了HOME目录和NODE_PATH目录的话,整个路径还包含NODE_PATH和HOME目录下的.node_libraries 与.node_modules
[
    NODE_PATH,
    HOME/.node_modules,
    HOME/.node_libraries,
    execPath/../../lib/node
]

clipboard.png

3、包结构

  • 一个package.json文件应该存在于包顶级目录下。
  • 二进制文件应该包含在bin目录下。
  • JavaScript代码应该包含在lib目录下。
  • 文档应该在doc目录下。
  • 单元测试应该在test目录下。

Node.js在没有找到目标文件时,会将当前目录当作一个包来尝试加载,所以在package.json文件中最重要的一个字段就是main。对于require,只需要main属性即可。
但是在除此之外包需要接受安装、卸载、依赖管理,版本管理等流程,所以CommonJS为package.json文件定义了如下一些必须的字段

  • name : 包名,需要在NPM上是唯一的。不能带有空格。
  • description : 包简介。通常会显示在一些列表中。
  • version : 版本号。一个语义化的版本号(http://semver.org/),通常为x.y.z。该版本号十分重要,常常用于一些版本控制的场合。
  • keywords : 关键字数组。用于NPM中的分类搜索。
  • maintainers : 包维护者的数组。数组元素是一个包含name、email、web三个属性的JSON对象。
  • contributors : 包贡献者的数组。第一个就是包的作者本人。在开源社区,如果提交的patch被merge进master分支的话,就应当加上这个贡献patch的人。格式包含name和email。

    "contributors": [{
             "name": "Jackson Tian", "email": "mail @gmail.com"
         }, {
             "name": "fengmk2", "email": "mail2@gmail.com"
     }],
  • bugs : 一个可以提交bug的URL地址。可以是邮件地址 (mailto:mailxx@domain),也可以是网页地址(http://url)。%E3%80%82)
  • licenses : 包所使用的许可证。例如:

     "licenses": [{
         "type": "GPLv2",
         "url": "http://www.example.com/licenses/gpl.html",
     }]
  • repositories : 托管源代码的地址数组。
  • dependencies : 当前包需要的依赖。这个属性十分重要,NPM会通过这个属性,帮你自动加载依赖的包。

其它请参考一下链接:
深入Node.js的模块机制
详解JavaScript模块化开发
前端模块化详解(完整版)


lihaixing
463 声望719 粉丝

前端就爱瞎折腾