前言
作为一个接口API测试工程师,你是否曾经有过疑惑,有些项目在模仿(fu zhi zhan tie)原有的代码时,有时候是通过export
将模块暴露出去,有时候是通过export default
,有些包可以通过import引用,有些是require,那么,来总结一下这些区别
首先了解一些基本的概念
AMD(RequireJS)
- AMD一开始是CommonJS规范中的一个草案,全称是Asynchronous Module Definition,即异步模块加载机制。后来由该草案的作者以RequireJS实现了AMD规范,所以一般说AMD也是指RequireJS。
- 特点:
- 对于依赖的模块,AMD推崇依赖前置,提前执行。也就是说,在define方法里传入的依赖模块(数组),会在一开始就下载并执行。
- 模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。
- 使用:通过
define
来定义一个模块,使用require
可以导入定义的模块。
// a.js
/*
* define(id?, dependencies?, factory);
* id:可选参数,用来定义模块的标识,如果没有提供该参数,脚本文件名(去掉拓展名)
* dependencies:是一个当前模块依赖的模块名称数组
* factory:工厂方法,模块初始化要执行的函数或对象。如果为函数,它应该只被执行一次。如果是对象,此对象应该为模块的输出值 在页面上使用require函数加载模块
*/
define(function(){
return 1;
})
// b.js
/*
*数组中声明需要加载的模块,可以是模块名、js文件路径
* require([dependencies], callback);
* [dependencies]: 是一个数组,表示所依赖的模块
* callback: 是一个回调函数,当前面指定的模块都加载成功后,它将被调用。加载的模块会以参数形式传入该函数,从而在回调函数内部就可以使用这些模块
*/
require(['a'], function(a){
console.log(a);// 1
});
CMD
- CMD是SeaJS在推广过程中生产的对模块定义的规范,在Web浏览器端的模块加载器中,SeaJS与RequireJS并称,SeaJS作者为阿里的玉伯。
- 特点:对于依赖的模块,CMD推崇依赖就近,延迟执行。也就是说,只有到
require
时依赖模块才执行。 - 使用:
// a.js
/*
* define 接受 factory 参数,factory 可以是一个函数,也可以是一个对象或字符串
* define(id?, deps?, factory)
* id 表示模块标识
* 数组 deps 是模块依赖
* factory 为对象、字符串时,表示模块的接口就是该对象、字符串。
*/
define(function(require, exports, module) {
var $ = require('jquery');
exports.setColor = function() {
$('body').css('color','#333');
};
});
//b.js
//数组中声明需要加载的模块,可以是模块名、js文件路径
seajs.use(['a'], function(a) {
$('#el').click(a.setColor);
});
CommonJS
- CommonJS规范为CommonJS小组所提出,目的是弥补JavaScript在服务器端缺少模块化机制,NodeJS、webpack都是基于该规范来实现的。
在 ES6 之前,ECMAScript 并没有提供代码组织的方式,那时候通常是基于 IIFE 来实现“模块化”,随着 JavaScript 在前端大规模的应用,以及服务端 Javascript 的推动,原先浏览器端的模块规范不利于大规模应用。于是早期便有了 CommonJS 规范,其目标是为了定义模块,提供通用的模块组织方式。 - 特点:
- 所有代码都运行在模块作用域,不会污染全局作用域;
- 模块是同步加载的,即只有加载完成,才能执行后面的操作;
- 模块在首次执行后就会缓存,再次加载只返回缓存结果,如果想要再次执行,可清除缓存;
- require返回的值是被输出的值的拷贝,模块内部的变化也不会影响这个值。
- 使用:
//a.js
module.exports = function () {
console.log("hello world")
}
//b.js
var a = require('./a');
a();//"hello world"
//或者
//a2.js
exports.num = 1;
exports.obj = {xx: 2};
//b2.js
var a2 = require('./a2');
console.log(a2);//{ num: 1, obj: { xx: 2 } }
ES6 Module
- ES6 Module是ES6中规定的模块体系,相比上面提到的规范, ES6 Module有更多的优势,有望成为浏览器和服务器通用的模块解决方案。
- 特点(对比CommonJS):
- CommonJS模块是运行时加载,ES6 Module是编译时输出接口;
- CommonJS加载的是整个模块,将所有的接口全部加载进来,ES6 Module可以单独加载其中的某个接口;
- CommonJS输出是值的拷贝,ES6 Module输出的是值的引用,被输出模块的内部的改变会影响引用的改变;
- CommonJS this指向当前模块,ES6 Module this指向undefined;
- 目前浏览器对ES6 Module兼容还不太好,我们平时在webpack中使用的export/import,会经过babel转换为CommonJS规范。
- 使用:
//a.js
var name = 'lin';
var age = 13;
var job = 'ninja';
export { name, age, job};
//b.js
import { name, age, job} from './a.js';
console.log(name, age, job);// lin 13 ninja
//或者
//a2.js
export default function () {
console.log('default ');
}
//b2.js
import customName from './a2.js';
customName(); // 'default'
Node.js模块里exports与module.exports的区别
其实require导出的内容是module.exports的指向的内存块内容,并不是exports的。 简而言之,区分他们之间的区别就是 exports 只是 module.exports的引用,辅助后者添加内容用的。
为了避免糊涂,尽量都用 module.exports 导出,然后用require导入。
var module = {
exports:{
name:"我是module的exports属性"
}
};
var exports = module.exports; //exports是对module.exports的引用,也就是exports现在指向的内存地址和module.exports指向的内存地址是一样的
console.log(module.exports); // { name: '我是module的exports属性' }
console.log(exports); // { name: '我是module的exports属性' }
exports.name = "我想改一下名字";console.log(module.exports); // { name: '我想改一下名字' }
console.log(exports); // { name: '我想改一下名字' }
//看到没,引用的结果就是a和b都操作同一内存地址下的数据
//这个时候我在某个文件定义了一个想导出的模块
var Circle = {
name:"我是一个圆",
func:function(x){
return x*x*3.14;
}
};
exports = Circle; // 看清楚了,Circle这个Object在内存中指向了新的地址,所以exports也指向了这个新的地址,和原来的地址没有半毛钱关系了
console.log(module.exports); // { name: '我想改一下名字' }
console.log(exports); // { name: '我是一个圆', func: [Function] }
ES中的模块重的export 和 export default的区别
- export与export default均可用于导出常量、函数、文件、模块等
- 在一个文件或模块中,export、import可以有多个,export default仅有一个
- 通过export方式导出,在导入时要加{ },export default则不需要
- export能直接导出变量表达式,export default不行。