[譯]JavaScript ES6模塊指南

jopen 9年前發布 | 18K 次閱讀 JavaScript開發 JavaScript

原文 http://hao.jser.com/archive/8772/

前言

ECMAScript 2015(又稱ES6)提供了一個前端JavaScript缺失已久的特性 —— 模塊。ES2015中的模塊參考了CommonJS規范(目前Node.js的模塊規范)以及AMD規范,并且盡可能的取其精華,去其糟粕:

  • 它提供了簡潔的語法

  • 以及異步的,可配置的模塊加載

這篇文章將會專注于ES2015的模塊語法以及注意點。關于模塊的加載和打包,將會在另一篇文章中細述。

為什么要使用模塊?

目前最普遍的JavaScript運行平臺便是瀏覽器,在瀏覽器中,所有的代碼都運行在同一個全局上下文中。這使得你即使更改應用中的很小一部分,你也要擔心可能會產生的命名沖突。

傳統的JavaScript應用被分離在多個文件中,并且在構建的時候連接在一起,這稍顯笨重。所以人們開始將每個文件內的代碼都包在一個自執行函數中:(function() { ... })();。這種方法創建了一個本地作用域,于是最初的模塊化的概念產生了。之后的CommonJS和AMD系統中所稱的模塊,也是由此實現的。

換句話說,現存的“模塊”系統是使用已有的語言特性所實現的。而ES2015則通過添加適當的新的語言特性,來使之官方化了。

創建模塊

一個JavaScript模塊就是一個對其他模塊暴露一些內部屬性/方法的文件。我們在這里僅會討論瀏覽器中的ES2015模塊系統,并不會涉及Node.js是如何組織它自身的模塊的。一些在創建ES2015模塊時需要注意的點:

每個模塊都有自己的上下文

和傳統的JavaScript不同,在使用模塊時,你不必擔心污染全局作用域。恰恰相反,你需要把所以你需要用到的東西從其他模塊中導入進來。但是,這樣也會使模塊之間的依賴關系更為清晰。

模塊的名字

模塊的名字由它的文件名或文件夾名所決定,并且你可以忽略它的.js后綴:

  • 如果你有一個叫utils.js的文件,那么你可以通過./utils這樣的相對路徑導入它

  • 如果你有一個叫./utils/index.js的文件,則你可以通過./utils/index或./utils來導入它。這使得你可以批量導入一個文件夾內的所有模塊。

導出和導入

可以使用ES2015的新關鍵字import和exports來導入或導出模塊中的東西。模塊可以導入和導出各種類型的變量,如函數,對象,字符串,數字,布爾值,等等。

默認導出

每一個模塊都支持導出 一個 不具名的變量,這稱作默認導出:

// hello-world.js
export default function() {}

// main.js
import helloWorld from './hello-world';
import anotherFunction from './hello-world';

helloWorld();
console.log(helloWorld === anotherFunction);

等價的CommonJS語法:

// hello.js
module.exports = function() {}

// main.js
var helloWorld = require('./hello-world');
var anotherFunction = require('./hello-world');

helloWorld();
console.log(helloWorld === anotherFunction);

任何的JavaScript值都可以被默認導出:

export default 3.14;
export default {foo: 'bar'};
export default 'hello world';

具名導出

除了默認導出外,ES2015的模塊系統還支持導出任意數量個具名的變量:

const PI = 3.14;
const value = 42;
export function helloWorld() {}
export {PI, value};

等價的CommonJS語法:

var PI = 3.14;
var value = 42;
module.exports.helloWorld = function() {}
module.exports.PI = PI;
module.exports.value = value;

你也可以在導出變量時對其重命名:

const value = 42;
export {value as THE_ANSWER};

等價的CommonJS語法:

var value = 42;
module.exports.THE_ANSWER = value;

在導入時,你也可以使用as關鍵字來重命名導入的變量:

import {value as THE_ANSWER} from './module';

等價的CommonJS語法:

var THE_ANSWER = require('./module'').value;

導入所有

最簡單的,在一條命令中導入一個模塊中所有變量的方法,是使用*標記。這樣一來,被導入模塊中所有導出的變量都會變成它的屬性,默認導出的變量則會被置于default屬性中。

// 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語法:

// 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的區別。后者僅僅會導入默認導出的變量,而前者則會在一個對象中導入所有。

導出所有

一個可能的需求是,你需要將另一個模塊中的一些(或所有)值在你的模塊中再次導出,這被稱作二次導出(re-exporting)。值得注意的是,你可以二次導出許多同名的值,這將不會導致異常,而是最后一個被導出的值將會獲得勝利。

// 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語法:

// 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;

注意點

一個關鍵點時,導入模塊的東西,并不是一個引用或一個值,而是一個類似與被導入模塊內部的一個getter對象。所以這可能會導致一些不符合預期的行為。

缺乏異常

在具名地導入其他模塊的變量時,如果你不小心打錯了變量名,這將不會拋出異常,而是導入的值將會變成undefined。

// module.js
export const value = 42;

// main.js
import {valu} from './module'; // no errors
console.log(valu); // undefined

可變的基本類型值

在導入一些基本類型的值(如數字,布爾值或字符串)時,可能會產生一個有趣的副作用。這些值可能會在模塊外被修改。例子:

// 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變量是一個數值類型,它在main模塊中被修改了值。

導入的變量是只讀的

不論你以何種聲明導出變量,它們都是只讀的。但是,如果導出的是對象,你可以改變變量的屬性。

// 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

測試模塊

如果想要測試,或mock被導出的變量,很不幸,這在新的ES2015模塊系統中是辦不到的。因為與CommonJS一樣,導出的變量在外面不能被重新賦值。唯一的解決辦法是,導出一個單獨的對象。

// 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的模塊標準化了模塊的加載和解析方式。CommonJS和AMD之間的爭論終于被解決了。

我們得到了更簡潔的模塊語法,以及靜態的模塊定義,這有助于編譯器的優化,甚至是類型檢查。

原文鏈接: https://strongloop.com/strongblog/an-introduction-to-javascript-es6-modules/

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!