模块
模块就是实现特定功能的一组方法。
理想情况下,开发者只需要实现核心的业务逻辑,其他都可以加载别人已经写好的模块。但是,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(getName("比比东")); // 罗刹神 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 就解决了。