ES6 支持 javascript 模块化,模块之间的导入导出有一定的规则,因为总是记不住,做一下学习笔记。
export 导出
export 命令用于规定对外的接口。一个模块就是一个独立的文件,文件内部的变量,外部是无法获取的,如果想读取模块内部的某个变量,必须使用 export 关键字输出该变量。
export 语句输出的接口,与其对应的值是动态绑定关系,即通过该接口,可以取到模块内部实时的值。
export 命令可以出现在模块的任何位置,只要处于模块顶层就可以。如果处于块级作用域内,就会报错,也就是不能出现在表达式,if 判断等块内。
// export 第一个种写法
export var firstName = "zhangsan";
// 第二种写法,输出一组变量时,一定要有大括号
var secondName = "lisi";
var thirdName = "andy";
export {secondName, thirdName};
// 输出函数
function v1(){}
export {v1}
// 也可以更改输出的名字,对外暴露的就是newFn
export {v1 as newFn}
import 命令
- 独立加载
不允许修改 import 进来的变量,除非操作的是一个变量中的属性,一般情况下不建议修改变量,仅做只读。
import 命令具有提升效果,会提升到整个模块的头部,首先执行。
由于 import 是静态执行,所以不能使用表达式和变量,这些只有在运行时才能得到结果的语法结构。所谓静态执行,指的是在编译时执行,在程序运行时不执行。
如果多次重复执行同一句import语句,那么只会执行一次,而不会执行多次。
// 第一种引入,直接引入输出的,大括号里面的变量名必须与export里面的一一对应
import {firstName,secondName} from './profile.js';
// 第二种写法,改变名称,将lastName 改为 surname
import {lastName as surname} from './profile.js';
// 仅仅执行,不输入。以下仅仅执行lodash模块,但是不输入任何值。
import 'lodash';
- 整体加载
整体加载用在不知道导出来的名字,用星号(*)指定一个对象,所有输出值都加载在这个对象上面。
// circle.js
export function area(radius) {
return Math.PI * radius * radius;
}
export function circumference(radius) {
return 2 * Math.PI * radius;
}
// main.js
// 整体加载
import * as circle from './circle';
console.log('圆面积:' + circle.area(4));
console.log('圆周长:' + circle.circumference(14));
// 下面两行都是不允许的,因为不允许运行时改变
circle.foo = 'hello';
circle.area = function () {};
export default 默认输出
export default 为模块指定默认输出。模块只能有一个 export default,但是可以有多个 export
// export-default.js
export default function () {
console.log('foo');
}
// 其他函数在加载时,可以以任意名字来加载
// import-default.js
import customName form './export-default';
customName(); // 'foo'
// 后面不能跟表达式,因为export default 输出的是一个default变量
export default var a = 1; // 错误
// 当输出 export default 后,可以同时 import 多个
import _, {each, each as forEach} from 'lodash';
// export default 也可以用来输出类。
// MyClass.js
export default class { ... }
// main.js
import MyClass from 'MyClass';
let o = new MyClass();
export 与 import 的复合写法
export 与 import 的复合写法相当于转手导出,当前模块没有引用导出的模块。
export { foo, bar } from 'my_module';
// foo和bar实际上并没有被导入当前模块,只是相当于对外转发了这两个接口,导致当前模块不能直接使用 foo 和 bar。
// 可以简单理解为
import { foo, bar } from 'my_module';
export { foo, bar };
// 接口改名
export { foo as myFoo } from 'my_module';
// 整体输出
export * from 'my_module';
// 具名接口改为默认接口的写法如下。
export { es6 as default } from './someModule';
// 等同于
import { es6 } from './someModule';
export default es6;
// 同样地,默认接口也可以改名为具名接口。
export { default as es6 } from './someModule';
CommonJS 的写法
CommonJS通过 module 对象来把 javascript 代码模块化。引入模块通过 require语法,导出模块通过 module.exports = {} 。范例
// lib.js
var counter = 3;
function incCounter() {
counter++;
}
module.exports = {
counter: counter,
incCounter: incCounter,
};
// 在main.js里面加载这个模块。
// main.js
var mod = require('./lib');
console.log(mod.counter); // 3
mod.incCounter();
console.log(mod.counter); // 3
// 上面代码说明,lib.js模块加载以后,它的内部变化就影响不到输出的mod.counter了。这是因为mod.counter是一个原始类型的值,会被缓存。除非写成一个函数,才能得到内部变动后的值。
读取模块内的数值 需要通过取值器函数。
// lib.js
var counter = 3;
function incCounter() {
counter++;
}
module.exports = {
get counter() {
return counter
},
incCounter: incCounter,
};
// 上面代码中,输出的counter属性实际上是一个取值器函数。现在再执行main.js,就可以正确读取内部变量counter的变动了。
$ node main.js
3
4
ES6 模块的引用是复制的引用,一处修改,其他地方都会改。
// lib.js
export let counter = 3;
export function incCounter() {
counter++;
}
// main.js
import { counter, incCounter } from './lib';
console.log(counter); // 3
incCounter();
console.log(counter); // 4
node 中的 import
目前,Node 的import命令只支持加载本地模块(file:协议),不支持加载远程模块。
如果模块名不含路径,那么import命令会去node_modules目录寻找这个模块。
如果模块名包含路径,那么import命令会按照路径去寻找这个名字的脚本文件。范例
import 'file:///etc/config/app.json';
import './foo';
import './foo?search';
import '../bar';
import '/baz';
/* 如果脚本文件省略了后缀名,比如import './foo',Node 会依次尝试四个后缀名:./foo.mjs、./foo.js、./foo.json、./foo.node。如果这些脚本文件都不存在,Node 就会去加载./foo/package.json的main字段指定的脚本。如果./foo/package.json不存在或者没有main字段,那么就会依次加载./foo/index.mjs、./foo/index.js、./foo/index.json、./foo/index.node。如果以上四个文件还是都不存在,就会抛出错误。
最后,Node 的import命令是异步加载,这一点与浏览器的处理方法相同 。
网友评论