JavaScript 语言自创立之初,一直没有模块(module)体系,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来。
很多编程语言都有这项功能,比如 Python 的 import、Ruby 的 require,甚至就连 CSS 都有 @import,但是 JavaScript 没有这方面的支持,这增加了开发大型的、复杂的项目时的难度。
于是前端开发者们开始想办法,为了防止命名空间被污染,采用的是命名空间的方式。
在 ES6 之前,一些前端社区制定了模块加载方案,最主要的有 CommonJS 和 AMD 两种。前者用于服务器,后者用于浏览器。
但这两种规范都由开源社区制定,没有统一,而 ES6 中引入了模块(Module)体系,从语言层在实现了模块机制,实现了模块功能,而且实现得相当简单,为 JavaScript 开发大型的、复杂的项目扫清了障碍。
ES6 中的模块功能主要由两个命令构成:export 和 import。
export 命令用于规定模块的对外接口,import 命令用于输入其他模块提供的功能,二者属于相辅相成、一一对应关系。
一、什么是模块
模块可以理解为函数代码块的功能,是封装对象的属性和方法的 javascript 代码,它可以是某单个文件、变量或者函数。
模块实质上是对业务逻辑分离实现低耦合高内聚,也便于代码管理而不是所有功能代码堆叠在一起,模块真正的魔力所在是仅导出和导入你需要的绑定,而不是将所有的东西都放到一个文件。
在理想状态下我们只需要完成自己部分的核心业务逻辑代码,其他方面的依赖可以通过直接加载被人已经写好模块进行使用即可。
二、export 导出 命令
一个模块就是一个独立的文件,该文件内部的所有变量,外部无法获取。如果想从外部能够读取模块内部的某个变量,就必须使用 export 关键字输出该变量。分为以下几种情况:
(1)在需要导出的 lib.js 文件中,使用 export{接口} 导出接口,大括号中的接口名字为上面定义的变量,import 和引入的 main.js 文件中的 export 是对应的:
//lib.js 文件
let bar = "stringBar";
let foo = "stringFoo";
let fn0 = function() {console.log("fn0");
};
let fn1 = function() {console.log("fn1");
};
export{bar , foo, fn0, fn1}
//main.js 文件
import {bar,foo, fn0, fn1} from "./lib";
console.log(bar+"_"+foo);
fn0();
fn1();
(2)在 export 接口的时候,我们可以使用 XX as YY,把导出的接口名字改了,比如:xiaoming as haoren,
这样做的目的是为了让接口字段更加语义化。
//lib.js 文件
let fn0 = function() {console.log("fn0");
};
let obj0 = {}
export {fn0 as foo, obj0 as bar};
//main.js 文件
import {foo, bar} from "./lib";
foo();
console.log(bar);
(3)直接在 export 的地方定义导出的函数,或者变量:
//lib.js 文件
export let foo = ()=> {console.log("fnFoo") ;return "foo"},bar = "stringBar";
//main.js 文件
import {foo, bar} from "./lib";
console.log(foo());
console.log(bar);
(4)不需要知道变量名字(相当于是匿名的)的情况,可以 直接把开发的接口给 export。如果一个 js 模块文件就只有一个功能,那么就可以使用 export default 导出。
//lib.js
export default "string";
//main.js
import defaultString from "./lib";
console.log(defaultString);
这样做的好处是其他模块加载该模块时,import 命令可以为该匿名函数指定任意名字。
(5)export 也能默认导出函数,在 import 的时候,名字可以自定义,因为每一个模块的默认接口就一个:
//lib.js
let fn = () => "string";
export {fn as default};
//main.js
import defaultFn from "./lib";
console.log(defaultFn());
(6)使用通配符 *,重新导出其他模块的接口
//lib.js
export * from "./other";
// 如果只想导出部分接口,只要把接口名字列出来
//export {foo,fnFoo} from "./other";
//other.js
export let foo = "stringFoo", fnFoo = function() {console.log("fnFoo")};
//main.js
import {foo, fnFoo} from "./lib";
console.log(foo);
console.log(fnFoo());
三、import 导入命令
ES6 导入的模块都是属于引用,每一个导入的 js 模块都是活的,每一次访问该模块的变量或者函数都是最新的,这个是原生 ES6 模块 与 AMD 和 CMD 的区别之一。
使用 export 命令定义了模块的对外接口以后,其他 JS 文件就可以通过 import 命令加载这个模块。
//main.js 文件
import {bar,foo, fn0, fn1} from "./lib";
console.log(bar+"_"+foo);
fn0();
fn1();
大括号里面的变量名,必须与被导入模块对外接口的名称相同。
想要输入的变量重新取一个名字,import 命令要使用 as 关键字,将输入的变量重命名。
import {formatFn as fn0} from 'lib.js';
注:import 后面的 from 指定模块文件的位置,可以是相对路径,也可以是绝对路径,.js 后缀可以省略。
四、ES6 模块化的基本规则、特点
1、每一个模块只加载一次,每一个 JS 只执行一次,如果下次再去加载同目录下同文件,直接从内存中读取。一个模块就是一个单例,或者说就是一个对象。
2、每一个模块内声明的变量都是局部变量,不会污染全局作用域。
3、模块内部的变量或者函数可以通过 export 导出。
4、一个模块可以导入别的模块。
五、class 与模块化相结合实例
结合上节课我们学的 ES6 class 与面向对象编程的知识,我们再实现一个把 class 和模块化结合的例子。
首先我们创建一个 parent.js 文件,使用 class 类的写法创建一个 Parent 类:
const name = "tom";
const age = "20";
class Parent{hw(){console.log(`hello world`)
}
static obj(){console.log('obj')/* 表示为静态方法不回呗实例继承,而是直接通过类调用。*/
}
}
var parent = new Parent()
parent.hw()//hell world
export{name,age,Parent}
之后在 child.js 中分别引入 parent.js 中的 name、age、Parent
import {name,age,Parent} from './parent'
class Child extends Parent{constructor(obj){
// 就是 new 命令自动跳用方法。一个类必须要有 constructor,如果没定义,有默认添加一个空的。super()// 调用父类的 constructor()
this._config = obj;
console.log(obj.name+"年龄"+obj.age)
}
hw(){console.log("hw")
}
set val(value){
this._config.name = value;
console.log(`name=${value}`)
}
get val(){console.log(this._config.name);
}
}
Child.obj()//obj 继承父类 static 方法
var model = new Child({name,age}) //tom 年龄 20
model.hw()//hw
model.val = "jock"; //name=jock
model.val//jock
六、总结
本文主要从什么是模块,模块的导出(导出变量、函数、类、文件等),模块的导入(单个导入、多个导入、导入整个)等角度讲述了 ES6 模块化操作。
ES6 模块的设计思想,是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。比如 CommonJS 模块就是对象,输入时必须查找对象属性。
模块打包现在最好用的就是 webpack 了,webpack 作为一款新兴的模块化管理和打包工具,其视任意文件都为模块,并打包成 bundle 文件,相比于 browserify 更加强大。
模块化开发是前端开发的一大趋势,比如大家去看 vue、react、angular,或者你们公司的项目源码,你会发现,几乎所有项目都使用了模块化。小伙伴们一定要紧跟时代的大潮,将组件化开发,模块化开发,自动化构建结合,探索高效的开发之道。