JS模塊化,整理一波

工做中,隨着前端項目的擴張,不管從代碼精簡,仍是團隊合做,上線部署等各個方面來講,模塊化已經不可或缺。javascript

模塊化的最大做用就是提升代碼的複用率,解耦,減小衝突html

前端模塊化的規範,比較著名的CommonJs AMD CMD ES6模塊化。這也是模塊化演變的進程。前端

1.CommonJs

CommonJS規範規定,每一個模塊內部,module變量表明當前模塊。這個變量是一個對象,它的exports屬性(即module.exports)是對外的接口。java

CommonJs定義的模塊能夠分爲三個部分:es6

  • require 用來引入模塊
  • exports 對象用於導出當前模塊的方法或變量,是惟一的導出口
  • module 這個對象就表明模塊自己

特性

  • 同步加載模塊
  • 全部要輸出的對象通通掛載在 module.exports 上,而後暴露給外界
  • 設計模式是單例模式,只會生成一個實例對象
  • 代碼運行在模塊做用域內,不會污染全局做用域,不會產生命名空間衝突

Node應用就是採用CommonJS模塊規範的典型。設計模式

咱們來看一個簡單的例子,定義一個methods對象,而後暴露出去數組

var methods = {};
<!--能夠這樣輸出-->
module.exports.methods = methods;

<!--也能夠這樣輸出,至關於隱藏一行 -->  
<!--var exports = module.exports; -->
exports.methods = methods;

<!--也能夠包裝一層再輸出-->
module.exports = { methods: methods };

複製代碼

引用:瀏覽器

var obj = require('./methods.js');
console.log(obj.methods);
複製代碼

可是,前面咱們寫到CommonJS的一個重要特性是加載模塊是同步的,也就是說,只有加載完成,才能執行後面的操做。
這種規範在用於服務器變成時,沒什麼毛病,由於模塊文件通常都存於本地硬盤中,因此加載起來也比較快。
可是放在瀏覽器環境中呢?
這個時候的資源大都放在服務器上,這個時候再採起同步加載,頗有可能由於網速等緣由形成瀏覽器「假死」的狀況,用戶體驗十分很差。
這就引伸出了AMD規範。bash

2.AMD規範

AMD是"Asynchronous Module Definition"的縮寫,意思就是"異步模塊定義"。服務器

比較著名的AMD規範的典型就是Require.js,咱們能夠經過requireJS一窺一二

Require.js

Require.js定義了兩個顯性API:

  • define(id, [depends], callback) 定義模塊
  • require([module], callback) 加載模塊

基本思想是,經過define方法,將代碼定義爲模塊;經過require方法,實現代碼的模塊加載。

咱們直接看代碼:

define能夠定義獨立模塊和非獨立模塊(也就是含有依賴)

<!--獨立模塊-->
define(function(){
    return {
        m: 0,
        methods: function() {}
    }
})

<!--非獨立模塊-->
<!--m1, m2的參數與前面的依賴數組是相對應的-->
define(['module1', 'module2'], function(m1, m2){
    return {
        m: 0,
        methods: function() {}
    }
})
複製代碼

require引用:

require(['module1', 'module2'], function ( m1, m2 ) {
        m1.doSomething();
        m2.doSomething();
});
複製代碼

require也能夠實現動態加載以及第三方加載,詳細可參考阮一峯老師的《RequireJS和AMD規範》,這裏不詳細舉例了。

咱們瞭解了AMD規範是異步加載模塊,不影響頁面構建進程,可是AMD規範的一個顯著特性是預加載,就是遇到的引入包都要提早執行加載(2.0以後也能夠改爲延遲執行,此處按下不表)好比:

define(function(require, exports, module) {
    console.log('start');

    var a = require('./a');
    a.doSomething();
    var b = require('./b');
    b.doSomething();

    return {
        c: function() {
            console.log('this is c');
        }
    };
});
複製代碼

這個執行順序是什麼呢?

加載a =>
加載b =>
console.log('start'); =>
a.doSomething(); =>
b.doSomething(); =>
console.log('this is c');
複製代碼

其實就至關於:

define(['a', 'b'], function(require, exports, module) {
    console.log('start');
    a.doSomething();
    b.doSomething();
    return {
        c: function() {
            console.log('this is c');
        }
    };
});
複製代碼

這種就是依賴前置,也是AMD規範的一個顯著特徵。
那到這可能會想爲何不能用到的時候再加載呢?
這個時候就能夠引伸出CMD規範。

3.CMD規範

CMD規範,全稱是Common Module Definition
實現CMD規範的典範是Sea.js,雖然已通過時,但你們不妨瞭解一下。

CMD規範是延遲執行,就是說等執行到這了,我再加載,也就是懶加載。而且推崇依賴就近。
就是用到了,你再寫,再執行加載。

define(function(require, exports, module) {
    console.log('start');

    var a = require('./a');
    a.doSomething();
    var b = require('./b');
    b.doSomething();

    return {
        c: function() {
            console.log('this is c');
        }
    };
});
複製代碼

那麼上面那段代碼拿到這裏,執行順序就會改變:

console.log('start'); =>
加載a =>
a.doSomething(); =>
加載b =>
b.doSomething(); =>
console.log('this is c');
複製代碼

這個能夠說是兩種規範最直觀的差異了,可是兩種規範的差異不只僅只是這些。

4. AMDCMD的不一樣之處

  1. 對於依賴的模塊,AMD 是提早執行,CMD 是延遲執行。不過 RequireJS2.0 開始,也改爲能夠延遲執行(根據寫法不一樣,處理方式不一樣)。CMD 推崇 as lazy as possible。
  2. CMD 推崇依賴就近,AMD 推崇依賴前置
  3. AMD 的 API 默認是一個當多個用,CMD 的 API 嚴格區分,推崇職責單一。

5. ES6模塊化

由於模塊化愈發重要,ES6 在語言標準的層面上,實現了模塊功能,並且實現得至關簡單,徹底能夠取代 CommonJSAMD 規範,成爲瀏覽器和服務器通用的模塊解決方案。

ES6 模塊的設計思想是儘可能的靜態化,使得編譯時就能肯定模塊的依賴關係,以及輸入和輸出的變量。
這和CommonJS AMD 規範是有根本區別的,由於CommonJS AMD 二者都是運行時加載

模塊功能主要由兩個命令構成:exportimport

5.1 export

export命令用於規定模塊的對外接口。
一個模塊就是一個獨立的文件。該文件內部的全部變量,外部沒法獲取。若是你但願外部可以讀取模塊內部的某個變量,就必須使用export關鍵字輸出該變量。

exports的使用:

<!--暴露變量-->
const a = 1;
const b = 2;
export {a, b};

<!--輸出函數-->
function a() {...}
export { a }
<!--也能夠設置別名-->
export { a as b }

複製代碼

5.2 import

import命令用於輸入其餘模塊提供的功能。

特性:

  • import命令輸入的變量都是隻讀,不可修改,由於它的本質是輸入接口。可是若是是對象,能夠修改對象的屬性。
  • import會提高到頭部,在編譯時執行。
  • 因爲import是靜態執行,因此不能使用表達式和變量,這些只有在運行時才能獲得結果的語法結構。
  • import語句是 Singleton 模式,只有一個實例,因此屢次調用同一個包的不一樣方法,只會生成一個實例對象。

import命令的使用:

import { a, b } from './main'

<!--取別名-->
import { a as b } from './main'

<!--也能夠直接加載模塊-->
import 'lodash';

<!--模塊總體加載-->
<!--此時的掛在對象a,應該是能夠靜態分析的,不容許運行時改變其任何屬性-->
import * as a from 'lodash'
複製代碼

5.3 export default

從前面的例子能夠看出,使用import命令的時候,用戶須要知道所要加載的變量名或函數名,不然沒法加載。可是,用戶確定但願快速上手,未必願意閱讀文檔,去了解模塊有哪些屬性和方法。

爲了給用戶提供方便,讓他們不用閱讀文檔就能加載模塊,就要用到export default命令,爲模塊指定默認輸出。

特性

  • 一個模塊只能有一個export default默認輸出,輸出變量名或方法爲default
  • export default加載時,不管是匿名函數仍是有名函數,統一默認爲匿名函數,須要在引用的時候重命名。
  • 使用export default的輸出,在import的時候,不須要使用{}
  • export default能夠輸出變量,函數,還有類

代碼展現一下:

<!--export-default.js-->
export default function () {
  console.log('foo');
}
<!--也能夠寫成有名函數-->
export default function foo() {
  console.log('foo');
}


<!--import-default.js-->
import customName from './export-default';
customName(); // 'foo'
複製代碼

也能夠自定義default

<!--modules.js-->
function add(x, y) {
  return x * y;
}
export {add as default};
<!--等同於-->
export default add;


<!--app.js-->
import { default as foo } from 'modules';
// 等同於
import foo from 'modules 複製代碼

輸出類:

<!--MyClass.js-->
export default class { ... }
<!--main.js-->
import MyClass from 'MyClass';
let o = new MyClass();
複製代碼

5.4 exportimport 的複合寫法

若是在一個模塊之中,先輸入後輸出同一個模塊,import語句能夠與export語句寫在一塊兒。

export { a, b } from 'xxx';

<!--等同於-->
import { a, b } from 'xxx';
export { a, b }

<!--默認接口改成具名接口-->
export { default as es6 } from './someModule';
複製代碼

這裏至關於一箇中轉,實際上並無導入到當前模塊,因此當前模塊不能直接使用轉發的接口。

5.5 import()

import()函數能夠說是import的補全,由於import是靜態加載,會先於模塊內其餘語句先執行,這樣的設計當然有利於編譯器提升效率,但也致使沒法在運行時加載模塊。
所以出現了提案,引入import()函數,實現動態加載。例如條件加載:

if (condition) {
  import('moduleA').then(...);
} else {
  import('moduleB').then(...);
}
複製代碼

目前import()函數提案已到stage4階段,你們其實能夠放心用了,兼容性很樂觀。

import()返回一個Promise對象,下面是一個例子:

const main = document.querySelector('main');

import(`./section-modules/${someVariable}.js`)
  .then(module => {
    module.loadPageInto(main);
  })
  .catch(err => {
    main.textContent = err.message;
  });
複製代碼

特性:

  • import()函數能夠用在任何地方,不只僅是模塊,非模塊的腳本也可使用。它是運行時執行,也就是說,何時運行到這一句,就會加載指定的模塊。
  • import()函數與所加載的模塊沒有靜態鏈接關係,這點也是與import語句不相同。
  • import()相似於 Noderequire方法,區別主要是前者是異步加載,後者是同步加載。

6.requireimport的區別總結

  • require是同步導入,import是異步導入
  • require是值拷貝,導出值變化不會影響導入值;import指向 內存地址,導入值會隨導出值而變化
  • require是運行時加載,import是編譯時加載(靜態加載)
  • require能夠實現第三方加載,import只能導入本地存在文件

I am moving forward.

相關文章
相關標籤/搜索