JS中模塊(Module)的前生今世

前言

什麼是一個模塊,js中的模塊又是什麼?爲何有AMD,CMD,UMD以及如今的CommonJS和ES6 他們有啥區別呢?究竟都是要解決什麼問題?javascript

遠古時代

在ES6出來以前,JS中是沒有嚴格意義的模塊的。在ES6以前代碼做用域只有全局做用域 和函數做用域。全部代碼最終是在一個上下文,共享整個全局變量。JS的引用一般是使用script標籤,而後共享全局變量。這就會產生很是多共享全局變量的問題。因爲JS的函數有函數做用域,因此就有了這個IIFE設計模式。html

let people =  (function(){
        
        var  name = 'ada';
        
        var getName = function (){
            return name
        }
        var setName = function(n){
            name = name;
        }
        return {
            getName:getName,
            setName:setName
        }
    })()
複製代碼

這就是最一個典型的模塊。這個模塊返回一個對象,這個對象裏有一些函數或者變量。能夠提供外界調用。內部經過閉包封裝了一些私有變量,如圖所示name 就沒法被外界訪問。這就是最初級的一個模塊的實現。java

AMD,CMD時代

上面這個模式解決了模塊設計的基本問題,就是封裝性。可是還欠缺一些通用的對模塊的管理。怎麼引用一個模塊?怎麼管理全局的某塊。因而業界就進化出了AMD和CMD。他們實質上都是經過一個配置爲每一個模塊分配了一個名字,對應到這個模塊的導出對象。jquery

好比咱們看一下 CMD的seajswebpack

seajs.config({
  base: "../sea-modules/",
  alias: {
    "jquery": "jquery/jquery/1.10.1/jquery.js"
  }
})
//使用一個模塊
seajs.use(['jquery'],function($,hm){  
   //引用一個庫
});  

//定義一個模塊
define(function(require, exports, module) {
  // 經過 require 引入依賴
  var $ = require('jquery');

  // 或者經過 module.exports 提供整個接口
  module.exports = ...

});
複製代碼

如代碼所示首先經過config方法爲每一個模塊定義一個名字和文件的映射。而後在使用中就能夠用名字來獲取代碼的導出對象。定義模塊時候實際上就是封裝了一個函數,而後內部有一個導出對象。 requireJs中的也相似。es6

CommonJS

CommonJS是在Nodejs中使用的,可是他也不是JS原生的模塊。commonjs裏每個文件都是一個模塊。由於在服務端咱們有一個文件路徑做爲文件的名字。因而commonjs中變成了這樣。 定義一個模塊 a.jsweb

var x = 5;
let setX = (a){
    x = a;
}

module.exports={
    x,
    setX
};
複製代碼

b.js中引用a.js設計模式

let b = require("a.js")
b.setX(100);
console.log(b.x);
複製代碼

commonjs 裏有兩個問題。1個就是瀏覽器沒法理解。2 是由於他是同步加載代碼的,不太適合瀏覽器(此處有疑問,爲什麼瀏覽器不支持同步,若是每次require就添加一個script標籤來執行,這是一個異步的過程)。api

因而webpack 就有了一個解決方案叫作bundle.jspromise

webpack

bundle.js就是webpack將全部js文件打包到一個文件裏。webpack同時實現了require。和模塊。到底是什麼讓咱們看下代碼。原始的代碼就是entry.js.只有一句話,而後咱們看看webpack打包以後是什麼樣的代碼。

entry.js

console.log("a");
複製代碼

下面看看打包以後的代碼

/******/ 	function __webpack_require__(moduleId) {
/******/
/******/ 		// Check if module is in cache
/******/ 		if(installedModules[moduleId]) {
/******/ 			return installedModules[moduleId].exports;
/******/ 		}
/******/ 		// Create a new module (and put it into the cache)
/******/ 		var module = installedModules[moduleId] = {
/******/ 			i: moduleId,
/******/ 			l: false,
/******/ 			exports: {},
/******/ 			hot: hotCreateModule(moduleId),
/******/ 			parents: (hotCurrentParentsTemp = hotCurrentParents, hotCurrentParents = [], hotCurrentParentsTemp),
/******/ 			children: []
/******/ 		};
/******/
/******/ 		// Execute the module function
/******/ 		modules[moduleId].call(module.exports, module, module.exports, hotCreateRequire(moduleId));
/******/
/******/ 		// Flag the module as loaded
/******/ 		module.l = true;
/******/
/******/ 		// Return the exports of the module
/******/ 		return module.exports;
/******/ 	}
...
...//這裏是模塊的代碼,被一個函數包裝起來。
                    /***/ (function(module, exports) {
                    
                    console.log("a");
                    
                    /***/ })
複製代碼

能夠看到webpack 的實現也是將模塊的代碼用IIFE來封裝,require的實現其實 也就是根據moduleId來找到對應函數,執行並返回模塊的導出對象。這也沒有什麼稀奇本質上和咱們最開始的IIFE沒有區別。

ES6 Module

從ES6 開始 ES真正有了Module,他和以往標準的有本質不一樣,他纔是真正的模塊。在瀏覽器中咱們能夠經過添加屬性type='module'引用一個模塊。

<html>
    <body>
     <script type=module src='dom.js'></script>
    </body>
</html>
複製代碼

dom.js

import getUsers from "./users.js"
console.log(getUsers());
複製代碼

users.js

// users.js

var users = ["Tyler", "Sarah", "Dan"];
export default function getUsers() {
  return users;
}
複製代碼

咱們頁面中引入dom.js,dom.js中import user.js。若是咱們用瀏覽器打開最初的html,隨着瀏覽器執行代碼,會自動請求users.js。而且每一個模塊是獨立隔離的,並不須要IIFE模式。

介紹一個優化辦法

對於現代的瀏覽器不少已經支持了ES6了,若是你還用babel轉化爲es5代碼,那麼會添加不少webpack相關的代碼,並且執行效率相對較低。能夠經過type='module'來直接執行代碼。這就須要你在webpack的時候打兩份代碼。一份沒有轉化,一份轉化了。對於非現代瀏覽器則繼續用老的降級方案。新瀏覽器直接執行es6。

<script type="module" src="runs-if-module-supported.js"></script>
<script nomodule src="runs-if-module-not-supported.js"></script>
複製代碼

TreeShaking

commonjs的 require 和 import 另一個區別就是import只能在模塊頭部引用,不能在條件分支中引用。而require中並無這個限制。因此要求咱們在配置webpack的treeshaking的時候,須要使用es6的模塊,不能使用commonjs。 .babelrc文件

{
    "presets": [
        [
            "env",
            {
                "modules": false,//特別注意只有這個爲false ,webpack才能treeshaking
                "targets": {
                    "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
                }
            }
        ],
        "stage-1"
    ],
    "plugins": ["transform-runtime"
複製代碼

錯誤示例,條件語句中不能使用import

if (!user) {
  import * as api from './api' // 只能在頁面頭部使用
}
複製代碼

require中可使用變量,而import 中就不行

let b = "er";
let fileName = "./us" + b + ".js";
let user = require(fileName)
複製代碼

這個例子在require中正確。而在import中就錯誤

let b = "er";
    let fileName = "./us" + b + ".js";
    import a from fileName;//報錯!error!
複製代碼

import()

動態和異步對於動態引入只能用import().import()可使用變量 也能夠在分支中使用。import()返回的事一個promise,因此可使用await來等待。

const name = await import(`./util/${name}`);
複製代碼

說明

因爲本人水平所限,不免有所疏漏。若是有錯誤之處,請評論,我會及時回覆並修改。

相關文章
相關標籤/搜索