最近在学习使用Webpack3的时候发现,它已经可以在不使用babel的情况下使用ES6的模块加载功能了。(不包括webpack.config.dev.js文件。
因为它是是webpack的配置文件,是nodejs直接运行处理的。所以仍需使用CommonJS规范,其他被webpack编译的js模块可以使用es6写法。)
说到ES6的模块加载功能,我们先复习一下CommonJS规范吧:
一 . CommonJS规范规定,在每个模块内的module
变量代表当前模块。这个变量的module.exports属性
是对外开放的接口。
加载某个模块,其实是加载此模块的module.exports
属性。
exampleA.js
var x = 5;
var addX = function (value) {
return value + x;
};
module.exports.x = x;
module.exports.addX = addX;
上面代码通过module.exports对外暴露
出变量x
和函数addX
。
每个模块的module对象默认拥有这些属性:
module.id
模块的识别符,通常是带有绝对路径的模块文件名。
module.filename
模块的文件名,带有绝对路径。
module.loaded
返回一个布尔值,表示模块是否已经完成加载。
module.parent
返回一个对象,表示调用该模块的模块。
module.children
返回一个数组,表示该模块要用到的其他模块。
module.exports
表示模块对外输出的值。
二 . export变量:为了方便,每个模块内都有一个指向module.exports的对象 exports。
三 .require
方法:用于加载模块。
读取并执行一个JavaScript文件,然后返回该模块的exports对象。
exampleB.js
var exampleA = require('./exampleA.js');//加载上文的模块 console.log(exampleA.x); //
console.log(exampleA.addX(1)); //
以上是Commonjs的常用功能。其他功能参考阮大神的:http://javascript.ruanyifeng.com/nodejs/module.html。
ES6中模块规范:
一 . export方法:
用法1:
// profile.js
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;
用法2:
// profile.js
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
export { firstName, lastName, year};
//export { firstName as obj1, lastName as obj2, year as obj3};//可以使用as
关键字重命名
与CommonJS不同的是:export
语句的输出接口,与其对应的值是动态绑定关系,即通过该接口,可以取到模块内部实时的值。
二 . import方法
import {firstName, lastName, year} from './profile.js';
//import { lastName as surname } from './profile.js';
//import * as profile from './profile.js';
import
命令具有提升效果,会提升到整个模块的头部,首先执行。
三 . export default 方法
// export-default.js
export default function () {
console.log('foo');
}
//其他模块加载该模块时,import
命令可以为该匿名函数指定任意名字。
// import-default.js
import customName from './export-default';
customName(); // 'foo'
上面的import方法
,可以用任意名称指向export-default.js(指向)
输出的方法,这时就不需要知道原模块输出的函数名。
这时import
命令后面,不使用大括号。
如果想在一条import
语句中,同时引入default接口和其他接口,可以写成下面这样。
import _, { each, each as forEach } from 'lodash';
四 . import() 方法
动态加载模块,返回一个 Promise 对象。(nodejs中使用require.ensure实现)
import('./someModules.js')
.then(module => {
console.log(module);
})
.catch(err => {
console.log(err);
});
ES6模块与CommonJS模块的区别:
一 . CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
CommonJS 模块是运行时加载js文件,ES6 模块是编译时加载模块,输出接口。
二 . 第二个差异是因为 CommonJS 加载的是一个对象(即module.exports
属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,
它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。(然而nodejs中对ES6模块的实现任是基于自身的CommonJS 并没有实现静态加载)
CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。
//CommonJS
// lib.js
var counter = 3;
function incCounter() {
counter++;
}
module.exports = {
counter: counter,
incCounter: incCounter,
};
上面代码输出内部变量counter
和改写这个变量的内部方法incCounter
。然后,在main.js
里面加载这个模块。
//CommonJS
// main.js
var mod = require('./lib'); console.log(mod.counter); //
mod.incCounter();
console.log(mod.counter); //
上面代码说明,lib.js
模块加载以后,它的内部变化就影响不到输出的mod.counter
了。这是因为mod.counter
是对模块内counter对象值的拷贝。
除非写成一个函数,才能实时获取mod.counter的变动。
//CommonJS
// lib.js
var counter = 3;
function incCounter() {
counter++;
}
module.exports = {
get: counter() {
return counter
},
incCounter: incCounter,
};
ES6模块:
// lib.js
export let counter = 3;
export function incCounter() {
counter++;
} // main.js
import { counter, incCounter } from './lib';
console.log(counter); //
incCounter();
console.log(counter); //
结果说明,ES6 模块输入的变量counter
是活的(闭包),完全反应其所在模块lib.js
内部的变化。
注:参考阮一峰大神的《ECMAScript6入门》。