一、ES6 Module 模块(import/export)
模块功能主要由两个命令构成:export和import。export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。
import和export命令只能在模块的顶层,不能在代码块之中
模块:一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如需从外部访问模块内部的变量及方法,则就必须使用export关键字输出该变量或方法。
导出export:
导出的是引用,也就是类似于指针,指向内存的值,当内部改变,外面使用时一样会变
// demo.js
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
var getName = function() {
return firstName + lastName;
}
export {firstName, lastName, year, getName };
//上面的文件ES6将其视为一个模块,里面用export命令对外部输出了三个变量一个函数。
//另外,export语句输出的接口,与其对应的值是动态绑定关系,
//即通过该接口,可以取到模块内部实时的值。//最后,export/import命令不能在块级作用域内,可以在全局作用域任何位置。
导入import:
静态导入:
// main.js
import {firstName, lastName, year} from './profile.js';
function setName(element) {
element.textContent = firstName + ' ' + lastName;
}
//上面代码的import命令,用于加载profile.js文件,并从中输入变量。
//import命令接受一对大括号,里面指定要从其他模块导入的变量名。
//大括号里面的变量名,必须与被导入模块(profile.js)对外接口的名称相同。
//整体导入
import * as myModule from './my-module.js';//不用加大括号{}
//将my-module里面输出的变量/函数,
//输入到myModule这个对象中作为属性/方法。
//通过myModule.属性名/myModule.方法()来调用
import './lodash.js';//立即执行lodash模块,但是不输入任何值
//默认导出/导入默认值:用户不需要知道模板中的变量名或函数名/先用export default语法默认导出
//既然是默认输出,那么一个模板只能使用一次
export default function foo() {
console.log('foo');
}
//导入默认值
import customName from './default.js';//不用加大括号{}
customName(); // 'foo'
动态导入: import()
import()函数可以用在任何地方,不仅仅是模块,非模块的脚本也可以使用。它是运行时执行,也就是说,什么时候运行到这一句,才会加载指定的模块。
另外,import()函数与所加载的模块没有静态连接关系,这点也是与import语句不相同。
import("模块路径")
//ES6 import()返回一个 Promise 对象
import(`./section-modules/${someVariable}.js`).then(module => {
module.loadPageInto(main);
}).catch(err => {
main.textContent = err.message;
});
//import()加载模块成功以后,这个模块会作为一个对象,当作then方法的参数。
//因此,可以使用对象解构赋值的语法,获取输出接口。
import('./myModule.js').then(({export1, export2}) => {// export1, export2代表从myModule.js中导出的输出接口,通过解构可得});
//如果模块有default输出接口,可以用参数直接获得。/---mymodules.js
export default function(){
return test;
}
/---test.js
import('./myModule.js').then((myModule)=>{
console.log(myModule.default) //获得上面的function
});
//也可以这样写
import('./myModule.js').then(({default:myModuleDefault})=>{ console.log(myModuleDefault) //获得上面的function});
注:
import()类似于 Node 的require()方法,区别主要是前者是异步加载,后者是同步加载。
适用场合:
(1)按需加载。
import()可以在需要的时候,再加载某个模块。
(2)条件加载
import()可以放在if代码块,根据不同的情况,加载不同的模块。
if (condition) {
import('moduleA').then(...);
} else {
import('moduleB').then(...);
}
(3)动态的模块路径
import()允许模块路径动态生成。
import(f()).then(...); //根据函数f的返回结果,加载不同的模块
二、CommonJS模块规范
CommonJS对模块的规范就三个:
1. 模块的定义 module、exports
CommonJS的模块只有一个唯一的出口, 那就是module.exports对象,我们把所有要导出的变量或函数都放到这个对象里, 再导出这个对象. 那我们就可以在外部访问到这些变量和函数, 而没有被导出的东西, 对外部的模块来说是不可见的.
/---index.js
function isNumber (n) {
return typeof n === 'number'
}
module.exports = {
sum: function(a, b) {
return a+b;
}
}
/----/
var mod = require('./index')
console.log(mod.sum(2, 2)) // 4
mod.isNumber() // 抛出错误
注:module.exports = {} 的形式导出, 也可以用exports.a = 'blabla' 的形式导出,
但是你决不能用exports = {} 的形式导出
最后导出的, 是module.exports而不是exports对象
2. 模块的引用 require
**require方法会返回模块中导出的module.exports对象
**
3. 模块的标识
模块标识主要有以下几种类型:
1. 是符合"小驼峰"式命名法的字符串. (从node_modules/系统模块中引入)
2. 以'.' 或 '..' 开头相对路径模块(相对当前目录引入)
3. 绝对路径(例如/var/www等绝对路径)
需要提到的一小点是, 文件后缀名".js"可以省略.
**其他:**Node对CommonJS的实现, 主要可以分为以下三点:
1. 模块路径分析:会分析传入的这个参数, 确定这个模块属于以下哪一类模块:
- 核心模块(Node自带的模块,跳过路径分析和文件定位)
- 路径模块(相对或绝对定位开始的模块)
- 自定义模块(node_modules里的模块,按照文件目录层级一层层往上找)
2. 模块定位:是得出模块的filename
会采用以下方式确定文件名
- 第一步, 找出目录下的package.json, 用JSON.parse()解析出main字段
- 第二步, 如果main字段指定的文件还是省略了扩展, 那么会依次补充.js, .node, .json尝试.
- 第三部, 如果main字段制定的文件不存在, 或者根本就不存在package.json, 那么会默认加载这个目录下的index.js, index.node, index.json文件.
3. **模块编译:**Node会新建一个模块对象,然后根据路径载入并编译。对于不同的文件扩展名,其载入方法也有所不同,具体如下所示
- js文件——通过fs模块同步读取文件后编译执行
- node文件——这是用C/C++编写的扩展文件,通过dlopen()方法加载最后编译生成的文件
- json文件——通过fs模块同步读取文件后,用JSON.parse()解析返回结果
- 其余扩展名文件——它们都被当做.js文件载入
每一个编译成功的模块都会将其文件路径作为索引缓存在Module._cache对象上,以提高二次引入的性能
三、ES6 Module与CommonJS差异
1、ES6 Module编译时输出接口(只引入导入的接口),CommonJS运行时加载(整个模块加载)
2、ES6 Module导入的是模块的引用,只存只读(inport接口是read-only),不能改变其内部状态,但内部值改变会影响外部使用的值,即原来模块中的值改变则该加载的值也改变
3、CommonJS导入的是模块中变量或者函数的值,即原来模块中的值改变不会影响已经加载的该值,类似于浅拷贝。
4、CommonJS this 指向当前模块,ES6 this 指向undefined
CommonJs模块化
// lib.js
var counter = 3;
function incCounter() {
counter++;
}
module.exports = {
counter: counter,
incCounter: incCounter
};
// main.js
var mod = require('./lib');
console.log(mod.counter); // 3
mod.incCounter();
console.log(mod.counter); // 3
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
注:CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成(它是一个模块之中最早执行的)。