ECMAScript 6 的模塊相比 CommonJS 的require (...)有什麼優點?

感覺語法比 CommonJS 繁瑣多了

http://es6.ruanyifeng.com/#docs/class


只要不是最壞的標準,那就一定比沒有標準強


ECMAScript 6 很多新功能都是毀譽參半,其中添加的一個新功能「模塊 Module」也一樣,有人認為 Module 可以幫助我們更好地寫代碼,也有人認為這增加了 Javascript 的複雜度,大多數人其實並不需要它。

到底是好是壞,這裡不去辯別。但目前 Module 的語法已經定下來了,我們可以來探索下它。

ECMAScript 6 的模塊有什麼優點?

三個字:爽,快,強。

爽?

1. 語言層級支持,無需引入第三方庫

2. 統一的 API,不用再寫 [shim](umdjs/umd · GitHub)

3. 清爽的語法(與 Python 相似),功能卻很豐富:

import:

import * as _ from "src/lodash"; // 引入外部文件所有對象
import { each, map } from "src/lodash"; // 引入外部文件部分對象
import _ from "src/lodash"; // 引入外部文件默認導出對象
import _, { each, map } from "src/lodash"; // 同時引入默認導出對象和部分對象
import "src/lodash"; // 只載入外部文件,但啥都不引入

export:

export let _ = function () {}; // 導出 _ 對象
export function lodash () {}; // 導出 lodash 函數
export default function (x) {return x}; // 導出匿名函數並設為默認導出對象
export { _, lodash as default }; // 一次導出多個對象

4. 由於可以選擇性引入並重命名外部模塊的部分對象,因此全局變數不被污染,局部變數也不怕被污染。

快?

1. 靜態代碼分析

基於目前第三方依賴管理工具的實現,我們要通過運行時檢查當前模塊引入了哪些外部模塊,效率較低。但現在有了新語法的支持,我們就可以在運行前判斷得出模塊之間的依賴關係,進行代碼檢查。

2. 上一條已經很勁爆了有木有 :)

強?

1. 更好地支持循環依賴

循環依賴示例:

/* --- a.js --- */
import * as b from "js/b";

// 在模塊中間調用其它模塊的方法
b.bar("b");

export function foo (from) {
console.log("caller: " + from);
}

/* --- b.js --- */
import * as a from "js/a";

// 在模塊中間調用其它模塊的方法
a.foo("a");

export function bar (from) {
a.foo(from);
}

/* --- main.js --- */
import {bar} from "js/b";
bar("main");

結果:

caller: b a.js:7
caller: a a.js:7
caller: main a.js:7

2. 提供模塊載入器介面(Module Loader API)

有了這個介面後,模塊的載入就有了更多的可能性,比如動態載入,載入後回調等。但我對於這個介面熟知不多,詳見介紹: The ECMAScript 6 module loader API。

延伸閱讀:

  1. harmony:modules [ES Wiki]
  2. ECMAScript 6 modules: the final syntax


少了外部工具依賴,原生api效率較高,看了一些相關wiki,使用靈活度也比現在amd,cmd都方便,向下兼容度不高,不支持的如果要寫shim,不太方便,有人已經寫好了,但是原理都是ajax讀文本再做解析,效率通用性跨域能力都弱,所以,真正大規模推廣使用,還很久遠。

當然,通過上線前的預編譯和debug時的動態解析工具來做開發,還是挺爽的。

參見類似項目:

ES6 Module Transpiler


TreeShaking

雖然這是 Webpack 2 中的新特性,但是它與 ES6 模塊化有著千絲萬縷的關係。

下面這段代碼:

module.js

export const sayHello = name =&> `Hello ${name}!`;
export const sayBye = name =&> `Bye ${name}!`;

index.js

import { sayHello } from "./module";

sayHello("World");

假設這個工程一共只有兩個文件,那麼sayBye() 方法雖然沒有被使用過,但是仍然會被 export。然而如果我們在 Babel 中啟用 ES6 模塊化,同時升級到 Webpack 2.0/3.0 那麼就可以輕鬆實現 TreeShaking(利用的是 Webpack 2+ 的 Native Import 特性),最終的效果是只有 sayHello() 會被作為 module 的一部分導出。

只需要用下面的代碼就可以啟用 Babel 的 ES6 模塊化。

.babelrc

{
presets: [
[ "es2015", { modules: false } ],
...
],
plugins: [...]
}

請注意,modules 屬性的值只能是 false,不支持 true,在過去我們沒有設置 module 屬性時,Babel 會將 import 轉譯成 CommonJS 的 require(),再有 Webpack 去處理 require() 的邏輯。而將 module 變為 false 後,Babel 會直接輸出 import 語句,轉由 Webpack 去實現所謂 native import 的邏輯,同理 export 語句也是由 Webpack 去實現轉譯的。


1. 使用保留關鍵字 import 和 export ,從而在瀏覽器實現後能最大程度的向下兼容。

理論上現有網站的代碼可能使用了 require、exports、module 等變數,而且並非用於標準的 CommonJS 實現。

2. 使用聲明式語法,而非特殊的函數調用。

CommonJS 語法可能的兩個小瑕疵:

var require = function() {}; // 覆蓋
var module = require("module"); // module is undefined

var delete = require; // 引用 require
var module = delete("module"); // 代碼含義容易混淆

PS:我也更喜歡 CommonJS 的語法。


ECMAScript 6的模塊處理方式有兩種,一種是通過import、export這種關鍵字實現的靜態方式;另一種是通過全局變數System實現的可動態編程的方式(相關標準還在制定中,目前的API還不穩定)。

對於靜態方式,使代碼的靜態分析更加輕鬆,方便做代碼檢測等。而動態方式主要用於瀏覽器端,方便瀏覽器非同步載入。

靜態方式的語法與其它語言很類似(比如python),從其它語言轉過來的開發者接受起來比較輕鬆。

我個人的話不贊同說ES6毀譽參半,或者說ES6毀大於譽。

很多傳統的前端人員可能剛開始一看到ES6的語法就傻眼了:這還是我認識的js嗎?可能還會想,變化這麼大,又要花時間學習了,又要重新積累一些東西了,這不是瞎折騰嗎?

我剛開始也有這些想法,但是後面逐漸深入接觸了ES6之後發現,確實值得這麼做,因為這讓js逐漸成熟起來,逐漸能承擔一些大型多人項目的開發。這些好處,真的是一時半會兒列舉不完。


規範規範規範,規則,有規矩才成方圓

CMD是國內玉伯大神在開發SeaJS的時候提出來的,屬於CommonJS的一種規範,此外還有AMD,其對於的框架是RequireJS

1、二者都是非同步模塊定義(Asynchronuous Module Definition)的一個實現;

2、CMD和AMD都是CommonJS的一種規範的實現定義,RequireJS和SeaJS是對應的實踐;

3、CMD和AMD的區別:CMD相當於按需載入,定義一個模塊的時候不需要立即制定依賴模塊,在需要的時候require就可以了,比較方便;而AMD則相反,定義模塊的時候需要制定依賴模塊,並以形參的方式引入factory中。

區別看下邊例子:

//AMD方式定義模塊
define(["dep1","dep2"],function(dep1,dep2){
//內部只能使用制定的模塊
return function(){};
});
//CMD
define(function(require,exports,module){
//此處如果需要某XX模塊,可以引入
var xx=require("XX");
});

4、JavaScript語言是弱結構性的,通過CommonJS定義一些規範,CMD和AMD得定義模塊的方式對代碼進行管理,使得更易維護;此外,NodeJS的誕生,對模塊的規範定義,和包(npm)的概念的引入,讓JS管理不再困難,一個字,爽爆了!


import, export, default, as, from 增加了5個關鍵詞,使用層面也比原來的複雜很多。

看個代碼,大家如果不去執行,僅憑理解看看是什麼結果。

下面這兩段有啥區別

var log = console.log.bind(console);
export {log};

var log = console.log.bind(console);
export {log: log};

下面這兩段的區別

import config from "./config";

const config from "./config";

還有很多複雜的用法,但是看著這麼複雜的用法,其實目前使用babel來翻譯都是翻譯成 require 來執行的,也就是說功能層面並沒有超越 require,不僅沒有超越,還沒有require 的功能強大。設想這樣的一個場景,你之前使用es5開發了很多 npm 庫包,目前 es6 很流行,你打算把其中一些用 es6 來重寫一下。那麼你可能不得不在裡面繼續使用 module.exports 的寫法來導出模塊,因為 es6 裡面導出的東西必須是一個對象,比如原來你的npm庫包,require("test") 得到一個函數,那麼你用了 es6 的export,用戶必須得使用 require("test").default 或者 require("test").fn 這樣的方式才能得到。相當於發布以後就不兼容了。


不再引用seajs和requirejs就能使用模塊就是最大的優點


# 語法級實現,便於做靜態分析。

類似 Browserify 這類工具想把 CommonJS 模塊打包放瀏覽器,因為 CommonJS 只是一個 function 調用,只能在運行時確定結果,想要確定哪些模塊是需要的、哪些不需要是辦不到的。

(目前他們的實現要麼全部打包,要麼非常保守地只要寫了 `require()` 就當作需要該模塊)

eg.

```

if ( Date.now() &> 150000000 ) require("foo")

```

# 標準化

CommonJS 倒不說,各種 AMD、CMD 都可以再見了。


推薦閱讀:

a=new b()和a=b(),其本質的區別在哪?
使用markdown製作的html幻燈片有哪些?
node.js 已經淡出眾多開發者的視野了嗎?

TAG:JavaScript | Nodejs | CommonJS | ECMAScript2015 |