JS模块化
模块化开发是一种管理方式,一种生产方式,一种解决问题的方案。一个模块就是实现某个特定功能的文件,我们可以很方便的使用别人的代码,想要什么模块,就引入那个模块。这里介绍了立即执行函数、AMD、CMD、CommonJS、ES Module(ES6 模块)及他们在开发中如何使用。
一、什么是模块化
在js刚刚出现的时候,是为了实现一些简单的功能,但随着浏览器的不断发展,js越来越被重视起来,可以实现较为复杂的功能。这个时候开发者为了维护方便,会把不同功能的模块抽离出来写入单独的js文件,但是当项目更为复杂的时候,html可能会引入很多个js文件,而这个时候就会出现命名冲突,污染作用域等一系列问题,这个时候模块化的概念及实现方法应运而生。
模块化开发是一种管理方式,一种生产方式,一种解决问题的方案。一个模块就是实现某个特定功能的文件,我们可以很方便的使用别人的代码,想要什么模块,就引入那个模块。但是模块开发要遵循一定的规范,后面就出现了我们所熟悉的AMD和CMD规范。
(以下只讲各模块化的基本知识与开发中的使用方法,不讲实现原理及内部处理机制。)
二、立即执行函数
在早期,使用立即执行函数实现模块化是最常见的手段,通过函数作用域解决了命名冲突、污染全局的问题,那么立即执行函数也是一种模块化的实现方式,但并非是一种解决方案。举个例子:
(function (a) {
// 在这里面声明各种变量、函数都不会污染全局作用域
})(a)
三、AMD
AMD即是“异步模块定义”,它采用异步方式加载模块,模块的加载不影响后面语句的运行,所有依赖整个模块的语句,都定义在一个回调函数中,等到加载完成后,整个回调函数才会运行。
在AMD规范中,我们使用define定义模块,使用require加载模块。
1、定义模块
define(id?, dependencies?, factory);
- id是定义的模块名,这个参数是可选的,如果没有定义该参数,模块名字应该默认为模块加载器请求的指定脚本的名字,如果有该参数,模块名必须是顶级的绝对的。
- dependencies是定义的模块中所依赖的模块数组,依赖模块优先级执行,并且执行结果按照数组中的排序依次以参数的形式传入factory。
- factory是模块初始化要执行的函数或对象,只被执行依次,如果是对象,则为该模块的输出值。
下面来看一个例子:
define("OrderModel", ["Header", "Pay"], function (Header, Pay) {
var OrderModel = function () {
this.headerData = Header.getHeaderData();
this.payData = Pay.getPayData();
}
return OrderModel;
})
2、加载模块
require([module], callback);
require要传入两个参数,第一个是[module],是一个数组,就是要加载的模块,第二个callback是加载成功之后的回调函数。
下面举个例子:
// 在定义模块中已经定义过OrderModel模块了,下面只需要加载并使用它
require(["OrderModel"], function (OrderModel) {
console.log(OrderModel.headerData);
console.log(OrderModel.payData);
})
四、CMD
CMD即是“通用模块定义”,CMD规范是国内发展出来的,CMD和AMD都是要解决同一个问题,只不过两者在模块定义方式和模块加载时机上有所不同罢了。
1、定义模块
在CMD中一个模块就是一个文件,通过define()进行定义。
define接收factory参数,它可以使一个函数,也可以是一个对象一个字符串。
- 当factory是一个对象或者一个字符串时,表示该模块的接口就是这个对象或者字符串。
- 当factory是一个函数时,表示是该模块的构造方法。执行该构造方法,可以得到模块向外提供的接口,factory在执行时,默认传入三个参数:require、exports、module。
- 其中require用来加载其它模块。exports用来实现向外提供的模块接口。
- module是一个对象,存储着与当前模块相关联的一些属性和方法,传给factory构造方法的exports是module.exports对象的一个引用,至通过exports参数来提供对外的接口,有时无法满足所有需求,比如当模块的接口是某个类的实例时,这个时候就需要通过module.exports来实现。
下面举个例子:
// 定义模块OrderModel.js
define(function (require, exports, module) {
var Header = require('./Header'); // require用来加载其它模块
exports.headerData = Header.getHeaderData(); // 对外提供headerData属性
// exports是module.exports的一个引用
console.log(exports === modele.exports); // true
var Pay = require('./Pay'); // 依赖可以就近加载
exports.payData = Pay.getPayData(); // 对外提供payData属性
exports.payFun = function() {
console.log('payFun log something');
}; // 对外提供payFun方法
})
2、加载模块
通过SeaJs的use方法我们可以加载模块
举个例子:
// 上面我们已经定义了OrderModel模块了,直接加载即可
seajs.use(["OrderModel.js"], function (orderModel) {
var headerData = orderModel.headerData;
var payData = orderModel.payData;
orderModel.payFun(); // 可以直接使用,输出 payFun log something
})
3、AMD与CMD的不同
- 对于依赖模块,AMD是提前执行,CMD是延迟执行
- 对于依赖模块,AMD是依赖前置,CMD是依赖就近
五、CommonJS
CommonJS规范主要应用于Node,每个文件就是一个模块,有自己的作用域,即在一个文件中定义的变量、函数、类都是私有的,对其他文件不可见。
1、定义模块
(上面说了每个文件就是一个模块,所以不存在定义的概念,只是为了承接上下文,更好理解罢了,文章后面不再说明。)
CommonJs规范规定,每个模块内部有两个变量可以使用:require和module。
- require用来加载某个需要的模块。
- module代表的是当前模块,是一个对象,存储着当前模块的相关联的属性和方法。exports是module上的一个属性。该属性表示当前模块对外输出的接口,其它文件加载该模块,实际上就是读取module.exports变量。(在实际开发中如果区分不了exports和module.exports的话,那就直接使用module.exports即可,那个exports就别管、别用了。)
举个例子:
// orderModel.js
var Header = require('./Header'); // require用来加载其它模块
var Pay = require('./Pay');
var payFun = function () {
console.log('payFun log something');
}
module.exports = { // 对外提供以下三个属性
headerData: Header.getHeaderData(),
payData: Pay.getPayData(),
payFun: payFun
}
2、加载模块
其实在上面的代码中,即orderModel.js中已经写出了加载模块的方法了。
下面是加载并使用orderModel.js的例子:
var orderModel = require('./orderModel');
var headerData = orderModel.headerData;
var payData = orderModel.payData;
orderModel.payFun(); // 输出 payFun log something
需要注意的是,CommonJS规范规定,模块可以多次加载,但是只会在第一次加载时运行一次,运行结果就会被缓存下来,以后再加载就直接读取缓存结果,如果想让模块再次运行,必须清除缓存。
举个例子:
require('./orderModel');
require('./orderModel').message = 'hello world';
require('./orderModel').message;
// hello world
清除缓存例子:
// 删除指定模块的缓存,这里删除orderModel.js,也可以写多个进行批量删除。
delete require.cache[require.resolve('./orderModel')];
// 删除所有模块的缓存,大范围攻击
Object.keys(require.cache).forEach(function (key) {
delete require.cache[key];
})
六、ES Module
在ES6没出来之前,模块加载方案主要使用CommonJS和AMD两种,前者用于服务器,后者用于浏览器。ES6在语言标准层面上实现了模块功能,而且使用起来相当简单。
1、定义模块
模块功能主要由两个命令构成:export和import,export命令用于规定模块的对外接口,import用于引入其它模块提供的功能。
一般来说,一个模块对应的就是一个文件,该文件内部的变量外部无法获取,如果你希望外部能够读取到某个变量,就需要使用export关键字输出该变量。
举个例子:
// user.js
let name = "张三";
let age = 20;
const getSex = (s) => {
return s === 1 ? "男" : "女";
}
// 通用写法,如果不想了解,就这样写就完事了
export {
name,
age,
getSex
}
2、加载模块
上面已经使用export命令定义了模块对外的接口后,其它的JS文件就可以通过import命令加载这个模块。
举个例子:
// main.js
import {name, age, getSex} from './user';
console.log(name); // 张三
console.log(age); // 20
console.log(getSex(1)); // 男
import接受一对大括号,里面指定的是要从其它模块导入的变量名。大括号内的变量名,必须与导入模块对外接口的名称相同。
我们经常需要对加载模块进行重命名,如下写法:
// main.js
import {name as otherName} from './user'; // 使用as进行重命名
console.log(otherName); // 张三
我们也经常使用到对模块的整体加载,如下写法:
// main.js
import * as user from './user'; // 使用 * 号指定一个对象,所有输出值都加载在这个对象上面
console.log(user.name); // 张三
console.log(user.age); // 20
console.log(user.getSex(1)); // 男
// 需要注意的是 user是静态分析的,不允许运行时改变
// 下面的写法是不允许的
user.height = 180;
user.setOld = function () {};
3、和CommonJS的区别
- ES Module不支持动态导入,但已提案,指日可待。
- ES Module是异步导入,因为用于浏览器,需要下载文件,如果采用同步导入对渲染有很大影响。CommonJS是同步导入,因为用于服务端,文件都在本地,同步导入即使卡主主线程影响也不大。
- ES Module导出的是值的引用,导入导出值都指向同一个内存地址,所以导入值会跟随导出值变化。而CommonJS在导出时都是值的拷贝,就算导出的值变了,导入的值也不会改变,所以想要更新值,必须重新导入一次。
更多推荐
所有评论(0)