前端模塊化在近幾年層出不窮,有Node的CommonJs,也有屬於client端的CMD/AMD模式,而ES6自己也出現了Modules,再加上Webpack以及babel的普及,雖然在代碼中常用到這些用法,可是若是不去深刻研究,總以爲是一個黑魔法,沒法探測一些問題的根源。javascript
事實上,隨着打包工具和Babel在前端工程化的世界裏大放異彩,AMD/CMD也在逐步退出歷史的舞臺,這裏簡單的介紹下其用法及語義。html
AMD及其用法 前端
AMD 即Asynchronous Module Definition,中文名是異步模塊定義的意思。表明(require.js)java
/** main.js 入口文件/主模塊 **/ // 首先用config()指定各模塊路徑和引用名 require.config({ baseUrl: "js/lib", paths: { "jquery": "jquery.min", //實際路徑爲js/lib/jquery.min.js "underscore": "underscore.min", } }); // 執行基本操做 require(["jquery","underscore"],function($,_){ // some code here });
CMD及其用法 node
CMD 即Common Module Definition, 中文名是通用模塊定義的意思。表明(Sea.js)jquery
/** sea.js **/ // 定義模塊 math.js define(function(require, exports, module) { var $ = require('jquery.js'); var add = function(a,b){ return a+b; } exports.add = add; }); // 加載模塊 seajs.use(['math.js'], function(math){ var sum = math.add(1+2); });
二者的區別 webpack
一、AMD推崇依賴前置,在定義模塊的時候就要聲明其依賴的模塊
二、CMD推崇就近依賴,只有在用到某個模塊的時候再去requirees6
Commonjs的應用主要是在Node應用中。 web
經過require引入文件, 文件內部則經過module.export暴露,以下a 就是 module.exportnpm
// 引入某個文件 const a = require('some.js') // some.js module.export = { ... // some code }
除去module.export,Commonjs還有一個exports屬性(不推薦使用), 事實上exports就是module.export
// 對外輸出接口能夠添加變量 var exports = module.exports; exports.area = function (r) { return Math.PI * r * r; }; exports.circumference = function (r) { return 2 * Math.PI * r; }; // 注意不要直接對exports賦值,這樣會切斷exports和module的關係 exports = a // 不要這麼作
ES6的Module是官方正式推出的模塊化寫法,雖然目前有挺多瀏覽器還不支持,不過咱們能夠利用babel將其轉換,話很少說,先介紹下Module的基本用法。
ES6的module主要是以import導入想要的對象,export 和 export default導出對象
import x from 'some.js' // 引用some.js中的export default import {a, b} from 'some.js' // 引用some.js的 export a 和 export b import x, {a, b} from 'some.js' // 引用 some.js的 export default 和 export a 和 export b // some.js const x = () => {} export const a = () => {} export const b = () => {} export default x
由於import是編譯時加載,因此import命令具備提高效果,會提高到整個模塊的頭部,首先執行。
// some code ... ... import xxx from 'xxx' // 提高到最頂部
1. 加載的時機不一樣
Common是運行時加載的,可使用變量或者表達式,如:
const 'f' + 'oo' = require('my_modules')
Module是編譯時加載的,不可使用變量或者表達式, 編譯時加載效率較高。
2.暴露出的接口不一樣
Common暴露出來的是值的拷貝,也就是說,一旦輸出一個值,模塊內部的變化就影響不到這個值。
// lib.js var counter = 3; function incCounter() { counter++; } module.exports = { counter: counter, incCounter: incCounter, };
// main.js var counter = require('./lib').counter; var incCounter = require('./lib').incCounter; console.log(counter); // 3 incCounter(); console.log(counter); // 3
Module則相反, 輸出的是值的引用。
webpack自己維護了一套模塊系統,這套模塊系統兼容了全部前端歷史進程下的模塊規範,包括 amd commonjs es6 等,爲了看module在webpack中是怎麼運行的,咱們能夠看一下下面簡單的代碼:
// webpack const path = require('path'); module.exports = { entry: './a.js', output: { path: path.resolve(__dirname, 'dist'), filename: 'bundle.js', } };
// a.js import a from './c'; export default 'a.js'; console.log(a);
// c.js export default 333;
打包後的代碼以下:
(function(modules) { function __webpack_require__(moduleId) { var module = { i: moduleId, l: false, exports: {} }; modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); return module.exports; } return __webpack_require__(0); })([ (function (module, __webpack_exports__, __webpack_require__) { // 引用 模塊 1 "use strict"; Object.defineProperty(__webpack_exports__, "__esModule", { value: true }); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__c__ = __webpack_require__(1); /* harmony default export */ __webpack_exports__["default"] = ('a.js'); console.log(__WEBPACK_IMPORTED_MODULE_0__c__["a" /* default */]); }), (function (module, __webpack_exports__, __webpack_require__) { // 輸出本模塊的數據 "use strict"; /* harmony default export */ __webpack_exports__["a"] = (333); }) ]);
簡化一波代碼再看,能夠看出打包後其實是一個當即執行函數,而且入參爲各個module文件, 最後返回的是__webpack_require__(0):
(function(modules) { function __webpack_require__(moduleId) { } return __webpack_require__(0); })([module1, module2]);
ok, 咱們繼續看__webpack_require__函數,能夠看出它是調用了咱們的入口模塊,同時傳入了module相關的屬性,以及函數自己
function __webpack_require__(moduleId) { var module = { i: moduleId, l: false, exports: {} }; modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); return module.exports; }
那麼繼續追溯到入口模塊,也就是咱們的第一個參數咱們能夠看到入口模塊又調用了 __webpack_require__(1) 去引用入參數組裏的第2個函數。
而後會將入參的 webpack_exports 對象添加 default 屬性,並賦值。
這裏咱們就能看到模塊化的實現原理,這裏的 webpack_exports 就是這個模塊的 module.exports 經過對象的引用傳參,間接的給 module.exports 添加屬性。
最後會將 module.exports return 出來。就完成了 webpack_require 函數的使命。
function (module, __webpack_exports__, __webpack_require__) { /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__c__ = __webpack_require__(1); /* harmony default export */ __webpack_exports__["default"] = ('a.js'); console.log(__WEBPACK_IMPORTED_MODULE_0__c__["a" /* default */]); }
至此,咱們能夠看出module其實在webpack中,最後的打包結果。
雖然webpack能夠打包轉換咱們的module,但一般咱們都會引入babel來對ES6轉成ES5的代碼,而Moduel屬於ES6,也會被轉譯。
事實上,babel是將module轉換成commonjs,這樣 webpack 就無需再作處理,直接使用 webpack 運行時定義的 webpack_require 處理。
不過babel在轉換的時候,會有一些特殊的處理, 像下面
首先 export 的時候, 會添加一個__esModule屬性到exports,是爲了代表這是通過轉換的module
export default a // 轉換成 Object.defineProperty(exports, "__esModule", { value: true }); exports.default = a;
再看 轉出的
轉出其實會多一個_interopRequireDefault函數,就是爲了處理default這個屬性
import d from 'd' // 轉化後 var _d = require('d'); var _d2 = _interopRequireDefault(_d); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
1.爲何有的地方使用 require 去引用一個模塊時須要加上 default?
咱們在上文 babel 對導出模塊的轉換提到,es6 的 export default 都會被轉換成 exports.default,即便這個模塊只有這一個輸出。
2.常常在各大UI組件引用的文檔上會看到說明 import { button } from 'xx-ui' 這樣會引入全部組件內容,須要添加額外的 babel 配置,好比 babel-plugin-component?
import { Button, Select } from 'element-ui' // 轉換成 var a = require('element-ui'); var Button = a.Button; var Select = a.Select;
babel-plugin-component就作了一件事,將 import { Button, Select } from 'element-ui' 轉換成了
import Button from 'element-ui/lib/button' import Select from 'element-ui/lib/select'
3.咱們在瀏覽一些 npm 下載下來的 UI 組件模塊時(好比說 element-ui 的 lib 文件下),看到的都是 webpack 編譯好的 js 文件,可使用 import 或 require 再去引用。可是咱們平時編譯好的 js 是沒法再被其餘模塊 import 的,這是爲何?
經過 webpack 模塊化原理章節給出的 webpack 配置編譯後的 js 是沒法被其餘模塊引用的,webpack 提供了 output.libraryTarget 配置指定構建完的 js 的用途。入口模塊返回的 module.exports 賦值給 module.exports
在剖析了總體的流程以後,能夠看到相關的技術細節仍是比較清晰的,學無止境~~~
import、require、export、module.exports 混合使用詳解
Module的語法
前端模塊化
Commonjs規範