ECMAScript 2015(又稱ES6)提供了一個前端JavaScript
缺失已久的特性 —— 模塊。ES2015中的模塊參考了CommonJS規範(目前Node.js
的模塊規範)以及AMD規範,而且儘量的取其精華,去其糟粕:javascript
它提供了簡潔的語法前端
以及異步的,可配置的模塊加載java
這篇文章將會專一於ES2015的模塊語法以及注意點。關於模塊的加載和打包,將會在另外一篇文章中細述。es6
目前最廣泛的JavaScript
運行平臺即是瀏覽器,在瀏覽器中,全部的代碼都運行在同一個全局上下文中。這使得你即便更改應用中的很小一部分,你也要擔憂可能會產生的命名衝突。瀏覽器
傳統的JavaScript
應用被分離在多個文件中,而且在構建的時候鏈接在一塊兒,這稍顯笨重。因此人們開始將每一個文件內的代碼都包在一個自執行函數中:(function() { ... })();
。這種方法建立了一個本地做用域,因而最初的模塊化的概念產生了。以後的CommonJS和AMD系統中所稱的模塊,也是由此實現的。異步
換句話說,現存的「模塊」系統是使用已有的語言特性所實現的。而ES2015則經過添加適當的新的語言特性,來使之官方化了。模塊化
一個JavaScript
模塊就是一個對其餘模塊暴露一些內部屬性/方法的文件。咱們在這裏僅會討論瀏覽器中的ES2015模塊系統,並不會涉及Node.js
是如何組織它自身的模塊的。一些在建立ES2015模塊時須要注意的點:函數
和傳統的JavaScript
不一樣,在使用模塊時,你沒必要擔憂污染全局做用域。偏偏相反,你須要把因此你須要用到的東西從其餘模塊中導入進來。可是,這樣也會使模塊之間的依賴關係更爲清晰。oop
模塊的名字由它的文件名或文件夾名所決定,而且你能夠忽略它的.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/