什麼是一個模塊,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。他們實質上都是經過一個配置爲每一個模塊分配了一個名字,對應到這個模塊的導出對象。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是在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
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 開始 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>
複製代碼
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()返回的事一個promise,因此可使用await來等待。
const name = await import(`./util/${name}`);
複製代碼
因爲本人水平所限,不免有所疏漏。若是有錯誤之處,請評論,我會及時回覆並修改。