javascript模塊化簡介

全部原創並不精彩,全部精彩並不是原創javascript

歷史

JavaScript 隨着時間的推移所負責的責任愈來愈重從最開始的添加表單驗證功能之類的腳本到angular 應用開發框架,隨着js任務愈來愈重就急需模塊化的解決方案。html

模塊化的基礎條件就是開闢一片獨立的上下文,那些擁有模塊化功能的語言或經過物理文件組織模塊,或以抽象的 namespace package 組織模塊,而JavaScript 並沒這種能力只能從語法上開闢獨立的上下文,就目前瀏覽器端運行的js來講能開闢獨立上下文的方式只有一種方式 function(閉包)java

  • 最開始的刀耕火種用閉包各類全局變量組織結構
  • AMD UMD commonjs es6
  • 如今webpack 支持 AMD commonjs es6 ,不過webpack更多的只是格式上的支持

對比一下各個模塊化方案代碼寫法

仔細觀察一下下面列舉的幾個例子不難發現根上仍是閉包node

AMD

define(['requrie','exports','module'],function(require, exports, module) {
        var a = require('a'),
            b = require('b');

        exports.A=a
    })
;
複製代碼

angular

angular.module('myApp', [])
  .controller('Ctl', ['$scope', '$log', function ($scope, $log) {
  $scope.name = 'leonwgc';
  $log.log('hello,world');
}]);
複製代碼

webpack

(function(modules) { // webpackBootstrap
    /******/ 	// The module cache
    /******/ 	var installedModules = {};
    /******/
    /******/ 	// The require function
    /******/ 	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;
    /******/ 	}
    /******/
    /******/ 	// Load entry module and return exports
    /******/ 	return __webpack_require__(__webpack_require__.s = 81);
    /******/ })
    /************************************************************************/
    /******/ ([
    /* 0 */
/***/ (function(module, exports, __webpack_require__) {

/***/ }),
/* 1 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
;
exports.cla = cla;

/***/ }),
/* 2 */
/***/ (function(module, exports, __webpack_require__) {
 "use strict";
var e = 2.71828182846;
function math(x) {
  return Math.exp(x);
}

module.exports = math;

/***/ })
        
    ]);
複製代碼

對比完代碼接下來簡要介紹一下AMD,Commonjs ,ES6模塊化的語法於定義jquery

AMD

define(id?, dependencies?, factory);webpack

id

第一個參數,id,是個字符串。它指的是定義中模塊的名字,這個參數是可選的。若是沒有提供該參數,模塊的名字應該默認爲模塊加載器請求的指定腳本的名字。若是提供了該參數,模塊名必須是「頂級」的和絕對的(不容許相對名字)。git

模塊格式

模塊名用來惟一標識定義中模塊,它們一樣在依賴數組中使用。AMD的模塊名規範是CommonJS模塊名規範的超集。引用以下:es6

  • 模塊名是由一個或多個單詞以正斜槓爲分隔符拼接成的字符串
  • 單詞須爲駝峯形式,或者".",".."
  • 模塊名不容許文件擴展名的形式,如".js"
  • 模塊名能夠爲 "相對的" 或 "頂級的"。若是首字符爲"."或".."則爲"相對的"模塊名
  • 頂級的模塊名從根命名空間的概念模塊解析
  • 相對的模塊名從 "require" 書寫和調用的模塊解析 上文引用的CommonJS模塊id屬性常被用於JavaScript模塊。

相對模塊名解析示例:github

  • 若是模塊 "a/b/c" 請求 "../d", 則解析爲"a/d"
  • 若是模塊 "a/b/c" 請求 "./e", 則解析爲"a/b/e"

依賴

第二個參數,dependencies,是個定義中模塊所依賴模塊的數組。依賴模塊必須根據模塊的工廠方法優先級執行,而且執行的結果應該按照依賴數組中的位置順序以參數的形式傳入(定義中模塊的)工廠方法中。web

依賴的模塊名若是是相對的,應該解析爲相對定義中的模塊。換句話來講,相對名解析爲相對於模塊的名字,並不是相對於尋找該模塊的名字的路徑。

本規範定義了三種特殊的依賴關鍵字。若是"require","exports", 或 "module"出如今依賴列表中,參數應該按照CommonJS模塊規範自由變量去解析。

依賴參數是可選的,若是忽略此參數,它應該默認爲["require", "exports", "module"]。然而,若是工廠方法的形參個數小於3,加載器會選擇以函數指定的參數個數調用工廠方法。

工廠方法

第三個參數,factory,爲模塊初始化要執行的函數或對象。若是爲函數,它應該只被執行一次。若是是對象,此對象應該爲模塊的輸出值。

若是工廠方法返回一個值(對象,函數,或任意強制類型轉換爲true的值),應該爲設置爲模塊的輸出值。

簡單的 CommonJS 轉換

若是依賴性參數被忽略,模塊加載器能夠選擇掃描工廠方法中的require語句以得到依賴性(字面量形爲require("module-id"))。第一個參數必須字面量爲require從而使此機制正常工做。

在某些狀況下,由於腳本大小的限制或函數不支持toString方法(Opera Mobile是已知的不支持函數的toString方法),模塊加載器能夠選擇掃描不掃描依賴性。

若是有依賴參數,模塊加載器不該該在工廠方法中掃描依賴性。

Simple Name/Value Pairs

If the module does not have any dependencies, and it is just a collection of name/value pairs, then just pass an object literal to define():

//Inside file my/shirt.js:
define({
    color: "black",
    size: "unisize"
});
複製代碼

Definition Functions

If the module does not have dependencies, but needs to use a function to do some setup work, then define itself, pass a function to define():

//my/shirt.js now does setup work
//before returning its module definition.
define(function () {
    //Do setup work here

    return {
        color: "black",
        size: "unisize"
    }
});
複製代碼

Definition Functions with Dependencies

If the module has dependencies, the first argument should be an array of dependency names, and the second argument should be a definition function. The function will be called to define the module once all dependencies have loaded. The function should return an object that defines the module. The dependencies will be passed to the definition function as function arguments, listed in the same order as the order in the dependency array:

//my/shirt.js now has some dependencies, a cart and inventory
//module in the same directory as shirt.js
define(["./cart", "./inventory"], function(cart, inventory) {
        //return an object to define the "my/shirt" module.
        return {
            color: "blue",
            size: "large",
            addToCart: function() {
                inventory.decrement(this);
                cart.add(this);
            }
        }
    }
);
複製代碼

Define a Module with Simplified CommonJS Wrapper

If you wish to reuse some code that was written in the traditional CommonJS module format it may be difficult to re-work to the array of dependencies used above, and you may prefer to have direct alignment of dependency name to the local variable used for that dependency. You can use the simplified CommonJS wrapper for those cases:

define(function(require, exports, module) {
        var a = require('a'),
            b = require('b');

        exports.A=a
    }
);

define(['requrie','exports','module'],function(require, exports, module) {
        var a = require('a'),
            b = require('b');

        exports.A=a
    }
);
複製代碼

r.js

babel

commonjs js 規範是 AMD 的子集 看一下demo

  • babel-plugin-transform-es2015-modules-amd
  • babel-plugin-transform-es2015-modules-commonjs
  • babel-plugin-transform-es2015-modules-systemjs
  • babel-plugin-transform-es2015-modules-umd
$ babel ES6 --out-dir AMD --plugins=transform-es2015-modules-amd
$ babel ES6 --out-dir UMD --plugins=transform-es2015-modules-umd
$ babel ES6 --out-dir common --plugins=transform-es2015-modules-commonjs

複製代碼

Commonjs

CommonJS API定義不少普通應用程序(主要指非瀏覽器的應用)使用的API,從而填補了這個空白。它的終極目標是提供一個相似Python,Ruby和Java標 準庫。這樣的話,開發者可使用CommonJS API編寫應用程序,而後這些應用能夠運行在不一樣的JavaScript解釋器和不一樣的主機環境中。在兼容CommonJS的系統中,你可使用 JavaScript程序開發:

  • 服務器端JavaScript應用程序
  • 命令行工具
  • 圖形界面應用程序
  • 混合應用程序(如,Titanium或Adobe AIR...)

基本語法

var x = 5;
var addX = function (value) {
  return value + x;
};
module.exports.x = x;
module.exports.addX = addX;
// 上面代碼經過module.exports輸出變量x和函數addX。

// require方法用於加載模塊。
var example = require('./example.js');

console.log(example.x); // 5
console.log(example.addX(1)); // 6
複製代碼

module對象

  • module.id 模塊的識別符,一般是帶有絕對路徑的模塊文件名。
  • module.filename 模塊的文件名,帶有絕對路徑。
  • module.loaded 返回一個布爾值,表示模塊是否已經完成加載。
  • module.parent 返回一個對象,表示調用該模塊的模塊。
  • module.children 返回一個數組,表示該模塊要用到的其餘模塊。
  • module.exports 表示模塊對外輸出的值。
{ id: '.',
  exports: { '$': [Function] },
  parent: null,
  filename: '/path/to/example.js',
  loaded: false,
  children:
   [ { id: '/path/to/node_modules/jquery/dist/jquery.js',
       exports: [Function],
       parent: [Circular],
       filename: '/path/to/node_modules/jquery/dist/jquery.js',
       loaded: true,
       children: [],
       paths: [Object] } ],
  paths:
   [ '/home/user/deleted/node_modules',
     '/home/user/node_modules',
     '/home/node_modules',
     '/node_modules' ]
}
複製代碼

exports

exports 要注意的問題

exports = function(x) {console.log(x)};

複製代碼

函數傳參傳入引用的引用

函數傳參基本是兩種類型 值類型和引用類型 最先接觸這個問題是在湯姆大叔的博客中

var liz={age:18}

function fun(liz){
 liz={age:19}
}

複製代碼
(function (exports, require, module, __filename, __dirname) {
  // exports = module.exports
});
複製代碼

ES6

在 ES6 以前,社區制定了一些模塊加載方案,最主要的有 CommonJS 和 AMD 兩種。前者用於服務器,後者用於瀏覽器。ES6 在語言標準的層面上,實現了模塊功能,並且實現得至關簡單,徹底能夠取代 CommonJS 和 AMD 規範,成爲瀏覽器和服務器通用的模塊解決方案。

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

// 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命令輸入。

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

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

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

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

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

module 語法

推薦

工做上的體會這種模塊加載的語法,就用最簡單常見的方式就好,千萬不要過多操做

import _default from "xxx"
import _default,{a,b,c} from "xxx"

export default class xxx{}
export class xxx{}
export {xx,xxx,xxxx}
複製代碼

export 與 import 的複合寫法

若是在一個模塊之中,先輸入後輸出同一個模塊,import語句能夠與export語句寫在一塊兒。

export { foo, bar } from 'my_module';

// 能夠簡單理解爲
import { foo, bar } from 'my_module';
export { foo, bar };
上面代碼中,exportimport語句能夠結合在一塊兒,寫成一行。但須要注意的是,寫成一行之後,foo和bar實際上並無被導入當前模塊,只是至關於對外轉發了這兩個接口,致使當前模塊不能直接使用foo和bar。

模塊的接口更名和總體輸出,也能夠採用這種寫法。

// 接口更名
export { foo as myFoo } from 'my_module';

// 總體輸出
export * from 'my_module';
默認接口的寫法以下。

export { default } from 'foo';
具名接口改成默認接口的寫法以下。

export { es6 as default } from './someModule';

// 等同於
import { es6 } from './someModule';
export default es6;
一樣地,默認接口也能夠更名爲具名接口。

export { default as es6 } from './someModule';
下面三種import語句,沒有對應的複合寫法。

import * as someIdentifier from "someModule";
import someIdentifier from "someModule";
import someIdentifier, { namedIdentifier } from "someModule";
爲了作到形式的對稱,如今有提案,提出補上這三種複合寫法。

export * as someIdentifier from "someModule";
export someIdentifier from "someModule";
export someIdentifier, { namedIdentifier } from "someModule";
複製代碼

模塊的繼承

模塊之間也能夠繼承。

假設有一個circleplus模塊,繼承了circle模塊。

// circleplus.js

export * from 'circle';
export var e = 2.71828182846;
export default function(x) {
  return Math.exp(x);
}
上面代碼中的export *,表示再輸出circle模塊的全部屬性和方法。注意,export *命令會忽略circle模塊的default方法。而後,上面代碼又輸出了自定義的e變量和默認方法。

這時,也能夠將circle的屬性或方法,更名後再輸出。

// circleplus.js

export { area as circleArea } from 'circle';
上面代碼表示,只輸出circle模塊的area方法,且將其更名爲circleArea。

加載上面模塊的寫法以下。

// main.js

import * as math from 'circleplus';
import exp from 'circleplus';
console.log(exp(math.e));
上面代碼中的import exp表示,將circleplus模塊的默認方法加載爲exp方法。
複製代碼

本文最後推一手我寫的自動生成模塊索引的工具index-creater

相關文章
相關標籤/搜索