模块化
前端模块化的好处都已经被说烂了,归纳为两点:
- 避免全局变量污染
- 有效的处理依赖关系
ES2015(ES6)终于引入了模块的概念,最近学习了下,顺便记下笔记。
准备工作
借助Babel演示Demo。
创建个module目录,并在该目录里执行:
npm init -y
然后安装各种依赖:
npm install --save-dev babel-cli babel-preset-env babel-preset-es2015
接着创建.babelrc文件:
{
"presets": [
"es2015",
"env"
]
}
修改package.json文件的"scripts"配置:
"scripts": {
"build": "babel src --out-dir dist"
}
最终的目录结构如:
模块导出
一个文件定义一个模块,通过export语句导出该模块输出的变量。export语句有两种语法格式:命名导出, 默认导出。
1 命名导出
命名导出就是显示指定导出变量的名称。
在src目录下创建math.js,index.js文件。
math.js内容:
// Case 1: export后面跟变量输出声明语句
var PI = 3.14;
export { PI }; // 导出变量PI,花括号不可省略
// Case 2: export后面直接跟变量定义语句
export var add = function (x, y) { // 导出函数print
return x + y;
}
这表示math.js模块导出变量PI和add, 用NodeJS的模块格式可表示为:
var PI = 3.14;
var add = function (x, y) { // 导出函数print
return x + y;
}
module.exports.PI = PI;
module.exports.add = add;
index.js内容:
import * as Math from "./math.js"; // import是导入模块,后面会说。
console.log(Math.PI);
console.log(Math.add(1, 2));
然后执行下面的命令进行Babel转换:
npm run build
如果没有报错的化,那module目录下应该生成了dist子目录,并且生成了index.js和math.js文件(先不要在意文件的内容)。
然后在执行命令:
node dist/index.js
看看输出结果是否OK:
3.14
3
命名导出主要用于输出多个变量,并且export此时后面既可以是变量输出声明语句块,也可以是变量定义语句(如Case 2)。
如果导出多个变量,可以采用简写格式(调整math.js内容):
var PI = 3.14;
var add = function (x, y) {
return x + y;
}
export { PI, add }; // 简写格式,统一列出需要输出的变量
重复上述步骤中执行npm和Node命令查看看输出结果是否OK。
并且该简写格式还可以对输出的变量重命名:
再次修改math.js
var PI = 3.14;
var add = function (x, y) {
return x + y;
}
export { PI, add as Add}; // 把输出变量add重命名为Add(注意不用双引号)
通过关键字as把输出变量add重命名为Add,注意Add是个字面量,不是字符串不需要引号。同样在导入math.js模块的index.js模块也要修改下:
import * as Math from "./math.js";
console.log(Math.PI);
console.log(Math.Add(1, 2)); // Add方法名称改动了。
2 默认导出
通过关键字default修饰export可以指定一个模块的默认输出(在导入模块的默认输出时,不需要指定导出变量名称,这个后面再说)。
// Case 3 常量
export default 25;
// Case 4 变量
//var PI = 3.14;
//export default PI
注意
1). default修饰的export后面只能跟表达式(变量,字面量,函数调用等),命名输出的export后面只能跟语句(花括号语句块,变量声明语句);
2). 一个模块最多只能有一个默认导出。
模块导入
通过import语句导入外部模块。对应export语句的两种导出方式(命名模块导出和默认模块导出),import也分别存在两种不同的模块导入语法格式。
1 导入模块的命名输出变量
修改index.js:
import { PI, Add} from './math.js';
console.log(PI);
console.log(Add(1, 2));
表示:导入math.js模块里输出的变量PI, Add,注意名称必须要和math.js模块的输出变量一一对应,否则就是undefined。
重新执行npm和node命令看看输出是否OK。
该格式还支持对导入的变量重命名:
import { PI as pi, Add as add} from './math.js';
如果导入一个模块所有命名输出,可采用通配符"*:
修改index.js:
import * as Math from './math.js'; // 此时必须通过as指定个别名
console.log(Math.PI);
console.log(Math.Add(1, 2));
表示导入模块math.js所有命名输出变量,并通过Math变量引用。
2 导入模块的默认输出
修改math.js:
var PI = 3.14;
var add = function (x, y) {
return x + y;
}
export { PI, add as Add}; // 简写格式,统一列出需要输出的变量
export default function say() { // 默认输出
console.log("I am default export");
}
修改index.js:
import say from "./math.js"; //
say();
表示导入模块math.js的默认输出,此时不可以用as重命名哦。
执行命令查看输出是否OK。
如果同时导入模块的命名输出和默认输出,可采用格式:
import say, * as Math from './math.js';
// OR
import say, { PI, Add } from './math.js';
有个限制条件默认导入一定放在命名导入前面,即不能写成:
import * as Math, say from './math.js';
// OR
import { PI, Add }, say from './math.js';
3 只导入
如果只导入一个模块,但不引用模块的输出变量,可以简写为:
import './math.js'
此时只会出发模块math.js的执行。
修改导入/导出的变量值
1 修改导入的变量值
模块可以导出任何类型变量(引用变量和值变量),如果在模块index.js里修改了模块math.js导出的引用变量或者值变量,那会影响模块math.js里的值么?
修改math.js:
var PI = 3.14;
var Person = {
name: 'Bob'
}
export { PI, Person};
export default function say() {
console.log(`Person:${JSON.stringify(Person)}, PI:${PI}`)
}
修改index.js:
import say, * as Math from './math.js';
say(); // 修改前
Math.Person = 12;
Math.PI = 0;
say(); // 修改后
执行npm和node查看输出:
从输出可以看出虽然我们在index.js模块里修改了math.js模块的导出变量,但并不会影响math.js里的值。
2 修改导出的变量值
反过来想想,如果模块math.js修改了其导出的引用变量或者值变量在,那会影响模块index.js里的取值么?
修改math.js:
var Count = 0;
var increase = function() {
Count++;
}
var Person = {
name: 'Bob'
}
var changeName = function() {
Person.name = 'John';
}
export { Count, Person, increase, changeName};
修改index.js:
import say, * as Math from './math.js';
console.log(`Person:${JSON.stringify(Math.Person)}, Count:${Math.Count}`);// 修改前
Math.increase();
Math.changeName();
console.log(`Person:${JSON.stringify(Math.Person)}, Count:${Math.Count}`);// 修改后
输出:
从输出可以看出只要math.js修改了导出值就会影响index.js的取值。
3 小结
1). 模块的输出变量(引用类型或者值类型)对其他引用模块来说都是只读的
2). 模块修改了其输出变量的值会影响其他引入模块的取值。
再看export语句
了解了export和import的用法后,我们再看看export语句另一个语法规则:导出引入的模块的变量。上面的例子里export语句都是导出模块自身定义的变量,export还可以导模块引入模块的输出。
在src目录添加文件log.js:
export var Log = function(msg) {
console.log(msg);
}
export default 'I am log.js';
修改math.js:
var Count = 0;
var increase = function() {
Count++;
}
var Person = {
name: 'Bob'
}
var changeName = function() {
Person.name = 'John';
}
export { Count, Person, increase, changeName};
export default function say() {
console.log(`Person:${JSON.stringify(Person)}, Count:${Count}`)
}
export * from './log.js'; //
修改index.js:
import say, * as Math from './math.js';
Math.Log('hello'); // 该方法来之log.js模块
查看下输出是否OK。
其中“export * from './log.js';”表示导出模块log.js所有的命名输出。等价于:
export { Log } from './log.js';
注意:这种语法格式“export * from './log.js';”不可以定义别名,而花括号的语法格式“export { Log } from './log.js'”可以定义别名,即:
export { Log as log} from './log.js'
怎么在math.js模块里导出模块log.js的默认输出呢?只能采用先导入,再导出方式:
import logName from './log.js';
export { logName }
参考
- MDN export
- MDN import
- Babel 入门教程
- Babel Learn ES2015
- An Introduction To JavaScript ES6 Modules