聊聊前端模塊化開發

隨着JavaScript開發變得愈來愈廣泛,命名空間和依賴性變得愈來愈難以處理。前端開發者都以模塊化的方式處理該問題。在這篇文章中,咱們將探討前端開發人員目前使用的模塊化方案以及試圖解決的問題。javascript

爲何須要JavaScript模塊?

模塊化可使你的代碼低耦合,功能模塊直接不相互影響。html

  1. 可維護性:每一個模塊都是單獨定義的,之間相互獨立。模塊儘量的須要和外部撇清關係,方便咱們獨立的對其進行維護與改造。維護一個模塊比在全局中修改邏輯判斷要好的多。
  2. 命名空間:爲了不在JavaScript中的全局污染,咱們經過模塊化的方式利用函數做用域來構建命名空間。
  3. 可複用性:雖然粘貼複製很簡單,可是考慮到咱們以後的維護以及迭代,你會至關崩潰。

模塊化的解決方案有哪些?

講完了JavaScript模塊化的好處,咱們來看下有哪些解決方案來實現JavaScript的模塊化。前端

揭示模塊模式(Revealing Module)

var myRevealingModule = (function () {
    var privateVar = "Ben Cherry",
        publicVar = "Hey there!";
        
    function privateFunction() {
        console.log( "Name:" + privateVar );
    }
    function publicSetName( strName ) {
        privateVar = strName;
    }
    function publicGetName() {
        privateFunction();
    }
    // Reveal public pointers to
    // private functions and properties
    return {
        setName: publicSetName,
        greeting: publicVar,
        getName: publicGetName
    };
})();
myRevealingModule.setName( "Paul Kinlan" );

經過這種構造,咱們經過使用函數有了本身的做用域或「閉包」。java

這種方法的好處在於,你能夠在函數內部使用局部變量,而不會意外覆蓋同名全局變量,但仍然可以訪問到全局變量。node

優勢:es6

  • 能夠在任何地方實現(沒有庫,不須要語言支持)。
  • 能夠在單個文件中定義多個模塊。

缺點:編程

  • 沒法以編程方式導入模塊(除非使用eval)。
  • 須要手動處理依賴關係。
  • 沒法異步加載模塊。
  • 循環依賴可能很麻煩。
  • 很難經過靜態代碼分析器進行分析。

CommonJS

CommonJS是一個旨在定義一系列規範的項目,以幫助開發服務器端JavaScript應用程序。CommonJS團隊試圖解決的一個領域就是模塊。Node.js開發人員最初打算遵循CommonJS規範,但後來決定反對它。segmentfault

在 CommonJS 的規範中,每一個 JavaScript 文件就是一個獨立的模塊上下文(module context),在這個上下文中默認建立的屬性都是私有的。也就是說,在一個文件定義的變量(還包括函數和類),都是私有的,對其餘文件是不可見的。瀏覽器

須要注意的一點是,CommonJS以服務器優先的方式來同步載入模塊,假使咱們引入三個模塊的話,他們會一個個地被載入。服務器

// In circle.js
const PI = Math.PI;
exports.area = (r) => PI * r * r;
exports.circumference = (r) => 2 * PI * r;
// In some file
const circle = require('./circle.js');
console.log( `The area of a circle of radius 4 is ${circle.area(4)}`);

Node.js的模塊系統經過library的方式對CommonJS的基礎上進行了模塊化實現。

在Node和CommonJS的模塊中,基本上有兩個與模塊系統交互的關鍵字:require和exports。

require是一個函數,可用於將接口從另外一個模塊導入當前範圍。傳遞給的參數require是模塊的id。在Node的實現中,它是node_modules目錄中模塊的名稱(或者,若是它不在該目錄中,則是它的路徑)。

exports是一個特殊的對象:放入它的任何東西都將做爲公共元素導出。

Node和CommonJS之間的一個獨特區別在於module.exports對象的形式。

在Node中,module.exports是導出的真正特殊對象,而exports它只是默認綁定到的變量module.exports。

另外一方面,CommonJS沒有任何module.exports對象。實際意義是,在Node中,沒法經過如下方式導出徹底預構造的對象module.exports:

// This won't work, replacing exports entirely breaks the binding to
// modules.exports.
exports = (width) => {
    return {
        area: () => width * width
    };
}
// This works as expected.
module.exports = (width) => {
    return {
        area: () => width * width
    };
}

優勢

  • 簡單:開發人員能夠在不查看文檔的狀況下掌握概念。
  • 集成了依賴管理:模塊須要其餘模塊並按所需順序加載。
  • require能夠在任何地方調用:模塊能夠經過編程方式加載。
  • 支持循環依賴。

缺點

  • 同步API使其不適合某些用途(客戶端)。
  • 每一個模塊一個文件。
  • 瀏覽器須要加載程序庫或轉換。
  • 模塊沒有構造函數(Node支持)。
  • 很難進行靜態代碼分析。

AMD

AMD誕生於一羣對CommonJS的研究方向不滿的開發人員。事實上,AMD在開發早期就與CommonJS分道揚鑣,AMD和CommonJS之間的主要區別在於它支持異步模塊加載。

//Calling define with a dependency array and a factory function
define(['dep1', 'dep2'], function (dep1, dep2) {
    //Define the module value by returning a value.
    return function () {};
});
// Or:
define(function (require) {
    var dep1 = require('dep1'),
    dep2 = require('dep2');
    return function () {};
});

經過使用JavaScript的傳統閉包來實現異步加載:

在請求的模塊加載完成時調用函數。模塊定義和導入模塊由同一個函數承載:定義模塊時,其依賴關係是明確的。所以,AMD加載器能夠在運行時具備項目的模塊依賴圖。所以能夠同時加載彼此不依賴的庫。這對於瀏覽器尤爲重要,由於啓動時間對於良好的用戶體驗相當重要。

優勢

  • 異步加載(更好的啓動時間)。
  • 支持循環依賴。
  • require和的兼容性exports。
  • 徹底整合了依賴管理。
  • 若有必要,能夠將模塊拆分爲多個文件。
  • 支持構造函數。
  • 插件支持(自定義加載步驟)。

缺點

  • 語法稍微複雜一些。
  • 除非編譯,不然須要加載程序庫。
  • 很難分析靜態代碼。

除了異步加載之外,AMD的另外一個優勢是你能夠在模塊裏使用對象、函數、構造函數、字符串、JSON或者別的數據類型,而CommonJS只支持對象。

UMD

統一模塊定義(UMD:Universal Module Definition )就是將 AMD 和 CommonJS 合在一塊兒的一種嘗試,常見的作法是將CommonJS 語法包裹在兼容 AMD 的代碼中。

(function(define) {
    define(function () {
        return {
            sayHello: function () {
                console.log('hello');
            }
        };
    });
}(
    typeof module === 'object' && module.exports && typeof define !== 'function' ?
    function (factory) { module.exports = factory(); } :
    define
));

該模式的核心思想在於所謂的 IIFE(Immediately Invoked Function Expression),該函數會根據環境來判斷須要的參數類別

ES6模塊

支持JavaScript標準化的ECMA團隊決定解決模塊問題, 兼容同步和異步操做模式。

//------ lib.js ------
export const sqrt = Math.sqrt;
export function square(x) {
    return x * x;
}
export function diag(x, y) {
    return sqrt(square(x) + square(y));
}
//------ main.js ------
import { square, diag } from 'lib';
console.log(square(11)); // 121
console.log(diag(4, 3)); // 5

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

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

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

ES6 的模塊自動採用嚴格模式,無論有沒有在模塊頭部加上"use strict";。 

嚴格模式主要有如下限制。

變量必須聲明後再使用 函數的參數不能有同名屬性,不然報錯 不能使用with語句 不能對只讀屬性賦值,不然報錯 不能使用前綴 0 表示八進制數,不然報錯 不能刪除不可刪除的屬性,不然報錯 不能刪除變量delete prop,會報錯,只能刪除屬性delete global[prop] eval不會在它的外層做用域引入變量 eval和arguments不能被從新賦值 arguments不會自動反映函數參數的變化 不能使用arguments.callee 不能使用arguments.caller 禁止this指向全局對象 不能使用fn.caller和fn.arguments獲取函數調用的堆棧 增長了保留字(好比protected、static和interface)

其中,尤爲須要注意this的限制。ES6 模塊之中,頂層的this指向undefined,即不該該在頂層代碼使用this。

export

export語法被用來建立JavaScript模塊。你能夠用它來導出對象(包括函數)和原始值(primitive values)。導出有兩種類型:named和default。

// named
// lib.js
export function sum(a, b) {
    return a + b;
}
export function substract(a, b) {
    return a - b;
}
function divide(a, b) {
    return a / b;
}
export { divide };
// default
// dog.js
export default class Dog {
    bark() {
    console.log('bark!');
    }
}

import

import語句用來導入其餘模塊。

整個導入

// index.js 
import * as lib from './lib.js'; console.log(lib.sum(1,2)); console.log(lib.substract(3,1)); console.log(lib.divide(6,3));

導入一個或多個named導出

// index.js
import { sum, substract, divide } from './lib';
console.log(sum(1,2));
console.log(substract(3,1));
console.log(divide(6,3));

須要注意,相應的導入導出名字必須匹配。

導入一個default導出

// index.js 
import Dog from './dog.js'; 
const dog = new Dog(); 
dog.bark(); // 'bark!'

注意,defualt導出在導入時,能夠用任意的名字。因此咱們能夠這樣作:

import Cat from './dog.js';

const dog = new Cat();
dog.bark(); // 'bark!'

參考

  1. 很全很全的JavaScript的模塊講解 http://www.javashuo.com/article/p-phjafnrd-h.html
  2. JavaScript Module Systems Showdown: CommonJS vs AMD vs ES2015 https://auth0.com/blog/javascript-module-systems-showdown/
  3. ECMAScript 6 入門http://es6.ruanyifeng.com/#docs/module

原文出處:https://www.cnblogs.com/jingh/p/10915466.html

相關文章
相關標籤/搜索