ECMAScript的2015年(原名为ES6)引入了一个全新的功能和概念,前端的JavaScript的世界,奇怪的是已经存在了很长一段时间-模块。ES2015正式肯定什么CommonJS的(对Node.js的模块的基础)和AMD曾试图在试图把全部的优点和漏下的弱点来解决:前端
- 紧凑语法
- 异步和可配置模块加载
本文将重点ES2015模块的语法和它的一些陷阱的。模块的加载和包装将在其余时间覆盖。java
为何模块?
若是这听起来像一个明显的问题,你也许能够跳转到文章中的下一节。若是你还在读这篇文章,这里是你的,为何JavaScript须要的模块基本底漆。webpack
目前最经常使用的JavaScript平台是一个Web浏览器而设计的全部代码的执行在一个单一的全球背景。这使得它很是具备挑战性的编写哪怕是很小的应用程序,而没必要处理命名冲突。从本质上说,这一切都归结为代码组织,而不是没必要担忧每次须要声明一个新的一次重挫现有的变量。git
传统的JavaScript应用程序是在文件的分区,并在制做的时候链接在一块儿。当这个已经证实本身是至关繁琐的,咱们就开始包装每个文件中的IIFE:(function() { ... })();
。这种类型的构造建立一个本地范围等模块的想法构思。这之后表如今CommonJS的和AMD系统加载代码,并推出的“模块”为JavaScript的概念。es6
换句话说,目前的“模块”系统(AB)利用现有的语言构造赋予它新的功能。ES2015经过适当的语言功能正式肯定这些概念,并让他们的官员。github
建立模块
一个JavaScript模块是出口一些其余模块消耗文件。请注意,咱们仅在浏览器中谈到ES6 / 2015的模块,不会被谈论的Node.js如何组织的模块系统。有几件事情建立ES2015模块时,要牢记:web
模块有本身的适用范围
不一样于传统的JavaScript,使用模块时,你没必要担忧污染全局范围。事实上,这个问题是彻底相反 - 你必须输入你须要使用到每个模块的一切。后来,可是,是一个更好的主意,由于你能够清楚地看到全部的每一个模块中使用的依赖。api
命名模块
一个模块的名字来自任何文件或文件夹名称,则能够省略.js
在两种状况下扩展。这里是如何工做的:浏览器
- 若是你有一个指定的文件
utils.js
,你能够经过导入./utils
相对路径。 - 若是你有一个命名的文件
./utils/index.js
,您经过引用它./utils/index
或干脆./utils
。这可让你有一大堆的文件夹中,并将其导入为单个模块。
出口和进口
大多数模块输出一些功能的其余模块使用新的ES2015导入export
和import
关键字。模块能够导出和导入任何类型的一个或多个变量,是一Function
,Object
,String
,Number
,Boolean
等。
默认出口
每一个模块均可以有一个,且只有一个,能够导出和导入不指定变量名称默认的导出。例如:
1
2
3
4
5
6
7
8
9
|
// hello-world.js
export default function() {}
// main.js
import helloWorld from './hello-world';
import anotherFunction from './hello-world';
helloWorld();
console.log(helloWorld === anotherFunction);
|
在等效CommonJS的将是:
1
2
3
4
5
6
7
8
9
|
// hello.js
module.exports = function() {}
// main.js
var helloWorld = require('./hello-world');
var anotherFunction = require('./hello-world');
helloWorld();
console.log(helloWorld === anotherFunction);
|
任何JavaScript值能够从一个模块做为默认的导出:
1
2
3
|
export default 3.14;
export default {foo: 'bar'};
export default 'hello world';
|
命名出口
除了默认的出口住命名的出口。在这种状况下,你必须明确地指定要导出并使用相同的名称将其导入的变量名。一个模块能够有任意数量的任何类型的命名出口。
1
2
3
4
|
const PI = 3.14;
const value = 42;
export function helloWorld() {}
export {PI, value};
|
在等效CommonJS的将是:
1
2
3
4
5
|
var PI = 3.14;
var value = 42;
module.exports.helloWorld = function() {}
module.exports.PI = PI;
module.exports.value = value;
|
您还能够更改出口名称不重命名原来的变量,例如:
1
2
|
const value = 42;
export {value as THE_ANSWER};
|
在等效CommonJS的将是:
1
2
|
var value = 42;
module.exports.THE_ANSWER = value;
|
若是你有名称冲突或只是想以不一样的名称比原来进口的变量,你可使用as
像这样的关键字:
1
|
import {value as THE_ANSWER} from './module';
|
在等效CommonJS的将是:
1
|
var THE_ANSWER = require('./module'').value;
|
导入全部的事情
一个简单的方法来导入全部的值从一个命令一个模块是使用*
符号。该组中的全部模块的匹配出口名称的属性的单一对象变量下的出口。默认出口放在下default
属性。
1
2
3
4
5
6
7
8
9
10
|
// module.js
export default 3.14;
export const table = {foo: 'bar'};
export function hello() {};
// main.js
import * as module from './module';
console.log(module.default);
console.log(module.table);
console.log(module.hello());
|
在等效CommonJS的将是:
1
2
3
4
5
6
7
8
9
10
|
// module.js
module.exports.default = 3.14;
module.exports.table = {foo: 'bar'};
module.exports.hello = function () {};
// main.js
var module = require('./module');
console.log(module.default);
console.log(module.table);
console.log(module.hello());
|
值得一提的如何使用时,默认的出口进口的区别import * as foo from
和import foo from
。后来只进口default
出口和* as foo
进口的一切模块出口做为一个单一的对象。
导出全部的事情
至关广泛的作法是在一个模块中从另外一个转口一些特定的值(甚至所有)。这就是所谓的再出口。被触发请注意,您能够在同一个“名”多个不一样的值倍再出口没有一个错误。在这种状况下,去年出口值获胜。
1
2
3
4
5
6
7
8
9
10
|
// module.js
const PI = 3.14;
const value = 42;
export const table = {foo: 'bar'};
export function hello() {};
// main.js
export * from './module';
export {hello} from './module';
export {hello as foo} from './module';
|
在等效CommonJS的将是:
1
2
3
4
5
6
7
8
|
// module.js
module.exports.table = {foo: 'bar'};
module.exports.hello = function () {};
// main.js
module.exports = require('./module');
module.exports.hello = require('./module').hello;
module.exports.foo = require('./module').hello;
|
陷阱
要明白,什么是被导入到模块都不引用或值,但绑定是很重要的。你能够认为它是“干将”,以居住模块的身体里面的变量。这致使一些行为彷佛出人意料。
缺乏错误
当名字从一个模块中导入变量,若是你犯了一个错字或变量被删除之后,没有错误将在进口过程当中引起的,而不是进口的结合会undefined
。
1
2
3
4
5
6
|
// module.js
export const value = 42;
// main.js
import {valu} from './module'; // no errors
console.log(valu); // undefined
|
可变绑定
进口绑定是指一个模块的身体里面的变量。这将致使当您导入“按值”变量,如一个有趣的反作用Number
,Boolean
或String
。这多是由于该变量的值将经过操做所述的进口模块以外被改变。换言之,一个“由值”变量可别处突变。下面是一个例子。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
// module.js
export let count = 0;
export function inc() {
count++;
}
// main.js
import {count, inc} from './module; // `count` is a `Number` variable
assert.equal(count, 0);
inc();
assert.equal(count, 1);
|
另外,在上述的例子中count
变量是的Number
类型,但其值出如今改变main
模块。
导入的变量是只读
不管是从一个模块被导出什么样的声明,进口变量老是只读的。你能够,可是,改变进口对象的属性。
1
2
3
4
5
6
7
8
9
|
// module.js
export let count = 0;
export const table = {foo: 'bar'};
// main.js
import {count, table} from './module;
table.foo = 'Bar'; // OK
count++; // read-only error
|
测试模块
测试,或者更具体地说:存根和嘲笑由模块输出变量,遗憾的是并无被新的ES2015的模块系统解决。就像使用CommonJS的,导出的变量不能从新分配。应对方法之一就是导出的对象,而不是单独的变量。
1
2
3
4
5
6
7
8
9
10
|
// module.js
export default {
value: 42,
print: () => console.log(this.value)
}
// module-test.js
import m from './module';
m.value = 10;
m.print(); // 10
|
底线
ES2015模块标准化的方式模块加载和分辨率应该在现代的JavaScript来实现。之间是否使用的参数CommonJS的或AMD终于获得了解决。
咱们获得了紧凑型模块的语法和静态模块定义能够协助将来的编译器优化,甚至类型检查。如今,已经没有必要对UMD在公开发行库的样板。
ES6今天
你怎么能利用今天ES6特色?在过去几年中使用transpilers已成为常态。人与大公司再也不羞涩了。巴贝尔是一个ES6到ES5 transpiler,支持全部的ES6功能。
若是你正在使用相似Browserify或WebPACK中在JavaScript创建管道,增长巴贝尔transpilation 只须要一两分钟。还有就是,固然,对于几乎每个共同的Node.js构建系统同样咕嘟咕嘟,步兵和其余许多人的支持。
怎么样的浏览器?
大多数浏览器都遇上上实现新的功能,但没有一我的的全力支持。这是否意味着你在等什么?这取决于。这是开始使用的语言特征,将在1 - 2年内广泛可用,以便您熟悉他们到时候是个好主意。在另外一方面,若是你以为以上的源代码100%控制的须要,你应该坚持ES5如今。