前言:很久没写文章总结了,这次主要粗略的总结一下 js中的模块化
1.模块
模块的职责:封装实现,暴露接口,声明依赖。先来一个对比,下面代码没有应用任何模块系统
(1)无封装
math.js
:(1)没有封装性(2)接口不明显
function add(a,b){
return a+ b
}
function sub(a,b){
return a - b
}
caculator.js
:(1)依赖math.js但是没有依赖声明 (2)使用全局状态
var action = "add";
function compute(a,b){
switch (action){
case "add": return add(a,b)
case "sub": return add(a,b)
}
}
(2)字面量
math.js
:(1)结构性好(2)访问控制没有
var math = {
add:function add(a,b){
return a+ b
},
sub:function sub(a,b){
return a - b
}
}
caculator.js
:(1)依赖math.js但是没有依赖声明 (2)无法标明属性是私有,无封装
var caculator = {
action:'add';
compute:function compute(a,b){
switch (action){
case "add": return math.add(a,b)
case "sub": return math.add(a,b)
}
}
}
(3)IIFE:字值型的函数表达式
可以创建一个局部作用域,封装内部成员变量,通过return输出需要输出的接口
版本一:caculator-1.js
:(1)实现了访问控制(2)依然没有依赖声明
var caculator = (function(){
var action = "add";
return{
compute:function compute(a,b){
switch (action){
case "add": return math.add(a,b)
case "sub": return math.add(a,b)
}
}
}
})()
版本二:caculator-2.js
:(1)显示依赖声明(2)仍然污染了全局变量(3)必须手动进行依赖管理
揭露模块模式:return部分与版本一不太一样,把方法定义在函数体里面,return的只是方法。具体可参考这位童鞋的文章:Javascript 设计模式 -- Revealing Module(揭示模块)模式
var caculator = (function(m){
var action = "add";
function compute(a,b(){
switch (action){
case "add": return m.add(a,b)
case "sub": return m.add(a,b)
}
}
return{
compute:compute
}
})(math)
(4)命名空间
解决暴露全局变量的问题,只暴露一个类似namespace的全局变量就实现所有模块的声明
math.js
:(1)第一个参数是模块声明;(2)第二个参数是声明依赖,目前是没有依赖的;(3)第三个参数是模块的构成
namespace("math",[],function(){
function add(a,b){
return a+ b
}
function sub(a,b){
return a - b
}
return{
add:add,
sub:sub
}
})
caculator.js
:(1)有依赖生命(2)依赖math被当做参数传入
namespace("caculator",["math"],function(m){
var action = "add"
function compute(a,b){
return m[action](a,b)
}
return{
compute:compute
}
})
namespace
的代码:还是没有解决依赖管理的问题,如果各个模块分散在不同的文件中,就要对脚本加载顺序进行手动的排序
var namespace = (function(){
//缓存所有的模块
var cache = {}
function createModule(name,deps,definition){ //参数是:模块名,依赖列表,定义
//先对参数进行判断,如果只有一个参数,就返回
if(arguments.length === 1){
return cache[name]
}
//必须取得所有依赖的模块,要保证前面的模块已经被定义好了
deps = deps.map(function(depName){
return ns(depName)
})
//初始化模块并返回
cache[name] = definition.apply(null,deps)
return cache[name];
}
return createModule
})()
2.模块系统
职责:(1)依赖管理:加载/分析/注入/初始化 (2)决定模块的写法
下面总结三种典型的模块系统的写法
(1)commonjs
优点:
依赖管理成熟可靠
社区活跃,规范接受度高
运行时支持,模块定义非常简单
文件级别的模块作用域隔离
可以处理循环依赖
缺点:
不是标准组织的规范
同步的require,没有考虑浏览器异步加载的过程
但是还是有办法使用的,目前有很多工具可以把多个模块的文件打包成一个文件:browserify,webpack,component
看下面用commonjs
写就的代码:math.js
:
function add(a,b){
return a+ b
}
function sub(a,b){
return a - b
}
exports.add = add
exports.sub = sub
caculator.js
:
var math = require("./math"); //依赖声明
function Caculator(container){
this.left = container.querySelector(".j-left" );
this.right = container.querySelector(".j-right" );
this.add = container.querySelector(".j-add" );
this.result = container.querySelector(".j-result");
this.add.addEventListener("click",this.compute.bind(this));
}
Caculator.prototype.compute = function(){
this.result.textContent = math.add(+this.left.value, +this.right.value)
}
exports.Caculator = Caculator; //暴露接口
用前端打包工具进行打包math.js
和caculator.js
首先安装browserify
,在命令行输入命令:browserify caculator.js > caculator-bundle.js
打包成形如命名空间的文件形式
(2)AMD
天然的作用于异步环境
AMD代码写法:math.js
:第一个参数是依赖列表
define([],function(){
function add(a,b){
return a+ b
}
function sub(a,b){
return a - b
}
return{ //接口暴露
add:add,
sub:sub
}
})
caculator.js
:参数一是依赖声明,参数二是依赖注入
define(["./math"],function(math){
function Caculator(container){
this.left = container.querySelector(".j-left" );
this.right = container.querySelector(".j-right" );
this.add = container.querySelector(".j-add" );
this.result = container.querySelector(".j-result");
this.add.addEventListener("click",this.compute.bind(this));
}
Caculator.prototype.compute = function(){}
return{
Caculator:Caculator
}
})
AMD还支持一个叫Simplified CommonJS wrapping
define(function(require,exports){
var math = require("./math");
function Caculator(container){
this.left = container.querySelector(".j-left" );
this.right = container.querySelector(".j-right" );
this.add = container.querySelector(".j-add" );
this.result = container.querySelector(".j-result");
this.add.addEventListener("click",this.compute.bind(this));
}
Caculator.prototype.compute = function(){}
exports.Caculator = Caculator;
})
上述如何获取依赖列表呢?
函数通过toString可以打印出它的函数体,然后用正则表达式提取出来factory.toString()
/require\(['"]([^'"]*)['"]\)/.exec(factory.toString())[1]
优点:
依赖管理成熟可靠
社区活跃,规范接受度高
专为
异步IO打造
,适合浏览器环境支持类似Commonjs的书写方式
通过插件api可支持加载非js资源
成熟的打包构建工具,并可结合插件
缺点:
模块定义繁琐,需要额外嵌套
只是库级别的支持,需要引入额外的库
无法处理循环依赖
无法实现条件加载
(3)ES6/module
语言级别的支持,未来的模块化
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。