文章目录
一、什么是模块化?
简单来说,将一个项目划分为不同的功能模块来开发维护的过程就是模块化。
js一开始并没有模块化的概念,直到ajax被提出,前端能够像后端请求数据,前端逻辑越来越复杂,就出现了许多问题:全局变量,函数名冲突,依赖关系不好处理。模块化就是衍生出来解决这些问题的一种方法。
模块化开发优点:
- 解决命名冲突
- 提供复用性
- 提高代码可维护性
- 灵活架构,焦点分离,方便模块间组合、分解
- 多人协作互不干扰
二、前期
在早期,模块化还没有出现,此时人们采用以下方法来解决问题
全局function模式/文件划分方式
直接将不同功能代码写到一个个文件中,在主页面引入需要的功能文件,即模块,
然后直接调用模块中的变量函数等
在这里举个具体例子,先创建两个js文件
module1.js
var module1 = function(){
console.log("This is module1");
}
var module2 = function(){
console.log("module1's module2");
}
module2.js
var module2 = function(){
console.log("This is module2");
}
var module1 = function(){
console.log("module2's module1");
}
主页面
<body>
<script src="JS/module1.js"></script>
<script src="JS/module2.js"></script>
<script>
module1();
module2();
</script>
</body>
结果
可以看到这种方式有很大的缺陷,所有模块都在全局作用域中作用,模块一多,就很可能造成全局作用域污染,命名冲突等
namespace 模式
每一个模块只暴露一个全局变量,其它模块成员都挂到这个全局变量上
同样举个例子,创建两个js文件,分别创建一个对象,在对象中添加需要的属性,函数,变量等等
module1.js
var module1 = {
name: "module1",
fn: function(){
console.log("namespace——"+this.name+"ok")
}
}
module2.js
var module2 = {
name: "module2",
fn: function(){
console.log("namespace——"+this.name+"ok")
}
}
主页面
<body>
<script src="JS/module1.js"></script>
<script src="JS/module2.js"></script>
<script>
module1.fn();
module2.fn();
</script>
</body>
结果
特点
- 通过「命名空间」减小了命名冲突的可能,
- 但是同样没有私有空间,所有模块成员也可以在模块外部被访问或者修改,
- 而且也无法管理模块之间的依赖关系。
IIFE
使用立即执行函数(IIFE)表达式为模块提供私有空间,将每个模块放在函数提供的私有作用域上,将需要暴露的成员,挂到到全局变量上
;(function(){
var name = "module1";
function method1(){
console.log("The first"+name);
}
function method2(){
console.log("The second"+name);
}
window.method = {
method1:method1,
method2:method2
}
})()
注意:在匿名函数前加上“;”,是为了防止出现编译错误,例如:
var a = 10
(function(){ })()
该代码会被解析成 var a = 10(function(){ })(),即将10看作一个函数,此时会报错
<body>
<script src="JS/module1.js"></script>
<script>
method.method1();
method.method2();
</script>
</body>
结果
IIFE模式加强——依赖注入
在匿名函数参数中加入依赖项
JS文件
;(function(window,$){
var name="Rohatt";
var method = function(){
console.log(name+":This is IIFE method");
$('body').css({
"background": "red"
})
}
window.module2 = {
method:method
}
})(window,jQuery)
注:如果想保持undefined的值不变(有些浏览器中undefined可以被赋值),可以在匿名函数中添加undefined参数,而不引入,如下:
(function(window,undefined){
})(window)
在jQuery中就存在这种写法,除了避免undefined被修改,也可以减少全局污染,对外只暴露$,jQuery,从作用域链上来说,也减少了查找的层级
主页面
<body>
<script src="../../jquery/1.js"></script>
<script src="JS/module2.js"></script>
<script>
module2.method();
</script>
</body>
三、模块化开发方式
1.CommonJs规范(重点)
CommonJs是以在浏览器环境之外构建javascript生态系统为目标产生的项目,比如服务器和桌面环境
CommonJs是同步加载模块,但也可以在浏览器端实现
特点
- 一个文件就是一个模块
- 每个模块都有单独的作用域
- 通过module.exports导出成员
- 通过require函数载入模块
使用前提
需要下载node.js服务器
node.js下载完成后,可以检查是否下载成功:
在命令行输入node --version,看是否能查到node.js版本号,如果有就说明下载成功
基本语法
- 暴露模块
module.exports = value; 一般来说,value是一个对象/函数
export.xxx = value;
- 引入模块
require(xxx)
自定义模块:xxx——文件路径
第三方模块:xxx——文件名称
服务器端运行
示例如下,先创建一个js文件m1.js,写下需要的功能
var name = "Jonhson"
function fn(){
console.log('m1.js的模板调用');
}
exports.fn = fn;
exports.name = name;
之后再创建一个js文件引入模块,main.js
var m1 =require('./m1')
// console.log(m1.fn());
console.log(m1);
之后使用node.js运行(这里使用的是gitbash),得到一下结果
undefined是log函数的返回值
浏览器端运行
在浏览器端不能直接使用node.js来运行文件,此时需要下载一个工具包browserify来帮助你在浏览器加载模块
如果是window10系统,需要先打开管理员权限(window+x),再进行下载
之后,输入命令npm install -g browserify下载
-g代表全局安装,可以在命令行执行这个命令
如下图:
同样来举个例子。还是先创建js文件,m1.js,app.js
//m1.js文件
var a = "m1";
function fn(){
console.log(a + ":Hello! nice to meet you");
}
function fn1(){
console.log("This is fn1");
}
module.exports = {
'fn':fn,
'fn1':fn1
}
//app.js文件
var m1 = require('./m1');
m1.fn1()
<body>
<!--引入需要使用的模块-->
<script src="./app.js"></script>
</body>
此时控制台会报错,浏览器无法识别require
这是就需要用browserify进行编译,使用命令:
browserify app.js -o bundle.js
将app.js文件编译为bundle.js文件
在相应的文件夹中进行
编译成功后可以看到多文件夹中了一个bundle.js文件
bundle.js文件
(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
var m1 = require('./m1');
m1.fn1()
},{"./m1":2}],2:[function(require,module,exports){
var a = "m1";
function fn(){
console.log(a + ":Hello! nice to meet you");
}
function fn1(){
console.log("This is fn1");
}
module.exports = {
'fn':fn,
'fn1':fn1
}
},{}]},{},[1]);
之后再在index页面引用bundle.js文件就可以了
2.AMD规范
AMD:Asynchronous Module Definition,异步模块定义
它采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。
AMD比较适合浏览器环境。目前,主要有两个Javascript库实现了AMD规范:require.js和curl.js
基本语法
- 引入模块
require ([‘module1’,‘module2’],function(m1,m2){
业务代码
})
- 定义暴露模块
定义没有依赖的模块
define(function(){
return 模块;
})
定义存在依赖的模块
define([‘module1’,‘module2’],function(m1,m2){
return 模块;
})
引入自定义模块
将开发的文件单独封装为一个模块
- 定义好文件(modules/module.js)
define(function(){
var name = "chole";
function getMaxNumber(arr){
return Math.max.apply(null,arr);
}
// 返回对象
return { 'getMaxNumber':getMaxNumber,'name':name }
});
- 配置和使用模块(main.js)
使用requirejs.config进行配置,即标明引用模块的路径
requirejs([ ],function(){ }),[ ]和path中一致,function中的参数为模块的返回值,即暴露出来的变量
// 主模块
requirejs.config({
//在未配置baseUrl时,m1中文件路径为相对于main.js的路径
//模块不需要写后缀
// baseUrl: 'js/lib',
paths: {
// jquery: './libs/1',
m1:'./modules/module'
}
});
requirejs(['m1'],function(method1){
// $('body').css({
// 'background':'green'
// })
console.log(method1);
var arr = [1,23,132,4];
console.log(method1.getMaxNumber(arr));
})
- 使用模块
引入require.js文件,同时引入接口文件
<body>
<!-- require.js文件实现了amd规范,引入了define,require函数 -->
<!-- main.js为项目入口文件 -->
<script data-main="JS/main.js" src="JS/require.js"></script>
</body>
- 结果
引入第三方模块
在require.js官方网址中有相关介绍
例如:jQuery支持AMD规范
放图
jQuery文件中进行了模块化
以jquery为例:
只需在刚才的main.js文件中添加相关路径即可
- 配置和使用模块
requirejs.config({
// baseUrl: 'js/lib',
paths: {
jquery: './libs/1',
m1:'./modules/module'
}
});
requirejs(['jquery','m1'],function($,method1){
$('body').css({
'background':'green'
})
console.log(method1);
var arr = [1,23,132,4];
console.log(method1.getMaxNumber(arr));
})
- 结果(body背景色变为绿色)
3.CMD规范
在 CMD 规范中,一个模块就是一个文件
基本语法
- 定义模块
define(factory)
factory 参数可以是一个函数,也可以是一个对象或字符串。
factory为对象、字符串时,表示模块的接口就是该对象、字符串
定义对象:
define({‘name’:‘giant’,‘word’:‘nice to meet you’})
定义字符串
define(“I’m a guy, so what?”)
两者返回值都为该参数值,可自行实验
factory为函数时,默认会传入三个参数:require、exports 和 module
define(function(require, exports, module) ,参数之间的顺序不可调换
require
require 是一个方法,接受 模块标识 作为唯一参数,用来获取其他模块提供的接口
exports
exports 是一个对象,用来向外提供模块接口。(也可以通过return直接向外提供接口)
module
module 是一个对象,上面存储了与当前模块相关联的一些属性和方法。
module.exports : 当前模块对外提供的接口。传给 factory 构造方法的 exports 参数是 module.exports 对象的一个引用。只通过 exports 参数来提供接口,有时无法满足开发者的所有需求。 比如当模块的接口是某个类的实例时,需要通过 module.exports
- 提供模块接口
上面已经提到,使用exports或module.exports
exports.属性名 = 需提供的内容
module.exports = { 键值对 } 以对象形式添加
- 引入依赖模块
require(’ 依赖模块地址 ')
- 加载模块
seajs.use(id, callback?)
id
id为加载模块的路径,加载多个模块时,使用数值的形式添加路径
callback
回调函数,可以没有参数
引入模块并使用
在这里举个例子,包含依赖模块引入和两种提供模块接口的方法
先创建一个底层工具库utils.js,返回随机整数值
define(function(require,exports,module){
module.exports = {
getNumberByMathRadom () {
return Math.random();
},
//返回N到M之间的一个数
getNumberByNtoM (n,m) {
return n + Math.ceil( (m-n)*Math.random());
}
}
})
之后创建m1.js模块文件,通过require引入utils,js
// define(factory)
define(function(require,exports,module){
function getMaxNumber(arr){
return Math.max.apply(null,arr);
}
// 引入依赖的模块
var utils = require('utils');
var num = utils.getNumberByNtoM(12,89);
var name = "m1.js";
exports.getMaxNumber = getMaxNumber;
exports.name = name;
exports.num = num;
})
可以再创建一个m2.js模块文件 (注释详解)
define(function(require,exports,module){
function getMaxNumber(arr){
return Math.max.apply(null,arr);
}
var name = "m2.js";
var age = "20";
var hobby = "swing";
// module.exports = {
// 'name':name,
// 'age':age,
// 'hobby':hobby,
// 'getMaxNumber':getMaxNumber
// }
// es6新增规范
//在对象中,如果变量名和属性值名称一致,则可以省略属性值不写
//注:es6中把方法中的function()进行了省略
module.exports = {
name,
age,
hobby,
getMaxNumber,
fn () {
console.log(this.name);
}
}
})
之后创建main.js文件,引入模块
seajs.use(['m1','m2'],function(module1,module2){
console.log(module1);
var arr = [1,23,4,1];
console.log(module1.getMaxNumber(arr));
console.log(module2);
module2.fn();
})
最后在主页面调用
<body>
<script src="sea.js"></script>
<script src="main.js"></script>
</body>
结果
4.ES6规范(重点)
ES6模块化的设计思想是尽量的静态化,使得在编译时就能够确定模块之间的依赖关系。
基本语法
- 模块导出
两种导出方式:
命名导出
可以在变量声明的同时导出
export foo = “foo”
export const foo = " foo "
export关键字不能嵌套在某个块中,且export语句和导出的位置没有顺序限制
默认导出
使用default关键字声明,每个模块只能有一个默认导出
const foo = " foo ";
export default foo;
只要导出中含有default,就会将其识别为默认导出
ES6支持在一个模块中同时定义这两种导出,
export { foo as default , bar };
- 模块导入
import { } from ’ 路径 ’
import { foo } from ’ ./fooModule.js ’
import和export类似,不能嵌套在某个块中,和导入的位置没有顺序限制
如果在浏览器中通过标识符原生加载模块,文件必需带有js扩展名
如果想把一个模块中的所有命名导出集中到一块,可以使用 导出
export * from ’ ./foo.js ';
将foo.js中所有的命名导出出现在导入的模块中
冲突
使用也可能发生导出名称冲突,发生冲突时,结果为最后一个名称相同的导出文件
如何提高js代码质量:
参考资料: