【基礎知識】前端模塊化

開發者能夠只須要實現業務邏輯,其餘可加載別人寫好的公共模塊。javascript

1、script引入文件

<script src="jquery.js"></script>
<script src="jquery_scroller.js"></script>
<script src="bootstarp.js"></script>
<script src="commutil.js"></script>
<script src="main.js"></script>
複製代碼

①最原始的寫法

/** commutil.js(main.js)中: 模塊就是實現特定功能的一組方法。 只要把不一樣的函數簡單地放在一塊兒,就算是一個模塊。 */
function fun1(){
    ...
}
function fun2(){
    ...
}
...
複製代碼

缺點:前端

  1. 全部的模塊都處於全局做用域下, 容易形成命名衝突
  2. 依賴關係不明顯, 好比 main.js 中有使用 jquery, 那麼 jquery 就必定要先加載,
  3. 可是從引入方式中咱們沒法直觀的察覺依賴關係, 不利於維護

②將上述的多個方法寫在一個對象中

var obj = new Object({
     _count : 0,
     fun1 : function (){
        ...
     },
     fun2 : function (){
        ...
     }
 });
複製代碼
//調用方法
obj.fun1()
obj.fun2()
複製代碼

缺點:java

  1. 這樣的寫法會暴露全部模塊成員,內部狀態能夠被外部改寫。
  2. 好比,外部代碼能夠直接改變內部計數器的值。
obj._count = 1;
複製代碼

③當即執行函數寫法

使用」當即執行函數」(Immediately-Invoked Function Expression,IIFE),能夠達到不暴露私有成員的目的。node

var obj = (function() {
    var _count = 0;
    var fun1 = function() {
        alert(_count)
    }
    var fun2 = function() {
        alert(_count + 1)
    }   
    return {
        fun1, fun2
    }
})()
複製代碼

使用上面的寫法,外部代碼沒法讀取內部的_count變量。jquery

console.info(obj._count);  //undefined
//調用內部函數能夠
obj.fun1()
複製代碼
  • jQuery用的就是當即執行函數。
  • 當即執行函數經常使用於第三方庫,好處在於隔離做用域。
  • 任何一個第三方庫都會存在大量的變量和函數,爲了不變量污染(命名衝突),【各類模塊化概念出來以前】開發者們想到的解決辦法就是使用當即執行函數。

2、require導入模塊【運行時加載】

隨着web項目愈來愈大,JS的代碼量也與日俱增,因而社區就自發約定了幾種模塊化的方案:requirejs遵循AMDseajs遵循CMDnodemodule遵循CommonJS規範,雖然寫法上有所不一樣,都是爲了可以間接實現模塊化的基礎上保持較爲一致的代碼風格。程序員

  • 2009年,美國程序員Ryan Dahl 創造了node.js項目,將javascript語言用於服務器端編程。 這標誌「Javascript模塊化編程」正式誕生。
  • 前端的複雜程度有限,沒有模塊也能夠,但服務器端,須要與操做系統和其餘應用程序互動,沒有有模塊,根本無法編程。
  • node編程最重要的思想之一就是模塊,正是這個思想,讓JavaScript的大規模工程成爲可能。
  • 模塊化編程在js界流行,也是基於此,隨後在瀏覽器端,requirejsseajs之類的工具包也出現了。
  • 能夠說在對應規範下,require統治了ES6以前的全部模塊化編程,即便如今,在ES6 module被徹底實現以前,仍是這樣。

①CommonJS

一個文件就是一個模塊, 其內部定義的變量, 方法都處於該模塊內, 不會對外暴露.es6

//新建 a.js, 導出sayHello和introSelf
// a.js
function sayHello(name) {
    console.log(`Hello ${name}`)
}
function introSelf(name, age){
    var msg = "myName is " + name + " and I'm " + age + " old";
    console.log(msg);
}
module.exports.sayHello = sayHello
module.exports.introSelf = introSelf
/**或 module.exports = { sayHello: sayHello, introSelf: introSelf } */

//在 b.js 中引入 a 並調用
// b.js
const a = require('./a')
a.sayHello('Lucy') 
a.introSelf('Lucy', 20) 
複製代碼
  • 因爲 CommonJs 是同步加載的模塊的, 在服務端(node), 文件都在硬盤上, 因此同步加載也無所謂, 可是在瀏覽器端, 同步加載就體驗很差了. 因此 CommonJs 主要使用於 node 環境下.
  • 可是,對於瀏覽器,這倒是個大問題,由於模塊都放在服務器端,等待時間取決於網速的快慢,若是等待時間過長,瀏覽器會處於「假死」狀態。
  • 所以,瀏覽器端的模塊,不能採用」同步加載」(synchronous),只能採用」異步加載」(asynchronous)。這就是AMD規範誕生的背景。

②AMD 和 CMD

  1. AMDAsynchronous Module Definition的縮寫,意思就是」異步模塊定義」。它採用異步方式加載模塊,模塊的加載不影響它後面語句的運行。全部依賴這個模塊的語句,都定義在一個回調函數中,等到加載完成以後,這個回調函數纔會運行。
  2. CMD (Common Module Definition),阿里的玉伯提出, 是seajs推崇的規範,CMD則是依賴就近,用的時候再require(實現了按需加載)。 模塊必須採用特定的define()函數來定義。
  3. AMDCMD模塊必須採用特定的define()函數來定義。
define(id?, dependencies?, factory)
    /** - id:字符串,模塊名稱(可選) - dependencies: 是咱們要載入的依賴模塊(可選), 使用相對路徑。,注意是數組格式 - factory: 工廠方法,返回一個模塊函數 */
複製代碼

若是一個模塊不依賴其餘模塊,那麼能夠直接定義在define()函數之中。web

  1. 對於依賴的模塊 AMD 推崇依賴前置(js能夠方便知道依賴模塊是誰,當即加載),而 CMD 推崇依賴就近(須要使用把模塊變爲字符串解析一遍才知道依賴了那些模塊,這也是不少人詬病CMD的一點)
//AMD2.0以前
 require(['./a', './b'], function(a, b) {
 a.doSomething();
 b.doSomething();
 })
 // AMD2.0以後
 define(['./a', './b'], function(a, b) {
 a.doSomething();
 b.doSomething();
 })
 
 // CMD
 define(function(require, exports, module) {
 var a = require('./a');
 a.doSomething();
 var b = require('./b');
 b.doSomething();
 })
複製代碼
  1. 調用模塊
require([module], callback);
複製代碼
//若有一個math.js
require(['math'], function (math) {
  math.add(2, 3);
});
複製代碼

3、ES6 MODULES【編譯時加載】

隨着ES2015的發佈,官方標準定義了一種模塊化的方案,那就是importexport。但是,標準畢竟是標準,各大瀏覽器和node終端要實現標準仍是有一段距離的。編程

  1. 阮一峯ES6-export命令
  • 模塊功能主要由兩個命令構成:exportimportexport命令用於規定模塊的對外接口,import命令用於輸入其餘模塊提供的功能。
  • 一個模塊就是一個獨立的文件。該文件內部的全部變量,外部沒法獲取。若是你但願外部可以讀取模塊內部的某個變量,就必須使用export關鍵字輸出該變量。下面是一個 JS 文件,裏面使用export命令輸出變量。
// profile.js
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;
複製代碼
  • 上面代碼是profile.js文件,保存了用戶信息。ES6 將其視爲一個模塊,裏面用export命令對外部輸出了三個變量。
  • export的寫法,除了像上面這樣,還有另一種。
// profile.js
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
export { firstName, lastName, year };
複製代碼
  • 導入模塊
import { firstName, lastName, year } from './profile.js';
//也能夠用as
import { lastName as surname } from './profile.js';
複製代碼
  • 上面代碼在export命令後面,使用大括號指定所要輸出的一組變量。它與前一種寫法(直接放置在var語句前)是等價的,可是應該優先考慮使用這種寫法。由於這樣就能夠在腳本尾部,一眼看清楚輸出了哪些變量。
  • export命令除了輸出變量,還能夠輸出函數或類(class)。
export function multiply(x, y) {
  return x * y;
};
複製代碼
  • 上面代碼對外輸出一個函數multiply
  • 一般狀況下,export輸出的變量就是原本的名字,可是可使用as關鍵字重命名。
function v1() { ... }
function v2() { ... }
export {
  v1 as streamV1,
  v2 as streamV2,
  v2 as streamLatestVersion
};
複製代碼
  • 上面代碼使用as關鍵字,重命名了函數v1v2的對外接口。重命名後,v2能夠用不一樣的名字輸出兩次。
  • 須要特別注意的是,export命令規定的是對外的接口,必須與模塊內部的變量創建一一對應關係。
// 報錯
export 1;

// 報錯
var m = 1;
export m;
複製代碼
  • 上面兩種寫法都會報錯,由於沒有提供對外的接口。第一種寫法直接輸出 1,第二種寫法經過變量m,仍是直接輸出 1。1只是一個值,不是接口。正確的寫法是下面這樣。
// 寫法一
export var m = 1;

// 寫法二
var m = 1;
export {m};

// 寫法三
var n = 1;
export {n as m};
複製代碼
  • 上面三種寫法都是正確的,規定了對外的接口m。其餘腳本能夠經過這個接口,取到值1。它們的實質是,在接口名與模塊內部變量之間,創建了一一對應的關係。
  • 一樣的,functionclass的輸出,也必須遵照這樣的寫法。
// 報錯
function f() {}
export f;

// 正確
export function f() {};

// 正確
function f() {}
export {f};
複製代碼
  1. 阮一峯ES6-export default命令
  • 從前面的例子能夠看出,使用import命令的時候,用戶須要知道所要加載的變量名或函數名,不然沒法加載。可是,用戶確定但願快速上手,未必願意閱讀文檔,去了解模塊有哪些屬性和方法。
  • 爲了給用戶提供方便,讓他們不用閱讀文檔就能加載模塊,就要用到export default命令,爲模塊指定默認輸出。
// export-default.js
export default function () {
  console.log('foo');
}
複製代碼
  • 上面代碼是一個模塊文件export-default.js,它的默認輸出是一個函數。
  • 其餘模塊加載該模塊時,import命令能夠爲該匿名函數指定任意名字。
// import-default.js
import customName from './export-default';
customName(); // 'foo'
複製代碼
  • 上面代碼的import命令,能夠用任意名稱指向export-default.js輸出的方法,這時就不須要知道原模塊輸出的函數名。須要注意的是,這時import命令後面,不使用大括號。
  • export default命令用在非匿名函數前,也是能夠的。
// export-default.js
export default function foo() {
  console.log('foo');
}

// 或者寫成

function foo() {
  console.log('foo');
}

export default foo;
複製代碼
  • 上面代碼中,foo函數的函數名foo,在模塊外部是無效的。加載的時候,視同匿名函數加載。
相關文章
相關標籤/搜索