【JS基礎】一文看懂前端模塊化規範

前言

前端的模塊化之路經歷了漫長的過程,這裏根據大佬們寫的文章,將模塊化規範部分作了彙總和整理,想詳細瞭解的小夥伴能夠看浪裏行舟大神寫的前端模塊化詳解(完整版),但願讀完的小夥伴能有些收穫,也但願以爲有用的小夥伴能夠點個贊,筆芯。javascript

什麼是模塊

  • 將一個複雜的程序依據必定的規則(規範)封裝成幾個塊(文件), 並進行組合在一塊兒
  • 塊的內部數據與實現是私有的,只是向外部暴露一些接口(方法)與外部其它模塊通訊

CommonJS

Node 應用由模塊組成,採用 CommonJS 模塊規範。每一個文件就是一個模塊,有本身的做用域。在一個文件裏面定義的變量、函數、類,都是私有的,對其餘文件不可見。在服務器端,模塊的加載是運行時同步加載的;在瀏覽器端,模塊須要提早編譯打包處理。前端

CommonJS規範加載模塊是同步的,也就是說,只有加載完成,才能執行後面的操做。java

基本語法:git

  • 暴露模塊:module.exports = valueexports.xxx = value
  • 引入模塊:require(xxx),若是是第三方模塊,xxx爲模塊名;若是是自定義模塊,xxx爲模塊文件路徑

可是,CommonJs有一個重大的侷限使得它不適用於瀏覽器環境,那就是require操做是同步的。這對服務器端不是一個問題,由於全部的模塊都存放在本地硬盤,能夠同步加載完成,等待時間就是硬盤的讀取時間。可是,對於瀏覽器,這倒是一個大問題,由於模塊都放在服務器端,等待時間取決於網速的快慢,可能要等很長時間,瀏覽器處於」假死」狀態。es6

所以,瀏覽器端的模塊,不能採用」同步加載」(synchronous),只能採用」異步加載」(asynchronous),這就是AMD規範誕生的背景。github

AMD

特色:非同步加載模塊,容許指定回調函數,瀏覽器端通常採用AMD規範編程

表明做:require.js瀏覽器

用法:緩存

//定義沒有依賴的模塊
    define(function(){
       return 模塊
    })

    //定義有依賴的模塊
    define(['module1', 'module2'], function(m1, m2){
       return 模塊
    })
    
    //引入使用模塊
    require(['module1', 'module2'], function(m1, m2){
       //使用m1/m2
    })
複製代碼

CMD

特色:專門用於瀏覽器端,模塊的加載是異步的,模塊使用時纔會加載執行服務器

表明做:Sea.js

用法:

//定義沒有依賴的模塊
    define(function(require, exports, module){
      exports.xxx = value
      module.exports = value
    })
    
    //定義有依賴的模塊
    define(function(require, exports, module){
      //引入依賴模塊(同步)
      var module2 = require('./module2')
        //引入依賴模塊(異步)
        require.async('./module3', function (m3) {
        })
      //暴露模塊
      exports.xxx = value
    })
    
    //引入使用模塊
    define(function (require) {
      var m1 = require('./module1')
      var m4 = require('./module4')
      m1.show()
      m4.show()
    })
複製代碼

CMD與AMD區別

AMD和CMD最大的區別是對依賴模塊的執行時機處理不一樣,而不是加載的時機或者方式不一樣,兩者皆爲異步加載模塊。

AMD依賴前置,js能夠方便知道依賴模塊是誰,當即加載;

而CMD就近依賴,須要使用把模塊變爲字符串解析一遍才知道依賴了那些模塊,這也是不少人詬病CMD的一點,犧牲性能來帶來開發的便利性,實際上解析模塊用的時間短到能夠忽略。

一句話總結: 二者都是異步加載,只是執行時機不同。AMD是依賴前置,提早執行,CMD是依賴就近,延遲執行。

UMD

UMD是AMD和CommonJS的糅合:

AMD模塊以瀏覽器第一的原則發展,異步加載模塊。

CommonJS模塊以服務器第一原則發展,選擇同步加載,它的模塊無需包裝(unwrapped modules)。

這迫令人們又想出另外一個更通用的模式UMD (Universal Module Definition),但願解決跨平臺的解決方案。

UMD先判斷是否支持Node.js的模塊(exports)是否存在,存在則使用Node.js模塊模式。

在判斷是否支持AMD(define是否存在),存在則使用AMD方式加載模塊。

(function (window, factory) {
        if (typeof exports === 'object') {
         
            module.exports = factory();
        } else if (typeof define === 'function' && define.amd) {
         
            define(factory);
        } else {
         
            window.eventUtil = factory();
        }
    })(this, function () {
        //module ...
    });
複製代碼

ES6模塊化

ES6 模塊的設計思想是儘可能的靜態化,使得編譯時就能肯定模塊的依賴關係,以及輸入和輸出的變量。CommonJS 和 AMD 模塊,都只能在運行時肯定這些東西。好比,CommonJS 模塊就是對象,輸入時必須查找對象屬性。

ES6 Module默認目前尚未被瀏覽器支持,須要使用babel,在平常寫demo的時候常常會顯示這個錯誤:

圖片描述

ES6模塊使用import關鍵字導入模塊,export關鍵字導出模塊:

/** 導出模塊的方式 **/
    
    var a = 0;
    export { a }; //第一種
       
    export const b = 1; //第二種 
      
    let c = 2;
    export default { c }//第三種 
    
    let d = 2;
    export default { d as e }//第四種,別名
    
    /** 導入模塊的方式 **/
    
    import { a } from './a.js' //針對export導出方式,.js後綴可省略
    
    import main from './c' //針對export default導出方式,使用時用 main.c
    
    import 'lodash' //僅僅執行lodash模塊,可是不輸入任何值
複製代碼

命名式導出與默認導出

export {<變量>}這種方式通常稱爲 命名式導出 或者 具名導出,導出的是一個變量的引用

export default這種方式稱爲 默認導出 或者 匿名導出,導出的是一個

舉例:

// a.js
    let x = 10
    let y = 20
    setTimeout(()=>{
        x = 100
        y = 200
    },100)
    export { x }
    export default y

    // b.js
    import { x } from './a.js'
    import y from './a.js'
    setTimeout(()=>{
        console.log(x,y) // 100,20
    },100)
複製代碼

ES6 模塊與 CommonJS 模塊的差別

① CommonJS 模塊輸出的是一個值的拷貝,ES6 模塊輸出的是值的引用。

CommonJS 模塊輸出的是值的拷貝,也就是說,一旦輸出一個值,模塊內部的變化就影響不到這個值。並且,CommonJS 模塊不管加載多少次,都只會在第一次加載時運行一次,之後再加載,返回的都是第一次運行結果的緩存,除非手動清除系統緩存。

ES6 模塊的運行機制與 CommonJS 不同,JS 引擎對腳本靜態分析的時候,遇到模塊加載命令import,就會生成一個只讀引用,等到腳本真正執行時,再根據這個只讀引用,到被加載的那個模塊裏面去取值。換句話說,ES6 的import有點像 Unix 系統的「符號鏈接」,原始值變了,import加載的值也會跟着變。所以,ES6 模塊是動態引用,而且不會緩存值,模塊裏面的變量綁定其所在的模塊。

② CommonJS 模塊是運行時加載,ES6 模塊是編譯時輸出接口。

CommonJS 加載的是一個對象(即module.exports屬性),該對象只有在腳本運行完纔會生成。即在輸入時是先加載整個模塊,生成一個對象,而後再從這個對象上面讀取方法,這種加載稱爲「運行時加載」。

例如:

// CommonJS模塊
    let { stat, exists, readFile } = require('fs');
    
    // 等同於
    let _fs = require('fs');
    let stat = _fs.stat;
    let exists = _fs.exists;
    let readfile = _fs.readfile;
複製代碼

上面代碼的實質是總體加載fs模塊(即加載fs的全部方法),生成一個對象(_fs),而後再從這個對象上面讀取 3 個方法。由於只有運行時才能獲得這個對象,致使徹底沒辦法在編譯時作「靜態優化」。

ES6 模塊不是對象,它的對外接口只是一種靜態定義,在代碼靜態解析階段就會生成。經過export命令顯式指定輸出的代碼,import時採用靜態命令的形式。即在import時能夠指定加載某個輸出值,而不是加載整個模塊,這種加載稱爲「編譯時加載」或者「靜態加載」。

// ES6模塊
    import { stat, exists, readFile } from 'fs';
複製代碼

上面代碼的實質是從fs模塊加載 3 個方法,其餘方法不加載。即 ES6 能夠在編譯時就完成模塊加載,效率要比 CommonJS 模塊的加載方式高。固然,這也致使了無法引用 ES6 模塊自己,由於它不是對象

因爲 ES6 模塊是編譯時加載,使得靜態分析成爲可能。有了它,就能進一步拓寬 JavaScript 的語法,好比引入宏(macro)和類型檢驗(type system)這些只能靠靜態分析實現的功能。

除了靜態加載帶來的各類好處,ES6 模塊還有如下好處:

  • 再也不須要UMD模塊格式了,未來服務器和瀏覽器都會支持 ES6 模塊格式。目前,經過各類工具庫,其實已經作到了這一點。
  • 未來瀏覽器的新API 就能用模塊格式提供,再也不必須作成全局變量或者navigator對象的屬性。
  • 再也不須要對象做爲命名空間(好比Math對象),將來這些功能能夠經過模塊提供。

總結

  1. CommonJS規範主要用於服務端編程,加載模塊是同步的,這並不適合在瀏覽器環境,由於同步意味着阻塞加載,瀏覽器資源是異步加載的,所以有了AMD、CMD解決方案。
  2. AMD規範在瀏覽器環境中異步加載模塊,並且能夠並行加載多個模塊。不過,AMD規範開發成本高,代碼的閱讀和書寫比較困難,模塊定義方式的語義不暢。
  3. CMD規範與AMD規範很類似,都用於瀏覽器編程,依賴就近,延遲執行,能夠很容易在Node.js中運行。不過,依賴SPM打包,模塊的加載邏輯偏重。
  4. ES6 在語言標準的層面上,實現了模塊功能,並且實現得至關簡單,徹底能夠取代 CommonJS 和 AMD 規範,成爲瀏覽器和服務器通用的模塊解決方案。

以上是本篇文章的內容,歡迎你們提出本身的想法,咱們一塊兒學習進步,與君共勉。

參考資料

相關文章
相關標籤/搜索