介绍了JavaScript的ES6模块
ECMAScript的2015年(原名为ES6)引入了一个全新的功能和概念,前端的JavaScript的世界,奇怪的是已经存在了很长一段时间-模块。ES2015正式确定什么CommonJS的(对Node.js的模块的基础)和AMD曾试图在试图把所有的优势和漏下的弱点来解决:
- 紧凑语法
- 异步和可配置模块加载
本文将重点ES2015模块的语法和它的一些陷阱的。模块的加载和包装将在其他时间覆盖。
为什么模块?
如果这听起来像一个明显的问题,你也许可以跳转到文章中的下一节。如果你还在读这篇文章,这里是你的,为什么JavaScript需要的模块基本底漆。
目前最常用的JavaScript平台是一个Web浏览器而设计的所有代码的执行在一个单一的全球背景。这使得它非常具有挑战性的编写哪怕是很小的应用程序,而不必处理命名冲突。从本质上说,这一切都归结为代码组织,而不是不必担心每次需要声明一个新的一次重挫现有的变量。
传统的JavaScript应用程序是在文件的分区,并在制作的时候连接在一起。当这个已经证明自己是相当繁琐的,我们就开始包装每一个文件中的IIFE:(function() { ... })();
。这种类型的构造创建一个本地范围等模块的想法构思。这以后表现在CommonJS的和AMD系统加载代码,并推出的“模块”为JavaScript的概念。
换句话说,目前的“模块”系统(AB)利用现有的语言构造赋予它新的功能。ES2015通过适当的语言功能正式确定这些概念,并让他们的官员。
创建模块
一个JavaScript模块是出口一些其他模块消耗文件。请注意,我们仅在浏览器中谈到ES6 / 2015的模块,不会被谈论的Node.js如何组织的模块系统。有几件事情创建ES2015模块时,要牢记:
模块有自己的适用范围
不同于传统的JavaScript,使用模块时,你不必担心污染全局范围。事实上,这个问题是完全相反 - 你必须输入你需要使用到每一个模块的一切。后来,但是,是一个更好的主意,因为你可以清楚地看到所有的每个模块中使用的依赖。
命名模块
一个模块的名字来自任何文件或文件夹名称,则可以省略.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现在。