JavaScript——模塊化

什麼是模塊?

就比如寫書的做者同樣,會把一本書分紅一個個的章節,再把每一章的內容分紅若干節,而後構建成一本完整的書,讓書更加有條理性和可閱讀性。而做爲一個程序員,就能夠把代碼分紅一些模塊,而後組成一個完整的「功能」javascript

模塊化的好處——下降代碼耦合性

  • 可維護性:模塊的獨立性,一個良好的模塊會減小外面的代碼對本身依賴,能夠獨立的去更新和改進
  • 命名空間:莫葵花開發封裝變量,能夠避免污染全局環境
  • 可複用性:將本身的模塊引入到不一樣項目,避免複製粘貼

模塊化的實現

  1. ES6以前
    在ES6以前,JavaScript不支持類的概念,而恰好建立一個函數的時候,解析器會針對函數產生一個新的運行環境,函數運行完了以後就會銷燬一切。從而就能夠隨意建立對象,達到了相似私有變量的效果,避免了父命名空間的衝突
匿名函數
(function(){
    //在函數的做用域中下面的變量是私有的
    var myGrades = [93,95,88,0,55,97];

    var average = function(){
        var total = myGrades.reduce(function(accumulator,item){
            return accumulator+item;
        },0)
        return 'Your average grade is ' + total / myGrades.length + '.';
    }

    var failing = function(){
        var failingGrades = myGrades.filter(function(item){
            return item < 70;
        })
        return "you failed" + failingGrades.length + 'times.';
    }
    console.log(failing()); //You failed 2 times.

}());
  • 注:要用圓括號把整個函數包起來,若是語句直接以關鍵詞開始,會被認爲是一個函數聲明,而不是函數語句。函數聲明必須有名字,函數語句才能夠匿名
把全局函數注入到匿名函數
(function (globalVariable) {

  // 在函數的做用域中下面的變量是私有的
  var privateFunction = function() {
    console.log('this is private!');
  }

  // 經過全局變量設置下列方法的外部訪問接口
  // 與此同時這些方法又都在函數內部

  globalVariable.each = function(collection, iterator) {
    if (Array.isArray(collection)) {
      for (var i = 0; i < collection.length; i++) {
        iterator(collection[i], i, collection);
      }
    } else {
      for (var key in collection) {
        iterator(collection[key], key, collection);
      }
    }
  };

  globalVariable.filter = function(collection, test) {
    var filtered = [];
    globalVariable.each(collection, function(item) {
      if (test(item)) {
        filtered.push(item);
      }
    });
    return filtered;
  };

  globalVariable.map = function(collection, iterator) {
    var mapped = [];
    globalUtils.each(collection, function(value, key, collection) {
      mapped.push(iterator(value));
    });
    return mapped;
  };

 }(globalVariable));
  • 在上例中,globalVariable是惟一一個全局變量,這種作法比徹底匿名的閉包的好處是,代碼結構更清晰,並且性能更好。咱們可以看到函數內部傳遞進來了全局變量,因此依賴關係很是清晰。其次在函數內部調用 globalVarible 的時候,解釋器可以直接找到局部的 globalVarible,就不用上溯到外部的 globalVarible.
對象接口
var myGradesCalculate = (function(){

    var myGrades = [93,95,88,0,55,97];
    // 經過接口在外部訪問下列方法
    // 與此同時這些方法又都在函數內部
    return {
        average: function(){
            var total = myGrades.reduce(function(accumulator,item){
                return accumulator+item;
            },0);

            return 'Your average grade is ' + total / myGrades.length + '.';
        },
        failing: function() {
            var failingGrades = myGrades.filter(function(item) {
                return item < 70;
            });

            return 'You failed ' + failingGrades.length + ' times.';
        }
    }
})();

myGradesCalculate.failing(); // 'You failed 2 times.' 
myGradesCalculate.average(); // 'Your average grade is 71.33333333333333.'
  • 當即執行的匿名函數返回了一個對象。這樣就算使用獨立的對象接口
把要公開的屬性專門放在一個對象聲明中返回
var myGradesCalculate = (function () {
    
  var myGrades = [93, 95, 88, 0, 55, 91];
  
  var average = function() {
    var total = myGrades.reduce(function(accumulator, item) {
      return accumulator + item;
      }, 0);
      
    return'Your average grade is ' + total / myGrades.length + '.';
  };

  var failing = function() {
    var failingGrades = myGrades.filter(function(item) {
        return item < 70;
      });

    return 'You failed ' + failingGrades.length + ' times.';
  };
  // 將公有指針指向私有方法

  return {
    average: average,
    failing: failing
  }
})();

myGradesCalculate.failing(); // 'You failed 2 times.' 
myGradesCalculate.average(); // 'Your average grade is 71.33333333333333.'
  • 相比於前一種方法,這種是默認全部的變量和方法都是私有的,只在最後顯示return對象的時候,才選擇暴露出對外接口,接口比較清晰
CommonJS和AMD

上面這些方法都有一個共同點:使用一個特定的全局模塊名來把一些私有變量和方法包起來,而後經過閉包來建立一個私有的命名空間。html

缺點:java

  • 沒法管理不一樣模塊之間的依賴關係。由於js是順序執行的,因此必須搞清楚js文件的依賴關係,一旦js文件過多,順序加載就很麻煩了。
  • 命名空間衝突沒法解決
CommonJS
  • 每一個JS文件都是一個獨立的模塊上下文,在這個上下文中默認建立的屬性都是私有的。對其餘文件是不可見的。

作法: 經過module.exports對象暴露對外接口。經過require()進行調用
典例: Node.js程序員

優點:web

  • 避免全局命名空間污染,require 進來的模塊能夠被賦值到本身隨意定義的局部變量中
  • 依賴關係更加清晰

特色:編程

  • 主要適用場景是服務器端編程,採用的是同步加載模塊策略。依賴的模塊逐個加載

劣勢:數組

  • 服務器模塊加載主要來源硬盤或內存,因此加載速度比較快,同步加載影響不大。單是瀏覽器端,網絡中加載一個模塊比硬盤加載慢,在等待加載過程當中,瀏覽器會掛起當前進程,知道模塊下載完成
AMD(異步模塊定義)

使用方式:瀏覽器

define([moduleA,moduleB],function(moduleA,moduleB){
  console.log(moduleA.hello());
});
  • 第一個參數是一個數組,數組中有兩個字符串也就是須要依賴的模塊名稱。AMD 會以一種非阻塞的方式,經過appendChild將這兩個模塊插入到DOM中。在兩個模塊都加載成功以後,define 會調用第二個參數中的回調函數,通常是函數主體。
  • define既是一種引用模塊的方式,也是定義模塊的方式
//moduleA
define([],function(){
  return {
    hello: function(){
      console.log('hello');
    },
    goodbye: function(){
      console.log('bye');
    }
  }
});

特色:服務器

  • 優先瀏覽器,異步載入模塊
  • 支持在模塊中使用對象、函數、構造函數、字符串、JSON等數據類型,而CommonJS只支持對象
  • AMD不支持Node裏的一些諸如 IO,文件系統等其餘服務器端的功能
UMD(通用模塊定義規範)

對於須要同時支持 AMDCommonJS 的模塊而言,可使用 UMD,而且UMD指出全局變量定義,因此UMD能夠同時在客戶端和服務端使用網絡

(function (root, factory) {
  if (typeof define === 'function' && define.amd) {
      // AMD
    define(['myModule', 'myOtherModule'], factory);
  } else if (typeof exports === 'object') {
      // CommonJS
    module.exports = factory(require('myModule'), require('myOtherModule'));
  } else {
    // Browser globals (Note: root is window)
    root.returnExports = factory(root.myModule, root.myOtherModule);
  }
}(this, function (myModule, myOtherModule) {
  // Methods
  function notHelloOrGoodbye(){}; // A private method
  function hello(){}; // A public method because it's returned (see below)
  function goodbye(){}; // A public method because it's returned (see below)

  // Exposed public methods
  return {
      hello: hello,
      goodbye: goodbye
  }
}));
  • 在執行UMD規範時,會優先判斷是當前環境是否支持AMD環境,而後再檢驗是否支持CommonJS環境,不然認爲當前環境爲瀏覽器環境(window)。
  1. ES6模塊(原生JS)
    特色:
  • JS原生支持的
  • 簡潔語法而且支持異步加載
  • import 進來的模塊對於調用它的模塊來是說是實時只讀的,而CommonJS只是至關於把代碼複製過來
//CommonJS
// lib/counter.js

var counter = 1;

function increment() {
  counter++;
}

function decrement() {
  counter--;
}

module.exports = {
  counter: counter,
  increment: increment,
  decrement: decrement
};


// src/main.js

var counter = require('../../lib/counter');

counter.increment();
console.log(counter.counter); // 1
//import
// lib/counter.js
export let counter = 1;

export function increment() {
  counter++;
}

export function decrement() {
  counter--;
}


// src/main.js
import * as counter from '../../counter';

console.log(counter.counter); // 1
counter.increment();
console.log(counter.counter); // 2

模塊化與組件化

參考資料:

JavaScript模塊化編程簡史(2009-2016)
JavaScript 模塊化入門Ⅰ:理解模塊
JavaScript Modules: A Beginner’s Guide
深刻理解JavaScript系列(3):全面解析Module模式

相關文章
相關標籤/搜索