js模塊發展歷程-javaScript模塊七日談
前端模塊化開發那點歷史 #588現代ES模塊也須要各類轉碼工具才能夠在瀏覽器里正常運行,下面是轉碼現代ES模塊須要瞭解到的知識點javascript
commonjs & ES module & babel轉碼 & webpack轉碼html
CommonJS 模塊輸出的是一個值的 拷貝,也就是說,一旦輸出一個值,模塊內部的變化就影響不到這個值了 若是輸出的是對象,改變其屬性的話,外部引用的地方是會發生變化的 若是直接改變輸出的引用,那外界引用的地方是不會變化的(取緩存裏面的結果) CommonJS 加載的是一個對象(即 module.exports 屬性),該對象只有在腳本運行完纔會生成 commonjs 一個模塊就是一個文件,require 命令第一次執行加載該腳本 就會執行整個腳本,而後在內存中生成一個對象 { id: '...', // 模塊名 exports: {}, // 真實的模塊 loaded: true // 是否加載完畢 } 之後再次 require 該模塊時,就會去緩存裏取該對象的 exports 的屬性 不管 require 多少次,模塊都只會運行一次,後續加載都是從緩存裏面取
module.exports
與 exports
的關係commonjs 規範僅僅定義了 exports module.exports 是 nodejs 對 commonjs 規範的實現 咱們把這種實現稱爲 commonjs2 https://github.com/webpack/webpack/issues/1114#issuecomment-105509929 exports 只是在初始化對 module.exports 的引用 初始化指向同一片內存空間 模塊導出的是 module.exports 若是對 module.exports 從新賦值,exports 上,掛的方法/屬性將會失效 require 引入的是 module.exports 導出的東西 爲避免混亂/錯誤,通常導出模塊只建議用 module.exports 通常第三方包都用這種方式導出 modules.exports = exports = {}
某個模塊出現循環加載,就只輸出已經執行的部分,還未執行的部分不會輸出
)// 代碼以下 // a.js exports.A = '我是a模塊'; var b = require('./b.js'); console.log('在 a.js 之中, 輸出的 b模塊==> ', b.B); exports.A = '我是後期修改過的a模塊'; console.log('a.js 執行完畢'); // b.js exports.B = '我是b模塊'; var a = require('./a.js'); console.log('在 b.js 之中,輸出a模塊 ==>', a.A); exports.B = '我是修改後的b模塊'; console.log('b.js 執行完畢'); // main.js var a = require('./a.js'); var b = require('./b.js'); console.log('在 main.js 之中,輸出的 a模塊=%j, b模塊=%j', a.A, b.B); // 輸出結果以下: ➜ webpack-plugin git:(master) ✗ node src/babel/index 在 b.js 之中,輸出a模塊 ==> 我是a模塊 b.js 執行完畢 在 a.js 之中, 輸出的 b模塊==> 我是修改後的b模塊 a.js 執行完畢 在 main.js 之中,輸出的 a模塊="我是後期修改過的a模塊", b模塊="我是修改後的b模塊" // 執行過程以下: 執行 a.js 遇到 require b.js,暫停 a.js 執行,去執行 b.js b.js 執行到第二行,遇到 require a.js ,從緩存中拿出剛剛 a.js 導出的模塊,在 b.js 裏面使用 繼續執行 b.js 後面的代碼 待 b.js 執行完畢後,控制權交還 a.js,繼續執行 拿到 b.js 導出的模塊,在 a.js 繼續使用 ... 直到結束
循環引用注意點
:因爲 commonjs
模塊遇到循環加載時,返回的是當前已經執行的部分的值,而不是所有代碼以後的值,二者可能會有差別,因此輸入變量的時候必須很是當心,使用 var a = require('a')
而不是 var a = require('a').foo
export default A // 用戶不須要知道導出模塊的變量名 import a from 'a.js' // 能夠導出多個 export var a = 1 // 這種方式能夠直接導出一個表達式 或 var a = 1 export {a} // 必須用花括號包起來 import {a} from 'a.js' // as 關鍵字重命名模塊 export { a as A } // 導入導出合併 export { default as Comps } from '../xxx' 至關於 import Comps from './xx' export { Comps } // 執行 loadsh 模塊,但並不輸出任何值 import 'lodash'; // 總體加載全部模塊,訪問時用 circle.xxx 訪問 import * as circle from './circle';
簡述:
ES6 模塊不是對象,而是經過export
命令顯式指定輸出的代碼,再經過import
命令輸入,它的接口只是一種靜態定義
,在代碼靜態解析階段
就會生成。// ES6模塊 import { stat, exists, readFile } from 'fs'; 上面代碼的實質是從fs模塊加載 3 個方法,其餘方法不加載。 這種加載稱爲「編譯時加載」或者靜態加載, 即 ES6 能夠在編譯時就完成模塊加載,效率要比 CommonJS 模塊的加載方式高。 固然,這也致使了無法引用 ES6 模塊自己,由於它不是對象。 因爲 ES6 模塊是編譯時加載,使得靜態分析成爲可能 import命令具備提高效果,會提高到整個模塊的頭部,首先執行 import命令是編譯階段執行的,在代碼運行以前。 因爲 import 是靜態執行,因此不能使用表達式和變量(這類只有在運行時才能獲得結果的語法結構) 靜態加載模塊的好處: 1. 再也不須要UMD模塊 2. 瀏覽器API能夠用模塊格式提供,沒必要再作成全局變量,再也不須要全局對象如:Math (能夠像Python同樣用模塊導入)
動態 import
動態import() 是很是有用的。而靜態型的 import 是初始化加載依賴項的最優選擇, 使用靜態 import 更容易從代碼靜態分析工具和 tree shaking 中受益 import(模塊路徑) 返回 promise,從 then 的結果裏拿到加載的模塊 webpack 2.x 以後,有一個魔力註釋的功能,會把加載的模塊重命名爲你註釋裏的文字
script type="application/javascript" 異步加載: async defer 腳本異步加載,不會阻塞dom結構的解析 async:加載完當即執行,渲染引擎中斷,待之腳本執行完繼續渲染 defer:加載完會等待頁面渲染完畢及頁面其餘腳本執行完畢纔會執行 多個 async 執行沒有順序保證,多個 defer 有順序保證
script type="module" 瀏覽器對 type="module" 的處理和 defer 標誌一致
ES6 處理「循環加載」與 CommonJS 有本質的不一樣。 ES6 模塊是動態引用,若是使用import從一個模塊加載變量(即import foo from 'foo'), 那些變量不會被緩存,而是成爲一個指向被加載模塊的引用, 須要開發者本身保證,真正取值的時候可以取到值。 es6 模塊會在使用使用時纔去加載對應的模塊 若是是循環應用,能夠將對應的輸出改寫成函數形式,利用函數的變量提高功能
// 此處是對比 CommonJS 模塊時運行時加載 -- 值得拷貝 ES6模塊時 編譯時 輸出接口 -- 值得引用 commonjs 模塊只會加載一次,之後在 碰到 require 一樣的東西就從緩存裏面加載 若是把原模塊導出的東西改變,引入模塊不會跟着改變,仍是從緩存裏面取原來的值 ES6模塊的運行機制與CommonJS不同,它遇到模塊加載命令import時,不會去執行模塊,而是隻生成一個動態的只讀引用。 等到真的須要用到時,再到模塊裏面去取值 JS 引擎對腳本靜態分析的時候,遇到模塊加載命令import,就會生成一個只讀引用。等到腳本真正執行時,再根據這個只讀引用,到被加載的那個模塊裏面去取值。 換句話說,ES6的輸入有點像Unix系統的「符號鏈接」,原始值變了,import輸入的值也會跟着變。 ES6 模塊是動態引用,而且不會緩存值,模塊裏面的變量綁定其所在的模塊。 commonjs: module.exports = {} exports 運行階段才加載模塊,可使用邏輯語句 模塊就是對象加載的就是該對象 加載的是整個模塊即將全部的接口都加載進來 輸出的是值得拷貝,原模塊發生變化不會影響已經加載的 this 指向當前的模塊 es6 模塊 export 能夠輸出多個 {} export default 解析階段肯定對外的接口,解析階段輸出接口,不可使用邏輯語句 加載的模塊不是對象 能夠單獨加載其中的幾個模塊 靜態分析,動態引用輸出的是值得引用,原模塊變化會影響已加載的模塊 this 指向 underfined
Babel 對 ES6 模塊轉碼就是轉換成 CommonJS 規範 模塊輸出語法轉換 Babel 對於模塊輸出的轉換,就是把全部輸出都賦值到 exports 對象的屬性上,並加上 ESModule: true 的標識 表示這個模塊是由 ESModule 轉換來的 CommonJS 輸出 對於解構賦值輸入 import {a} from './a.js' 轉義爲 var _c = require('./a.js') 而後取 _c.a 對於 default import a from './a' import {default as a} from './a' babel轉義時的處理,引入了一個 函數 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : {'default': obj} } var _a = _interopRequireDefault(require("./a.js")); console.log(_a["default"]); // 意思就是若是不是 esmodule 就爲其手動添加個 default 屬性,取值時統一取 default
export export.default
導出的模塊轉換爲 exports.xxx 和 exports.default
呢?而不是 module.exports
???我沒有找到解釋,若是您知道,麻煩給我留言下
module.exports = { entry: "./index.js", output: { path: path.resolve(__dirname, "dist"), filename: "[name].[contenthash:8].js" }, mode: "development" };
webpack --config webpack.config.js --env=dev
輸出 main.[hash].js
// 打包後代碼簡化以下 // 首先是一個 webpack 模塊運行時代碼 (function(modules) { // webpackBootstrap // 緩存模塊 var installedModules = {}; // 函數 __webpack_require__ 參數 模塊 id,用於加載和緩存模塊 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: {} }; // Execute the module function modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); // Flag the module as loaded module.l = true; // Return the exports of the module return module.exports; } /*** 全部加載的模塊都存在於 installedModules 內,其結構爲: id: { id, loaded: Boolean // 是否加載過 exports // 模塊的導出 } */ // 省略... 定義各類工具函數和變量 // Load entry module and return exports // 加載 entry 模塊,並返回其導出,咱們寫的模塊纔會被真正執行 return __webpack_require__(__webpack_require__.s = "./index.js"); })({ "./index.js": (function(module, __webpack_exports__, __webpack_require__) { // ... }, "./src/a.js": (function(module, __webpack_exports__, __webpack_require__) { // ... }, // ... }) 這個自調用的函數的參數 modules,就是包含全部待加載模塊的一個對象 { [id: string]: Function } 異步加載: import ==> webpack.requireEnsure ==> webpackJsonp https://www.njleonzhang.com/2018/12/30/webpack-bundle-1.html
export default
這種導出模塊的書寫方式後,會將其轉換成 exports.default
,這時若是用 require
引入時,須要對其加上 .default
如 require('./a.js').default
這樣才能獲取 a 模塊 export default
導出的 a
import
動態加載的模塊也須要 .default
才能獲取真實模塊導出的值,如 import('./a.js').then(res => res.dafault)
須要 babel-plugin-component
前端
import {Button} from 'antdesign' // 會被轉換成 commonjs ,以下 var Button = requre('antdesign').Button // 這樣就所有加載了 // 此時就須要 babel-plugin-component 出場了,其會將上述組件轉換成以下 import Button from 'antdesign/compoents/button' // 繼而轉換成 commonjs 時會變成這樣 var Button = require('antdesign/compoents/button').default 因此通常 ui 框架的項目結構目錄向下面同樣 compoennts comps1 comps.js index.js ...