模块
模块就是实现特定功能的一组方法。
理想情况下,开发者只需要实现核心的业务逻辑,其他都可以加载别人已经写好的模块。但是,JavaScript 不是一种模块化编程语言,在 ES6 以前,它是不支持类,所以也就没有模块了。
JavaScript 社区做了很多努力,在现有的运行环境中,实现”模块”的效果。
原始写法
只要把不同的函数(以及记录状态的变量)简单地放在一起,就算是一个模块。
function f1(){
//...
}
function f2(){
//...
}
上面的函数 f1() 和 f2(),组成一个模块。使用的时候,直接调用就行了。
缺点:污染全局变量,无法保证不与其他模块发生变量名冲突,而且模块成员之间看不出直接关系。
对象写法
为了解决上面的缺点,可以把模块写成一个对象,所有的模块成员都放到这个对象里面。
var module1 = new Object({
num: 0,
f1: function (){
//...
},
f2: function (){
//...
}
});
上面的函数 f1() 和 f2(),都封装在 module1 对象里。使用的时候,调用这个对象的属性
module1.f1()
这样的写法会暴露所有模块成员,内部状态可以被外部改写。比如,外部代码可以直接改变内部计数器的值。
module1.num = 1
立即执行函数写法
使用立即执行函数,可以达到不暴露私有成员的目的
var module2 = (function() {
var num = 0;
var f1 = function() {
alert(num)
}
var f2 = function() {
alert(num + 1)
}
return {
f1: f1,
f2: f2
}
})()
使用上面的写法,外部代码无法读取内部的 num 变量。
console.log(module2.num) //undefined
主流模块规范
CommonJS
CMD
AMD
现阶段的标准
ES6 标准发布后,module 成为标准,标准使用是以 export 指令导出接口,以 import 引入模块,但是在 node 模块中,我们依然采用的是 CommonJS 规范,使用 require 引入模块,使用 module.exports 导出接口。
export 导出模块
export 语法声明用于导出函数、对象、指定文件(或模块)的原始值。
export 有两种模块导出方式:命名式导出(名称导出)和默认导出(定义式导出),命名式导出每个模块可以多个,而默认导出每个模块仅一个。
export { name1, name2};
export { a1 as name1, a2 as name2 };
export let name1, name2;
export let name1 = a1, name2 = a1;
export default expression;
export default function (a) { console.log(a) }
export default function f1(a) { console.log(a) }
export { name1 as default};
export * from name1;
export { name1, name2} from a1;
export { a1 as name1, a2 as name2 } from b;
- name1 ,name2 ,导出的标识符。导出后,可以通过这个标识符在另一个模块中使用 import 引用
- default 设置模块的默认导出。设置后import不通过标识符而直接引用默认导出
- “*” 继承模块并导出继承模块所有的方法和属性
- as 重命名导出“标识符”
- from 从已经存在的模块、脚本文件导出
命名式导出
模块可以通过 export 前缀关键词声明导出对象,导出对象可以是多个。这些导出对象用名称进行区分,称之为命名式导出。
export { fn }; // 导出一个已定义的函数
export const foo = Math.sqrt(2); // 导出一个常量
我们可以使用 * 和 from 关键字来实现的模块的继承
export * from 'article';
模块导出时,可以指定模块的导出成员。导出成员可以认为是类中的公有对象,而非导出成员可以认为是类中的私有对象:
var name = '石昊';
var age = 6;
export {name, age};
// 相当于导出 {name:name, age:age}
模块导出时,我们可以使用 as 关键字对导出成员进行重命名:
var name = '石昊';
var age = 6;
export {name as nickName, age};
注意,下面的语法有严重错误的情况:
// 错误演示1
export 1;
// 错误演示12
var a = 100;
export a;
export在导出接口的时候,必须与模块内部的变量具有一一对应的关系。直接导出1没有任何意义,也不可能在 import 的时候有一个变量与之对应.
export a 虽然看上去成立,但是 a 的值是一个数字,根本无法完成解构,因此必须写成 export {a} 的形式。即使a被赋值为一个 function,也是不允许的。而且,大部分风格都建议,模块中最好在末尾用一个export导出所有的接口,例如:
export {f1 as default,a,b,c};
默认导出
默认导出也被称做定义式导出。命名式导出可以导出多个值,但在在 import 引用时,也要使用相同的名称来引用相应的值。而默认导出每个导出只有一个单一值,这个输出可以是一个函数、类或其它类型的值,这样在模块 import 导入时也会很容易引用。
export default function() {}; // 可以导出一个函数
export default class(){}; // 也可以出一个类
命名式导出与默认导出
默认导出可以理解为另一种形式的命名式导出,默认导出可以认为是使用了 default 名称的命名式导出。
下面两种导出方式是等价的:
const name = '石昊';
export default name;
export { name as default };
export 使用示例
使用名称导出一个模块时:
// "info.js" 模块
export function getName(name) {
if(name === '唐三'){
return '海神'
}else if(name == '千仞雪'){
return '天使神'
}else if(name == '比比东'){
return '罗刹神'
}else{
return '酱油'
}
}
const weapon = '海神三叉戟';
export { weapon };
在另一个模块(脚本文件)中,我们可以像下面这样引用:
import { getName, weapon } from 'info';
console.log(getGodName("比比东")); // 罗刹神
console.log(weapon); // 海神三叉戟
使用默认导出一个模块时:
// "info.js"模块
export default function(name) {
if(name === '唐三'){
return '海神'
}else if(name == '千仞雪'){
return '天使神'
}else if(name == '比比东'){
return '罗刹神'
}else{
return '酱油'
}
}
在另一个模块(脚本文件)中,我们可以像下面这样引用,相对名称导出来说使用更为简单:
// 引用 "info.js"模块
import getName from 'info';
console.log(getName("唐三")); // 海神
import 导入模块
import 语法用于从已导出的模块、脚本中导入函数、对象、指定文件(或模块)的原始值。
import 模块导入与 export 模块导出功能相对应,也存在两种模块导入方式:命名式导入(名称导入)和默认导入(定义式导入)。
import 的语法跟 require 不同,而且 import 必须放在文件的最开始,且前面不允许有其他逻辑代码,这和其他所有编程语言风格一致。
import defaultMember from "module-name";
import * as name from "module-name";
import { member1 } from "module-name";
import { member as alias } from "module-name";
import { member1 , member2 } from "module-name";
import { member1 , member2 as alias2 , [...] } from "module-name";
import defaultMember, { member1 [ , [...] ] } from "module-name";
import defaultMember, * as name from "module-name";
import "module-name";
- defaultMember 导入默认导出成员
- name 从将要导入模块中收到的导出值的名称
- member1,member1 导入指定名称的多个成员
- alias 别名,对指定导入成员进行的重命名
- module-name 要导入的模块,是一个文件名
- as 重命名导入成员名称
- from 从已经存在的模块、脚本文件等导入
命名式导入
我们可以通过指定名称,就是将这些成员插入到当作用域中。导入时,可以导入单个成员或多个成员:
// 花括号里面的变量与 export 后面的变量一一对应
import { member } from "my-module";
import { name, age } from "my-module";
通过 * 符号,我们可以导入模块中的全部属性和方法。当导入模块全部导出内容时,就是将导出模块所有的导出绑定内容,插入到当前模块的作用域中:
import * as myModule from "my-module";
// myModule 可以访问到"my-module"所有的导出绑定内容
导入模块对象时,也可以使用as对导入成员重命名,以方便在当前模块内使用:
import {reallyReallyLongModuleMemberName as shortName} from "my-module";
导入多个成员时,同样可以使用别名:
import {reallyReallyLongModuleMemberName1 as shortName1, reallyReallyLongModuleMemberName2 as shortName2} from "my-module";
导入一个模块,但不进行任何绑定:
import "my-module";
默认导入
在模块导出时,可能会存在默认导出。同样的,在导入时可以使用import指令导出这些默认值。
直接导入默认值:
import myDefault from "my-module";
也可以在命名式导入中,同时使用默认导入:
import myDefault, * as myModule from "my-module"; // myModule 做为命名空间使用
import myDefault, {foo, bar} from "my-module"; // 指定成员导入
import 使用示例
// 导出文件 a.js
function getData(url, callback) {
let xhr = new XMLHttpRequest();
xhr.onload = function () {
callback(this.responseText)
};
xhr.open("GET", url, true);
xhr.send();
}
export function getUserInfo(url, callback) {
getData(url, data => callback(JSON.parse(data)));
}
// 导入文件 b.js
import { getUserInfo } from "b";
getUserInfo("http://baidu.com", data => {
console.log(data);
}
default 关键字
// a.js
export default function() {}
// 等效于
function a() {};
export {a as default};
在import的时候,可以这样用:
import a from './a';
// 等效于
import {default as a} from './d';
这个语法糖的好处就是import的时候,可以省去花括号{},简单的说,如果import的时候,你发现某个变量没有花括号括起来(没有*号),那么你在脑海中应该把它还原成有花括号的as语法。
import $,{ each, map } from 'jquery';
// import 后面第一个 $ 是 {defalut as $} 的替代写法
as 关键字
as简单的说就是取一个别名 export 和 import 中都可以使用:
// a.js
var a = function() {};
export {a as fun};
// b.js
import {fun as a} from './a';
a();
上面这段代码,export 的时候,对外提供的接口是 fun,它是a.js 内部 a 这个函数的别名。
import 中的 as 就很简单,就是你在使用模块里面的方法的时候,给这个方法取一个别名,好在当前的文件里面使用。之所以是这样,是因为有的时候不同的两个模块可能通过相同的接口,比如有一个c.js也通过了fun这个接口:
// c.js
export function fun() {};
如果在 b.js 中同时使用 a 和 c 这两个模块,就必须想办法解决接口重名的问题,as 就解决了。