一、模块化的由来
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文件查找策略
要点:
- 优先从文件模块的缓存中加载
- 原生模块的优先级仅次于文件模块缓存的优先级
- 原生模块也有一个缓存区,同样也是优先从缓存区加载
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
]
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会通过这个属性,帮你自动加载依赖的包。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。