javascript 模塊化開發

1、爲何會有模塊化


1. 當一個項目開發的愈來愈複雜的時候,會遇到一些問題,好比:

  • 命名衝突:當項目由團隊進行協做開發的時候,不一樣開發人員的變量和函數命名可能相同;即便是一個開發,當開發週期比較長的時候,也有可能會忘記以前使用了什麼變量,從而致使重複命名,致使命名衝突。javascript

  • 文件依賴:代碼重用時,引入js文件的數目可能少了,或者引入的順序不對,好比使用boostrap的時候,須要引入jQuery,而且jQuery的文件必需要比boostrap的js文件先引入。php

2. 當使用模塊化開發的時候能夠避免以上的問題,而且讓開發的效率變高,以及方便後期的維護:

  • 提高開發效率:代碼方便重用,別人開發的模塊直接拿過來就可使用,不須要重複開發法相似的功能。css

  • 方便後期維護:代碼方便重用,別人開發的模塊直接拿過來就可使用,不須要重複開發法相似的功能。html

因此總結來講,在生產角度,模塊化開發是一種生產方式,這種方式生產效率高,維護成本低。從軟件開發角度來講,模塊化開發是一種開發模式,寫代碼的一種方式,開發效率高,方便後期維護。java

2、模塊化開發的演變過程


1. 全局函數

function add(a , b) { return parseFloat(a) + parseFloat(b); } function substract(a ,b) {} function multiply(a ,b) {} function divide(a ,b) {} 

在早期的開發過程當中就是將重複的代碼封裝到函數中,再將一系列的函數放到一個文件中,這種狀況下全局函數的方式只能認爲的認爲它們屬於一個模塊,可是程序並不能區分哪些函數是同一個模塊,若是僅僅從代碼的角度來講,這沒有任何模塊的概念。node

存在的問題:jquery

  • 污染了全局變量,沒法保證不與其餘模塊發生變量名衝突。
  • 模塊成員之間看不出直接關係。

2. 對象封裝-命名空間

var calculator = { add: function(a, b) { return parseFloat(a) + parseFloat(b); }, subtract: function(a, b) {}, multiply: function(a, b) {}, divide: function(a, b) {} }; 

經過添加命名空間的形式從某種程度上解決了變量命名衝突的問題,可是並不能從根本上解決命名衝突。 不過此時從代碼級別能夠明顯區分出哪些函數屬於同一個模塊。git

存在的問題:github

  • 暴露了全部的模塊成員,內部狀態能夠被外部改寫,不安全。
  • 命名空間愈來愈長。

3. 私有公有成員分離

var calculator = (function () { // 這裏造成一個單獨的私有的空間 // 私有成員的做用: // 一、將一個成員私有化 // 二、抽象公共方法(其餘成員中會用到的) // 私有的轉換邏輯 function convert(input){ return parseInt(input); } function add(a, b) { return convert(a) + convert(b); } function subtract(a, b) {} function multiply(a, b) {} function divide(a, b) {} return { add : add, subtract : subtract, multiply : multiply, divide : divide } })(); 
  1. 利用此種方式將函數包裝成一個獨立的做用域,私有空間的變量和函數不會影響到全局做用域。
  2. 以返回值的方式獲得模塊的公共成員,公開公有方法,隱藏私有空間內部的屬性、元素,好比註冊方法中可能會記錄日誌。
  3. 能夠有選擇的對外暴露自身成員。
  4. 從某種意義上來講,解決了變量命名衝突的問題。

4. 模塊的擴展與維護

// 計算模塊 (function (calculator) { function convert(input) { return parseInt(input); } calculator.add = function(a, b) { return convert(a) + convert(b); } window.calculator = calculator; })(window.calculator || {}); // 新增需求 (function (calculator) { calculator.remain = function (a , b) { return a % b; } window.calculator = calculator; })(window.calculator || {}); alert(calculator.remain(4,3)); 
  1. 利用此種方式,有利於對龐大的模塊的子模塊劃分。
  2. 實現了開閉原則:對新增開發,對修改關閉。對於已有文件儘可能不要修改,經過添加新文件的方式添加新功能。

5. 第三方依賴的管理

(function (calculator , $) { // 依賴函數的參數,是屬於模塊內部 // console.log($); calculator.remain = function (a , b) { return a % b; } window.calculator = calculator; })(window.calculator || {} , jQuery); 

模塊最好要保證模塊的職責單一性,最好不要與程序的其餘部分直接交互,經過向匿名函數注入依賴項的形式,除了保證模塊的獨立性,還使模塊之間的以來關係變得明顯。
  對於模塊的依賴經過自執行函數的參數傳入,這樣作能夠作到依賴抽象,本例中使用的jQuery,而當要使用zepto的時候,只要更換傳入的參數便可。
  原則:高內聚低耦合,模塊內相關性高,模塊間關聯低。npm

總結:在什麼場景下使用模塊化開發

  • 業務複雜
  • 重用邏輯很是多
  • 擴展性要求較高

3、模塊化規範


服務器端規範主要是CommonJSnode.js用的就是CommonJS規範。
  客戶端規範主要有:AMD(異步模塊定義,推崇依賴前置)、CMD(通用模塊定義,推崇依賴就近)。AMD規範的實現主要有RequireJSCMD規範的主要實現有SeaJSRequireJS在國外用的比較多,SeaJS在國內用的比較多,而且SeaJS的創始人爲阿里的玉伯,因此SeaJS在阿里系用的很是普遍,包括京東等大廠也在用SeaJS,咱們詳細介紹的也是SeaJS。可是SeaJS已經中止維護了,由於在ES6中已經有了模塊化的實現,隨着ES6的普及,第三方的模塊化實現將會慢慢的淘汰(可是這個在國內可能還要不少年)。

4、SeaJs


1. SeaJs簡介

  • SeaJS是一個基於CMD規範實現的模塊化開發解決方案。
  • 做者:Alibaba 玉伯
  • Alibaba 玉伯
  • 特性:
    • 簡單友好的模塊化定義規範。
    • 天然直觀的代碼組織方式。
  • 哲學:一切皆模塊

2. 使用步驟

  • 引入sea.js庫
  • 定義模塊
     

define(function(require, exports, module){ 模塊代碼 });

* 暴露接口
- exports - module.exports * 依賴模塊 

require(‘模塊id’)

* 啓動模塊系統

seajs.use(‘模塊id’,function( 模塊對象 ){ 業務代碼 });

#####HelloWorld * 01-convertor.js ```JavaScript /** * 轉換模塊,處處成員:convertToNumber */ define(function (require, exports, module) { exports.convertToNumber = function (input) { return parseFloat(input); } }); 
  • 01-calculator.js
define(function (require, exports, module) { // 此處是模塊的私有空間,定義模塊的私有成員 // 載入01-convertor模塊 var convertor = require('./01-convertor'); function add(a, b) { return convertor.convertToNumber(a) + convertor.convertToNumber(b); } exports.add = add; }); 
  • 01-helloworld.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="node_modules/seajs/dist/sea.js"></script> <script> seajs.use('./01-calculator.js', function (calculator) { alert(calculator.add(1,2)); }); </script> </head> <body> </body> </html> 

3. 定義模塊define

  • 先有規範,後有實現

  • 在CMD規範中,一個模塊就是一個js文件

  • define是一個全局函數,用來定義模塊

  • define(factory)

    • 對象{}這種方式,外部會直接獲取到該對象
    • 字符串''同上
    • 函數function( require, exports, module ){ // 模塊代碼 }
      • 注意:爲了減小出錯,定義函數的時候直接把這三個參數寫上

factory爲對象、字符串時,表示模塊的接口就是該對象、字符串。好比能夠以下定義一個 JSON 數據模塊:

define({ "foo": "bar" }); 

也能夠經過字符串定義模板模塊:

define('I am a template. My name is {{name}}.'); 

factory爲函數時,表示是模塊的構造方法。執行該構造方法,能夠獲得模塊向外提供的接口。factory方法在執行時,默認會傳入三個參數:require、exports和 module:

define(function(require, exports, module) { // 模塊代碼}); 
define define(id?, deps?, factory)

define也能夠接受兩個以上參數。字符串 id表示模塊標識,數組 deps是模塊依賴。好比:

define('hello', ['jquery'], function(require, exports, module) { // 模塊代碼}); 

id和 deps參數能夠省略。省略時,能夠經過構建工具自動生成。
  注意:帶 id和 deps參數的 define用法不屬於 CMD 規範,而屬於 Modules/Transport 規範。

define.cmd Object

一個空對象,可用來斷定當前頁面是否有 CMD 模塊加載器:

if (typeof define === "function" && define.cmd) { // 有 Sea.js 等 CMD 模塊加載器存在 } 
require Function

require是 factory函數的第一個參數。

require require(id)

require是一個方法,接受 模塊標識 做爲惟一參數,用來獲取其餘模塊提供的接口。

define(function(require, exports) { // 獲取模塊 a 的接口 var a = require('./a'); // 調用模塊 a 的方法 a.doSomething(); }); 

注意:在開發時,require的書寫須要遵循一些 簡單約定

require.async require.async(id, callback?)

require.async方法用來在模塊內部異步加載模塊,並在加載完成後執行指定回調。callback參數可選。

define(function(require, exports, module) { // 異步加載一個模塊,在加載完成時,執行回調 require.async('./b', function(b) { b.doSomething(); }); // 異步加載多個模塊,在加載完成時,執行回調 require.async(['./c', './d'], function(c, d) { c.doSomething(); d.doSomething(); }); }); 

注意:require是同步往下執行,require.async則是異步回調執行。require.async 通常用來加載可延遲異步加載的模塊。

require.resolve require.resolve(id)

使用模塊系統內部的路徑解析機制來解析並返回模塊路徑。該函數不會加載模塊,只返回解析後的絕對路徑。

define(function(require, exports) { console.log(require.resolve('./b')); // ==> http://example.com/path/to/b.js }); 

這能夠用來獲取模塊路徑,通常用在插件環境或需動態拼接模塊路徑的場景下。

4. exports 和 module.exports

  • 功能:經過給 exports或module.exports動態的掛載變量、函數或對象,外部會獲取到該接口
  • exports 等價於 module.exports
  • 能夠經過屢次給exports 掛載屬性向外暴露
  • 不能直接給 exports 賦值
  • 若是想暴露單個變量、函數或對象能夠經過直接給module.exports 賦值 便可

5. exports Object

exports是一個對象,用來向外提供模塊接口。

define(function(require, exports) { // 對外提供 foo 屬性 exports.foo = 'bar'; // 對外提供 doSomething 方法 exports.doSomething = function() {}; }); 

除了給 exports對象增長成員,還可使用 return直接向外提供接口。

define(function(require) { // 經過 return 直接提供接口 return { foo: 'bar', doSomething: function() {} }; }); 

若是 return語句是模塊中的惟一代碼,還可簡化爲:

define({ foo: 'bar', doSomething: function() {} }); 

上面這種格式特別適合定義 JSONP 模塊。

特別注意:下面這種寫法是錯誤的!

define(function(require, exports) { // 錯誤用法!!! exports = { foo: 'bar', doSomething: function() {} }; }); 

正確的寫法是用 return或者給 module.exports賦值:

define(function(require, exports, module) { // 正確寫法 module.exports = { foo: 'bar', doSomething: function() {} }; }); 

提示:exports 僅僅是 module.exports 的一個引用。在 factory 內部給 exports 從新賦值時,並不會改變 module.exports 的值。所以給 exports 賦值是無效的,不能用來更改模塊接口。

6. module Object

module是一個對象,上面存儲了與當前模塊相關聯的一些屬性和方法。

6.1 module.id String

模塊的惟一標識。

define('id', [], function(require, exports, module) { // 模塊代碼 }); 

上面代碼中,define的第一個參數就是模塊標識。

6.2 module.uri String

根據模塊系統的路徑解析規則獲得的模塊絕對路徑。

define(function(require, exports, module) { console.log(module.uri); // ==> http://example.com/path/to/this/file.js }); 

通常狀況下(沒有在 define 中手寫 id 參數時),module.id 的值就是 module.uri,二者徹底相同。

6.3 module.dependencies Array

dependencies是一個數組,表示當前模塊的依賴。

6.4 module. exports Object

當前模塊對外提供的接口。
  傳給 factory 構造方法的 exports 參數是 module.exports 對象的一個引用。只經過 exports 參數來提供接口,有時沒法知足開發者的全部需求。 好比當模塊的接口是某個類的實例時,須要經過 module.exports 來實現:

define(function(require, exports, module) { // exports 是 module.exports 的一個引用 console.log(module.exports === exports); // true // 從新給 module.exports 賦值 module.exports = new SomeClass(); // exports 再也不等於 module.exports console.log(module.exports === exports); // false }); 

注意:對 module.exports 的賦值須要同步執行,不能放在回調函數裏。下面這樣是不行的:

// x.js define(function(require, exports, module) { // 錯誤用法 setTimeout(function() { module.exports = { a: "hello" }; }, 0); }); 

在 y.js 裏有調用到上面的 x.js:

// y.js define(function(require, exports, module) { var x = require('./x'); // 沒法馬上獲得模塊 x 的屬性 a console.log(x.a); // undefined }); 

7. 小結

這就是 CMD 模塊定義規範的全部內容。常用的 API 只有 define, require, require.async, exports, module.exports 這五個。其餘 API 有個印象就好,在須要時再來查文檔,不用刻意去記。

與 RequireJS 的 AMD 規範相比,CMD 規範儘可能保持簡單,並與 CommonJS 和 Node.js 的 Modules 規範保持了很大的兼容性。經過 CMD 規範書寫的模塊,能夠很容易在 Node.js 中運行

5、CommonJs

其實學完了SeaJs規範以後,CommonJs規範也就差很少會用了,二者很是的類似,而且在node.js中使用起來更簡單。

在node.js中直接使用require引包,直接使用exports和module.exports暴露公開成員,而且npm基於CommonJs實現了自動加載和安裝依賴。

一樣的CommonJs讓node.js變得:一、增長內聚性,有助分工協做,二、方便重構,三、提升代碼質量

node.js中的實現爲:

(function(exports,require,module,__filename,__dirname){ return module.exports; }); 

require

  • 加載模塊後會緩存,屢次加載後獲得同一對象 require('http')

  • 查看模塊緩存console.log(require.cache);

  • 查詢模塊絕對路徑 require.resolve('./test.js');

  • 查看單個的模塊緩存 require.cache[require.resolve('./test.js')]

  • 刪除模塊緩存 delete require.cache[require.resolve('./test.js')];

做者:iceman_dev 連接:https://www.jianshu.com/p/3832c00a44a7 來源:簡書 簡書著做權歸做者全部,任何形式的轉載都請聯繫做者得到受權並註明出處。
相關文章
相關標籤/搜索