自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

談?wù)勄岸四K化的演變歷程

開(kāi)發(fā) 前端
Node.js 使用的是 CommonJS 模塊規(guī)范,它也是支持 ES 模塊的。在 Node.js 13 之前,ES 模塊是一項(xiàng)實(shí)驗(yàn)性技術(shù),因此,可以通過(guò)使用 .mjs 擴(kuò)展名保存模塊并通過(guò)標(biāo)志訪問(wèn)它來(lái)使用模塊。


隨著前端項(xiàng)目越來(lái)越大,代碼復(fù)雜性不斷增加,對(duì)于模塊化的需求越來(lái)越大。模塊化是工程化基礎(chǔ),只有將代碼模塊化,拆分為合理單元,才具備調(diào)度整合的能力。下面就來(lái)看看模塊化的概念,以及不同模塊化方案的使用方式和優(yōu)缺點(diǎn)。

1、模塊概述

由于代碼之間會(huì)發(fā)生大量交互,如果結(jié)構(gòu)不合理,這些代碼就會(huì)變得難以維護(hù)、難以測(cè)試、難以調(diào)試。而使用模塊化就解決了這些問(wèn)題,模塊化的特點(diǎn)如下:

可重用性: 當(dāng)應(yīng)用被組織成模塊時(shí),可以方便的在其他地方重用這些模塊,避免編寫(xiě)重復(fù)代碼,從而加快開(kāi)發(fā)流程;

可讀性: 當(dāng)應(yīng)用變得越來(lái)越復(fù)雜時(shí),如果在一個(gè)文件中編寫(xiě)所有功能,代碼會(huì)變得難以閱讀。如果使用模塊設(shè)計(jì)應(yīng)用,每個(gè)功能都分布在各自的模塊中,代碼就會(huì)更加清晰、易讀;

可維護(hù)性: 軟件的美妙之處在于進(jìn)化,從長(zhǎng)遠(yuǎn)來(lái)看,我們需要不斷為應(yīng)用增加新的功能。當(dāng)應(yīng)用被結(jié)構(gòu)化為模塊時(shí),可以輕松添加或刪除功能。除此之外,修復(fù)錯(cuò)誤也是軟件維護(hù)的一部分,使用模塊就可以更快速地定位問(wèn)題。

模塊化是一種將系統(tǒng)分離成獨(dú)立功能部分的方法,可以將系統(tǒng)分割成獨(dú)立的功能部分,嚴(yán)格定義模塊接口,模塊間具有透明性。通過(guò)將代碼進(jìn)行模塊化分隔,每個(gè)文件彼此獨(dú)立,開(kāi)發(fā)者更容易開(kāi)發(fā)和維護(hù)代碼,模塊之間又能夠互相調(diào)用和通信,這就是現(xiàn)代化開(kāi)發(fā)的基本模式。

(2)模式

JavaScript 模塊包含三個(gè)部分:

  • 導(dǎo)入: 在使用模塊時(shí),需要將所需模塊作為依賴(lài)項(xiàng)導(dǎo)入。例如,如果想要?jiǎng)?chuàng)建一個(gè) React 組件,就需導(dǎo)入 react 模塊。要使用像 Lodash 這樣的工具庫(kù),就需要安裝并導(dǎo)入它作為依賴(lài)項(xiàng);
  • 代碼: 模塊具體代碼;
  • 導(dǎo)出: 模塊接口,從模塊中導(dǎo)出的內(nèi)容可供導(dǎo)入模塊的任何地方使用。

(3)類(lèi)型

模塊化的貫徹執(zhí)行離不開(kāi)相應(yīng)的約定,即規(guī)范。這是能夠進(jìn)行模塊化工作的重中之重。實(shí)現(xiàn)模塊化的規(guī)范有很多,比如:AMD、RequireJS、CMD、SeaJS、UMD、CommonJS、ES6 Module。除此之外,IIFE(立即執(zhí)行函數(shù))也是實(shí)現(xiàn)模塊化的一種方案。

本文將介紹其中的六個(gè):

  • IIFE: 立即調(diào)用函數(shù)表達(dá)式
  • AMD: 異步模塊加載機(jī)制
  • CMD: 通用模塊定義
  • UMD: 統(tǒng)一模塊定義
  • CommonJS: Node.js 采用該規(guī)范
  • ES 模塊: JavaScript 內(nèi)置模塊系統(tǒng)

2. IIFE

在 ECMAScript 6 之前,模塊并沒(méi)有被內(nèi)置到 JavaScript 中,因?yàn)?JavaScript 最初是為小型瀏覽器腳本設(shè)計(jì)的。這種模塊化的缺乏,導(dǎo)致在代碼的不同部分使用了共享全局變量。

比如,對(duì)于以下代碼:

var name = 'JavaScript';
var age = 20;

當(dāng)上面的代碼運(yùn)行時(shí),name 和 age 變量會(huì)被添加到全局對(duì)象中。因此,應(yīng)用中的所有 JavaScript 腳本都可以訪問(wèn)全局變量 name 和 age,這就很容易導(dǎo)致代碼錯(cuò)誤,因?yàn)樵谄渌幌嚓P(guān)的單元中也可以訪問(wèn)和修改這些全局變量。除此之外,向全局對(duì)象添加變量會(huì)使全局命名空間變得混亂并增加了命名沖突的機(jī)會(huì)。

所以,我們就需要一種封裝變量和函數(shù)的方法,并且只對(duì)外公開(kāi)定義的接口。因此,為了實(shí)現(xiàn)模塊化并避免使用全局變量,可以使用如下方式來(lái)創(chuàng)建模塊:

(function () {
    // 聲明私有變量和函數(shù)
 
    return {
        // 聲明公共變量和函數(shù)
    }
})();

上面的代碼就是一個(gè)返回對(duì)象的閉包,這就是我們常說(shuō)的IIFE(Immediately Invoked Function Expression),即立即調(diào)用函數(shù)表達(dá)式。在該函數(shù)中,就創(chuàng)建了一個(gè)局部范圍。這樣就避免了使用全局變量(IIFE 是匿名函數(shù)),并且代碼單元被封裝和隔離。

可以這樣來(lái)使用 IIFE 作為一個(gè)模塊:

var module = (function(){
  var age = 20;
  var name = 'JavaScript'
  
  var fn1 = function(){
    console.log(name, age)
  };
  
  var fn2 = function(a, b){
    console.log(a + b)
  };
  
  return {
    age,
    fn1,
    fn2,
  };
})();

module.age;           // 20
module.fn1();         // JavaScript 20
module.fn2(128, 64);  // 192

在這段代碼中,module 就是我們定義的一個(gè)模塊,它里面定義了兩個(gè)私有變量 age 和 name,同時(shí)定義了兩個(gè)方法 fn1 和 fn2,其中 fn1 中使用 module 中定義的私有變量,fn2 接收外部傳入?yún)?shù)。最后,module 向外部暴露了age、fn1、fn2。這樣就形成了一個(gè)模塊。

當(dāng)試圖在 module 外部直接調(diào)用fn1時(shí),就會(huì)報(bào)錯(cuò):

fn1(); // Uncaught ReferenceError: fn1 is not defined

當(dāng)試圖在 module 外部打印其內(nèi)部的私有變量name時(shí),得到的結(jié)果是 undefined:

module.name; // undefined

上面的 IIFE 的例子是遵循模塊模式的,具備其中的三部分,其中 age、name、fn1、fn2 就是模塊內(nèi)部的代碼實(shí)現(xiàn),返回的 age、fn1、fn2 就是導(dǎo)出的內(nèi)容,即接口。調(diào)用 module 方法和變量就是導(dǎo)入使用。

3. CommonJS

(1)概念

① 定義

CommonJS 是社區(qū)提出的一種 JavaScript 模塊化規(guī)范,它是為瀏覽器之外的 JavaScript 運(yùn)行環(huán)境提供的模塊規(guī)范,Node.js 就采用了這個(gè)規(guī)范。

注意:

  • 瀏覽器不支持使用 CommonJS 規(guī)范;
  • Node.js 不僅支持使用 CommonJS 來(lái)實(shí)現(xiàn)模塊,還支持最新的  ES 模塊。

CommonJS 規(guī)范加載模塊是同步的,只有加載完成才能繼續(xù)執(zhí)行后面的操作。不過(guò)由于 Node.js 主要運(yùn)行在服務(wù)端,而所需加載的模塊文件一般保存在本地硬盤(pán),所以加載比較快,而無(wú)需考慮使用異步的方式。

② 語(yǔ)法

CommonJS 規(guī)范規(guī)定每個(gè)文件就是一個(gè)模塊,有獨(dú)立的作用域,對(duì)于其他模塊不可見(jiàn),這樣就不會(huì)污染全局作用域。在 CommonJS 中,可以分別使用 export 和 require 來(lái)導(dǎo)出和導(dǎo)入模塊。在每個(gè)模塊內(nèi)部,都有一個(gè) module 對(duì)象,表示當(dāng)前模塊。通過(guò)它來(lái)導(dǎo)出 API,它有以下屬性:

  • exports:模塊導(dǎo)出值。
  • filename:模塊文件名,使用絕對(duì)路徑;
  • id:模塊識(shí)別符,通常是使用絕對(duì)路徑的模塊文件名;
  • loaded:布爾值,表示模塊是否已經(jīng)完成加載;
  • parent:對(duì)象,表示調(diào)用該模塊的模塊;
  • children:數(shù)組,表示該模塊要用到的其他模塊;

③ 特點(diǎn)

CommonJS 規(guī)范具有以下特點(diǎn):

  • 文件即模塊,文件內(nèi)所有代碼都運(yùn)行在獨(dú)立的作用域,因此不會(huì)污染全局空間;
  • 模塊可以被多次引用、加載。第一次被加載時(shí),會(huì)被緩存,之后都從緩存中直接讀取結(jié)果。
  • 加載某個(gè)模塊,就是引入該模塊的 module.exports 屬性,該屬性輸出的是值拷貝,一旦這個(gè)值被輸出,模塊內(nèi)再發(fā)生變化不會(huì)影響到輸出的值。
  • 模塊加載順序按照代碼引入的順序。

④ 優(yōu)缺點(diǎn)

CommonJS 的優(yōu)點(diǎn):

  • 使用簡(jiǎn)單
  • 很多工具系統(tǒng)和包都是使用 CommonJS 構(gòu)建的;
  • 在 Node.js 中使用,Node.js 是流行的 JavaScript 運(yùn)行時(shí)環(huán)境。

CommonJS 的缺點(diǎn)

  • 可以在 JavaScript 文件中包含一個(gè)模塊;
  • 如果想在 Web 瀏覽器中使用它,則需要額外的工具;
  • 本質(zhì)上是同步的,在某些情況下不適合在 Web 瀏覽器中使用。

(2)使用

在 CommonJS 中,可以通過(guò) require 函數(shù)來(lái)導(dǎo)入模塊,它會(huì)讀取、執(zhí)行 JavaScript 文件,并返回該模塊的 exports 對(duì)象,該對(duì)象只有在模塊腳本運(yùn)行完才會(huì)生成。

① 模塊導(dǎo)出

可以通過(guò)以下兩種方式來(lái)導(dǎo)出模塊內(nèi)容:

module.exports.TestModule = function() {
    console.log('exports');
}

exports.TestModule = function() {
    console.log('exports');
}

則合兩種方式的導(dǎo)出結(jié)果是一樣的,module.exports和exports的區(qū)別可以理解為:exports是module.exports的引用,如果在exports調(diào)用之前調(diào)用了exports=...,那么就無(wú)法再通過(guò)exports來(lái)導(dǎo)出模塊內(nèi)容,除非通過(guò)exports=module.exports重新設(shè)置exports的引用指向。

當(dāng)然,可以先定義函數(shù),再導(dǎo)出:

function testModule() {
    console.log('exports');
}

module.exports = testModule;

這是僅導(dǎo)出一個(gè)函數(shù)的情況,使用時(shí)就是這樣的:

testModule = require('./MyModule');

testModule();

如果是導(dǎo)出多個(gè)函數(shù),就可以這樣:

function testModule1() {
    console.log('exports1');
}

function testModule2() {
    console.log('exports2');
}

導(dǎo)入多個(gè)函數(shù)并使用:

({testModule1, testModule2} = require('./MyModule'));

testModule1();
testModule2();

② 模塊導(dǎo)入

可以通過(guò)以下方式來(lái)導(dǎo)入模塊:

const module = require('./MyModule');

注意,如果 require 的路徑?jīng)]有后綴,會(huì)自動(dòng)按照.js、.json和.node的順序進(jìn)行補(bǔ)齊查找。

③ 加載過(guò)程

在  CommonJS 中,require 的加載過(guò)程如下:

  1. 優(yōu)先從緩存中加載;
  2. 如果緩存中沒(méi)有,檢查是否是核心模塊,如果是直接加載;
  3. 如果不是核心模塊,檢查是否是文件模塊,解析路徑,根據(jù)解析出的路徑定位文件,然后執(zhí)行并加載;
  4. 如果以上都不是,沿當(dāng)前路徑向上逐級(jí)遞歸,直到根目錄的node_modules目錄。

(3)示例

下面來(lái)看一個(gè)購(gòu)物車(chē)的例子,主要功能是將商品添加到購(gòu)物車(chē),并計(jì)算購(gòu)物車(chē)商品總價(jià)格:

// cart.js

var items = [];

function addItem (name, price) 
    item.push({
    name: name,
    price: price
  });
}

exports.total = function () {
    return items.reduce(function (a, b) {
      return a + b.price;
    }, 0);
};

exports.addItem = addItem;

這里通過(guò)兩種方式在 exports 對(duì)象上定義了兩個(gè)方法:addItem 和 total,分別用來(lái)添加購(gòu)物車(chē)和計(jì)算總價(jià)。

下面在控制臺(tái)測(cè)試一下上面定義的模塊:

let cart = require('./cart');

這里使用相對(duì)路徑來(lái)導(dǎo)入 cart 模塊,打印 cart 模塊,結(jié)果如下:

cart // { total: [Function], addItem: [Function: addItem] }

向購(gòu)物車(chē)添加一些商品,并計(jì)算當(dāng)前購(gòu)物車(chē)商品的總價(jià)格:

cart.addItem('book', 60);
cart.total()  // 60

cart.addItem('pen', 6);
cart.total()  // 66

這就是創(chuàng)建模塊的基本方法,我們可以創(chuàng)建一些方法,并且只公開(kāi)希望其他文件使用的部分代碼。該部分成為 API,即應(yīng)用程序接口。

這里有一個(gè)問(wèn)題,只有一個(gè)購(gòu)物車(chē),即只有一個(gè)模塊實(shí)例。下面來(lái)在控制臺(tái)執(zhí)行以下代碼:

second_cart = require('./cart');

那這時(shí)會(huì)創(chuàng)建一個(gè)新的購(gòu)物車(chē)嗎?事實(shí)并非如此,打印當(dāng)前購(gòu)物車(chē)的商品總金額,它仍然是66:

second_cart.total();  // 66

當(dāng)我們?創(chuàng)建多個(gè)實(shí)例時(shí),就需要再模塊內(nèi)創(chuàng)建一個(gè)構(gòu)造函數(shù),下面來(lái)重寫(xiě) cart.js 文件:

// cart.js

function Cart () {
    this.items = [];
}

Cart.prototype.addItem = function (name, price) {
    this.items.push({
        name: name,
        price: price
    });
}

Cart.prototype.total = function () {
    return this.items.reduce(function(a, b) {
        return a + b.price;
    }, 0);
};

module.export = Cart;

現(xiàn)在,當(dāng)需要使用此模塊時(shí),返回的是 Cart 構(gòu)造函數(shù),而不是具有 cart 函數(shù)作為一個(gè)屬性的對(duì)象。下面來(lái)導(dǎo)入這個(gè)模塊,并創(chuàng)建兩個(gè)購(gòu)物車(chē)實(shí)例:

Cart = require('./second_cart');

cart1 = new Cart();
cart2 = new Cart();

cart1.addItem('book', 50);
cart1.total();   // 50
cart2.total();   // 50

4. AMD

(1)概念

CommonJS 的缺點(diǎn)之一是它是同步的,AMD 旨在通過(guò)規(guī)范中定義的 API 異步加載模塊及其依賴(lài)項(xiàng)來(lái)解決這個(gè)問(wèn)題。AMD 全稱(chēng)為 Asynchronous Module Definition,即異步模塊加載機(jī)制。它規(guī)定了如何定義模塊,如何對(duì)外輸出,如何引入依賴(lài)。

AMD規(guī)范重要特性就是異步加載。所謂異步加載,就是指同時(shí)并發(fā)加載所依賴(lài)的模塊,當(dāng)所有依賴(lài)模塊都加載完成之后,再執(zhí)行當(dāng)前模塊的回調(diào)函數(shù)。這種加載方式和瀏覽器環(huán)境的性能需求剛好吻合。

① 語(yǔ)法

AMD 規(guī)范定義了一個(gè)全局函數(shù) define,通過(guò)它就可以定義和引用模塊,它有 3 個(gè)參數(shù):

define(id?, dependencies?, factory);

其包含三個(gè)參數(shù):

  • id:可選,指模塊路徑。如果沒(méi)有提供該參數(shù),模塊名稱(chēng)默認(rèn)為模塊加載器請(qǐng)求的指定腳本的路徑。
  • dependencies:可選,指模塊數(shù)組。它定義了所依賴(lài)的模塊。依賴(lài)模塊必須根據(jù)模塊的工廠函數(shù)優(yōu)先級(jí)執(zhí)行,并且執(zhí)行的結(jié)果應(yīng)該按照依賴(lài)數(shù)組中的位置順序以參數(shù)的形式傳入工廠函數(shù)中。
  • factory:為模塊初始化要執(zhí)行的函數(shù)或?qū)ο?。如果是函?shù),那么該函數(shù)是單例模式,只會(huì)被執(zhí)行一次;如果是對(duì)象,此對(duì)象應(yīng)該為模塊的輸出值。

除此之外,要想使用此模塊,就需要使用規(guī)范中定義的 require 函數(shù):

require(dependencies?, callback);

其包含兩個(gè)參數(shù):

  • dependencies:依賴(lài)項(xiàng)數(shù)組;
  • callback:加載模塊時(shí)執(zhí)行的回調(diào)函數(shù)。

有關(guān) AMD API 的更詳細(xì)說(shuō)明,可以查看 GitHub 上的 AMD API 規(guī)范:https://github.com/amdjs/amdjs-api/blob/master/AMD.md。

② 兼容性

該規(guī)范的瀏覽器兼容性如下:

③ 優(yōu)缺點(diǎn)

AMD 的優(yōu)點(diǎn):

  • 異步加載導(dǎo)致更好的啟動(dòng)時(shí)間;
  • 能夠?qū)⒛K拆分為多個(gè)文件;
  • 支持構(gòu)造函數(shù);
  • 無(wú)需額外工具即可在瀏覽器中工作。

AMD 的缺點(diǎn):

  • 語(yǔ)法很復(fù)雜,學(xué)習(xí)成本高;
  • 需要一個(gè)像 RequireJS 這樣的加載器庫(kù)來(lái)使用 AMD。

(2)使用

當(dāng)然,上面只是 AMD 規(guī)范的理論,要想理解這個(gè)理論在代碼中是如何工作的,就需要來(lái)看看 AMD 的實(shí)際實(shí)現(xiàn)。RequireJS 就是 AMD 規(guī)范的一種實(shí)現(xiàn),它被描述為“JavaScript 文件和模塊加載器”。下面就來(lái)看看 RequireJS 是如何使用的。

① 引入RequireJS

可以通過(guò) npm 來(lái)安裝 RequireJS:

npm i requirejs

也可以在 html 文件引入 require.js 文件:

<script data-main="js/config" src="js/require.js"></script>

這里 script標(biāo)簽有兩個(gè)屬性:

  • data-main="js/config":這是 RequireJS 的入口,也是配置它的地方;
  • src="js/require.js":加載腳本的正常方式,會(huì)加載 require.js 文件。

在 script 標(biāo)簽下添加以下代碼來(lái)初始化 RequireJS:

<script>
    require(['config'], function() {
        //...
    })
</script>

當(dāng)頁(yè)面加載完配置文件之后, require() 中的代碼就會(huì)運(yùn)行。這個(gè) script 標(biāo)簽是一個(gè)異步調(diào)用,這意味著當(dāng) RequireJS 通過(guò) src="js/require.js 加載時(shí),它將異步加載 data-main 屬性中指定的配置文件。因此,該標(biāo)簽下的任何 JavaScript 代碼都可以在 RequireJS 獲取時(shí)執(zhí)行配置文件。

那 AMD 中的 require() 和 CommonJS 中的 require() 有什么區(qū)別呢?

  • AMD require() 接受一個(gè)依賴(lài)數(shù)組和一個(gè)回調(diào)函數(shù),CommonJS require() 接受一個(gè)模塊 ID;
  • AMD require() 是異步的,而 CommonJS require() 是同步的。

② 定義 AMD 模塊

下面是 AMD 中的一個(gè)基本模塊定義:

define(['dependency1', 'dependency2'], function() {
  // 模塊內(nèi)容
});

這個(gè)模塊定義清楚地顯示了其包含兩個(gè)依賴(lài)項(xiàng)和一個(gè)函數(shù)。

下面來(lái)定義一個(gè)名為addition.js的文件,其包含一個(gè)執(zhí)行加法操作的函數(shù),但是沒(méi)有依賴(lài)項(xiàng):

// addition.js
define(function() {
    return function(a, b) {
        alert(a + b);
    }
});

再來(lái)定義一個(gè)名為 calculator.js 的文件:

define(['addition'], function(addition) {
    addition(7, 9);
});

當(dāng) RequireJS 看到上面的代碼塊時(shí),它會(huì)去尋找依賴(lài)項(xiàng),并通過(guò)將它們作為參數(shù)傳遞給函數(shù)來(lái)自動(dòng)將其注入到模塊中。

RequireJS 會(huì)自動(dòng)為 addition.js 和 calculator.js 文件創(chuàng)建一個(gè) <script> 標(biāo)簽,并將其放在HTML <head> 元素中,等待它們加載,然后運(yùn)行函數(shù),這類(lèi)似于 require() 的行為。

下面來(lái)更新一下 index.html 文件:

// index.html
require(['config'], function() {
    require(['calculator']);
});

當(dāng)瀏覽器加載 index.html 文件時(shí),RequireJS 會(huì)嘗試查找 calculator.js 模塊,但是沒(méi)有找到,所以瀏覽器也不會(huì)有任何反應(yīng)。那該如何解決這個(gè)問(wèn)題呢?我們必須提供配置文件來(lái)告訴 RequireJS 在哪里可以找到 calculator.js(和其他模塊),因?yàn)樗且玫娜肟凇?/p>

下面是配置文件的基本結(jié)構(gòu):

requirejs.config({
    baseURL: "string",
    paths: {},
    shim: {},
});

這里有三個(gè)屬性值:

  • baseURL:告訴 RequireJS 在哪里可以找到模塊;
  • path:這些是與 define() 一起使用的模塊的名稱(chēng)。 在路徑中,可以使用文件的 CDN,這時(shí) RequireJS 將嘗試在本地可用的模塊之前加載模塊的 CDN 版本;
  • shim:允許加載未編寫(xiě)為 AMD 模塊的庫(kù),并允許以正確的順序加載它們

我們的配置文件如下:

requirejs.config({
    baseURL: "js",
    paths: {
        // 這種情況下,模塊位于 customScripts 文件中
        addition: "customScripts/addition",
        calculator: "customScripts/calculator",
    },
});

配置完成之后,重新加載瀏覽器,就會(huì)收到瀏覽器的彈窗:

這就是在 AMD 中使用 RequireJS 定義模塊的方法之一。我們還可以通過(guò)指定其路徑名來(lái)定義模塊,該路徑名是模塊文件在項(xiàng)目目錄中的位置。 下面給出一個(gè)例子:

define("path/to/module", function() {
    // 模塊內(nèi)容
})

當(dāng)然,RequireJS 并不鼓勵(lì)這種方法,因?yàn)楫?dāng)我們將模塊移動(dòng)到項(xiàng)目中的另一個(gè)位置時(shí),就需要手動(dòng)更改模塊中的路徑名。

在使用 AMD 定義模塊時(shí)需要注意:

  • 在依賴(lài)項(xiàng)數(shù)組中列出的任何內(nèi)容都必須與工廠函數(shù)中的分配相匹配;
  • 盡量不要將異步代碼與同步代碼混用。當(dāng)在 index.html 上編寫(xiě)其他 JavaScript 代碼時(shí)就是這種情況。

5. CMD

CMD 全稱(chēng)為 Common Module Definition,即通用模塊定義。CMD 規(guī)范整合了 CommonJS 和 AMD 規(guī)范的特點(diǎn)。sea.js 是 CMD 規(guī)范的一個(gè)實(shí)現(xiàn) 。

CMD 定義模塊也是通過(guò)一個(gè)全局函數(shù) define 來(lái)實(shí)現(xiàn)的,但只有一個(gè)參數(shù),該參數(shù)既可以是函數(shù)也可以是對(duì)象:

define(factory);

如果這個(gè)參數(shù)是對(duì)象,那么模塊導(dǎo)出的就是對(duì)象;如果這個(gè)參數(shù)為函數(shù),那么這個(gè)函數(shù)會(huì)被傳入 3 個(gè)參數(shù):

define(function(require, exports, module) {
  //...
});

這三個(gè)參數(shù)分別如下: (1)require:一個(gè)函數(shù),通過(guò)調(diào)用它可以引用其他模塊,也可以調(diào)用 require.async 函數(shù)來(lái)異步調(diào)用模塊; (2)exports:一個(gè)對(duì)象,當(dāng)定義模塊的時(shí)候,需要通過(guò)向參數(shù) exports 添加屬性來(lái)導(dǎo)出模塊 API; (3)module 是一個(gè)對(duì)象,它包含 3 個(gè)屬性:

  • uri:模塊完整的 URI 路徑;
  • dependencies:模塊依賴(lài);
  • exports:模塊需要被導(dǎo)出的 API,作用同第二個(gè)參數(shù) exports。

下面來(lái)看一個(gè)例子,定義一個(gè) increment 模塊,引用 math 模塊的 add 函數(shù),經(jīng)過(guò)封裝后導(dǎo)出成 increment 函數(shù):

define(function(require, exports, module) {
  var add = require('math').add;
  exports.increment = function(val) {
    return add(val, 1);
  };
  module.id = "increment";
});

CMD 最大的特點(diǎn)就是懶加載,不需要在定義模塊的時(shí)候聲明依賴(lài),可以在模塊執(zhí)行時(shí)動(dòng)態(tài)加載依賴(lài)。除此之外,CMD 同時(shí)支持同步加載模塊和異步加載模塊。

AMD 和 CMD 的兩個(gè)主要區(qū)別如下:

  • AMD 需要異步加載模塊,而 CMD 在加載模塊時(shí),可以同步加載(require),也可以異步加載(require.async)。
  • CMD 遵循依賴(lài)就近原則,AMD 遵循依賴(lài)前置原則。也就是說(shuō),在 AMD 中,需要把模塊所需要的依賴(lài)都提前在依賴(lài)數(shù)組中聲明。而在 CMD 中,只需要在具體代碼邏輯內(nèi),使用依賴(lài)前,把依賴(lài)的模塊 require 進(jìn)來(lái)。

6. UMD

UMD 全程為 Universal Module Definition,即統(tǒng)一模塊定義。其實(shí) UMD 并不是一個(gè)模塊管理規(guī)范,而是帶有前后端同構(gòu)思想的模塊封裝工具。

UMD 是一組同時(shí)支持 AMD 和 CommonJS 的模式,它旨在使代碼無(wú)論執(zhí)行代碼的環(huán)境如何都能正常工作,通過(guò) UMD 可以在合適的環(huán)境選擇對(duì)應(yīng)的模塊規(guī)范。比如在 Node.js 環(huán)境中采用 CommonJS 模塊管理,在瀏覽器環(huán)境且支持 AMD 的情況下采用 AMD 模塊,否則導(dǎo)出為全局函數(shù)。

一個(gè)UMD模塊由兩部分組成:

  • **立即調(diào)用函數(shù)表達(dá)式 (IIFE)**:它會(huì)檢查使用模塊的環(huán)境。其有兩個(gè)參數(shù):root 和 factory。 root 是對(duì)全局范圍的 this 引用,而 factory 是定義模塊的函數(shù)。
  • 匿名函數(shù): 創(chuàng)建模塊,此匿名函數(shù)被傳遞任意數(shù)量的參數(shù)以指定模塊的依賴(lài)關(guān)系。

UMD 的代碼實(shí)現(xiàn)如下:

(function (root, factory) {
  if (typeof define === 'function' && define.amd) {
    define([], factory);
  } else if (typeof exports === 'object') {
    module.exports,
    module.exports = factory();
  } else {
    root.returnExports = factory();
  }
}(this, function () {
  // 模塊內(nèi)容定義
  return {};
}));

它的執(zhí)行過(guò)程如下:

  1. 先判斷是否支持 Node.js 模塊格式(exports 是否存在),存在則使用 Node.js 模塊格式;
  2. 再判斷是否支持 AMD(define 是否存在),存在則使用 AMD 方式加載模塊;
  3. 若兩個(gè)都不存在,則將模塊公開(kāi)到全局(Window 或 Global)。

UMD的特點(diǎn)如下:① UMD 的優(yōu)點(diǎn):

  • 小而簡(jiǎn)潔;
  • 適用于服務(wù)器端和客戶(hù)端。

② UMD 的缺點(diǎn):

  • 不容易正確配置。

7. ES 模塊

(1)概念

通過(guò)上面的例子,你可能會(huì)發(fā)現(xiàn),使用 UMD、AMD、CMD 的代碼會(huì)變得難以編寫(xiě)和理解。于是在 2015 年,負(fù)責(zé) ECMAScript 規(guī)范的 TC39 委員會(huì)將模塊添加為 JavaScript 的內(nèi)置功能,這些模塊稱(chēng)為 ECMAScript模塊,簡(jiǎn)稱(chēng) ES 模塊。

模塊和經(jīng)典 JavaScript 腳本略有不同:

  • 模塊默認(rèn)啟用嚴(yán)格模式,比如分配給未聲明的變量會(huì)報(bào)錯(cuò):
<script type="module">
  a = 5; 
</script>
  • 模塊有一個(gè)詞法頂級(jí)作用域。 這意味著,例如,運(yùn)行 var foo = 42; 在模塊內(nèi)不會(huì)創(chuàng)建名為 foo 的全局變量,可通過(guò)瀏覽器中的 window.foo 訪問(wèn),盡管在經(jīng)典JavaScript腳本中會(huì)出現(xiàn)這種情況;
<script type="module">
  let person = "Alok";
</script>

<script type="module">
   alert(person);{/* Error: person is not defined */}
</script>
  • 模塊中的 this 并不引用全局 this,而是 undefined。 (如果需要訪問(wèn)全局 this,可以使用 globalThis);
<script>
  alert(this); {/* 全局對(duì)象 */}
</script>

<script type="module">
  alert(this); {/* undefined */}
</script>
  • 新的靜態(tài)導(dǎo)入和導(dǎo)出語(yǔ)法僅在模塊中可用,并不適用于經(jīng)典腳本。
  • 頂層 await 在模塊中可用,但在經(jīng)典 JavaScript 腳本中不可用;
  • await 不能在模塊中的任何地方用作變量名,經(jīng)典腳本中的變量可以在異步函數(shù)之外命名為 await;
  • JavaScript 會(huì)提升 import 語(yǔ)句。因此,可以在模塊中的任何位置定義它們。

CommonJS 和 AMD 都是在運(yùn)行時(shí)確定依賴(lài)關(guān)系,即運(yùn)行時(shí)加載,CommonJS 加載的是拷貝。而 ES 模塊是在編譯時(shí)就確定依賴(lài)關(guān)系,所有加載的其實(shí)都是引用,這樣做的好處是可以執(zhí)行靜態(tài)分析和類(lèi)型檢查。

(2)語(yǔ)法

① 導(dǎo)出

當(dāng)導(dǎo)出模塊代碼時(shí),需要在其前面添加 export 關(guān)鍵詞。導(dǎo)出內(nèi)容可以是變量、函數(shù)或類(lèi)。任何未導(dǎo)出的代碼都是模塊私有的,無(wú)法在該模塊之被外訪問(wèn)。ES 模塊支持兩種類(lèi)型的導(dǎo)出:

  • 命名導(dǎo)出:
export const first = 'JavaScript';
export function func() {
    return true;
}

當(dāng)然,我們也可以先定義需要導(dǎo)出的變量/函數(shù),最后統(tǒng)一導(dǎo)出這些變量/函數(shù):

const first = 'JavaScript';
const second = 'TypeScript';
function func() {
    return true;
}
export {first, second, func};
  • 默認(rèn)導(dǎo)出:
function func() {
    return true;
}

export default func;

當(dāng)然,也可以直接默認(rèn)導(dǎo)出:

export default function func() {
    return true;
}

默認(rèn)導(dǎo)出可以省略變量/函數(shù)/類(lèi)名,在導(dǎo)入時(shí)可以為其指定任意名稱(chēng):

// 導(dǎo)出
export default function () {
  console.log('foo');
}
// 導(dǎo)入
import customName from './module';

注意: 導(dǎo)入默認(rèn)模塊時(shí)不需要大括號(hào),導(dǎo)出默認(rèn)的變量或方法可以有名字,但是對(duì)外是無(wú)效的。export default 在一個(gè)模塊文件中只能使用一次。

可以使用 as 關(guān)鍵字來(lái)重命名需要暴露出的變量或方法,經(jīng)過(guò)重命名后同一變量可以多次暴露出去:

const first = 'test';
export {first as second};

② 導(dǎo)入

使用命名導(dǎo)出的模塊,可以通過(guò)以下方式來(lái)導(dǎo)入:

import {first, second, func} from './module';

使用默認(rèn)導(dǎo)出的模塊,可以通過(guò)以下方式來(lái)引入,導(dǎo)入名稱(chēng)可以自定義,無(wú)論導(dǎo)出的名稱(chēng)是什么:

import customName from './module.js';

導(dǎo)入模塊位置可以是相對(duì)路徑也可以是絕對(duì)路徑,.js擴(kuò)展名是可以省略的,如果不帶路徑而只是模塊名,則需要通過(guò)配置文件告訴引擎查找的位置:

import {firstName, lastName} from './module';

可以使用 as 關(guān)鍵字來(lái)將導(dǎo)入的變量/函數(shù)重命名:

import { fn as fn1 } from './profile';

在 ES 模塊中,默認(rèn)導(dǎo)入和命名導(dǎo)入是可以同時(shí)使用的,比如在 React 組件中:

import React, {usestate, useEffect} from 'react';

const Comp = () => {
 return <React.Fragment>...</React.Fragment> 
}

export default Comp;

可以使用 as 關(guān)鍵字來(lái)加載整個(gè)模塊,用于從另一個(gè)模塊中導(dǎo)入所有命名導(dǎo)出,會(huì)忽略默認(rèn)導(dǎo)出:

import * as circle from './circle';
console.log('圓面積:' + circle.area(4));
console.log('圓周長(zhǎng):' + circle.circumference(14));

③ 動(dòng)態(tài)導(dǎo)入

上面我們介紹的都是靜態(tài)導(dǎo)入,使用靜態(tài) import 時(shí),整個(gè)模塊需要先下載并執(zhí)行,然后主代碼才能執(zhí)行。有時(shí)我們不想預(yù)先加載模塊,而是按需加載,僅在需要時(shí)才加載。這可以提高初始加載時(shí)的性能,動(dòng)態(tài) import 使這成為可能:

<script type="module">
  (async () => {
    const moduleSpecifier = './lib.mjs';
    const {repeat, shout} = await import(moduleSpecifier);
    repeat('hello');
    // → 'hello hello'
    shout('Dynamic import in action');
    // → 'DYNAMIC IMPORT IN ACTION!'
  })();
</script>

與靜態(tài)導(dǎo)入不同,動(dòng)態(tài)導(dǎo)入可以在常規(guī)腳本中使用。

④ 其他用法

可以使用以下方式來(lái)先導(dǎo)入后導(dǎo)出模塊內(nèi)容:

export { foo, bar } from './module';

上面的代碼就等同于:

import { foo, bar } from './module';
export { foo, boo};

另一個(gè)與模塊相關(guān)的新功能是import.meta,它是一個(gè)給 JavaScript 模塊暴露特定上下文的元數(shù)據(jù)屬性的對(duì)象。它包含了這個(gè)模塊的信息,比如說(shuō)這個(gè)模塊的 URL。

默認(rèn)情況下,圖像是相對(duì)于 HTML 文檔中的當(dāng)前 URL 加載的。import.meta.url可以改為加載相對(duì)于當(dāng)前模塊的圖像:

function loadThumbnail(relativePath) {
  const url = new URL(relativePath, import.meta.url);
  const image = new Image();
  image.src = url;
  return image;
}

const thumbnail = loadThumbnail('../img/thumbnail.png');
container.append(thumbnail);

(3)在瀏覽器使用

目前主流瀏覽器都支持 ES 模塊:

圖片

如果想在瀏覽器中使用原生 ES 模塊方案,只需要在 script 標(biāo)簽上添加 type="module" 屬性。通過(guò)該屬性,瀏覽器知道這個(gè)文件是以模塊化的方式運(yùn)行的。而對(duì)于不支持的瀏覽器,需要通過(guò) nomodule 屬性來(lái)指定某腳本為 fallback 方案:

<script type="module">
  import module1 from './module1'
</script>
<script nomodule src="fallback.js"></script>

支持 type="module" 的瀏覽器會(huì)忽略帶有 nomodule 屬性的腳本。使用 type="module" 的另一個(gè)作用就是進(jìn)行 ES Next 兼容性的嗅探。因?yàn)橹С?ES 模塊化的瀏覽器,都支持 ES Promise 等特性。

由于默認(rèn)情況下模塊是延遲的,因此可能還希望以延遲方式加載 nomodule 腳本:

<script nomodule defer src="fallback.js"></script>

(4)在 Node.js 使用

上面提到,Node.js 使用的是 CommonJS 模塊規(guī)范,它也是支持 ES 模塊的。在 Node.js 13 之前,ES 模塊是一項(xiàng)實(shí)驗(yàn)性技術(shù),因此,可以通過(guò)使用 .mjs 擴(kuò)展名保存模塊并通過(guò)標(biāo)志訪問(wèn)它來(lái)使用模塊。

從 Node.js 13 開(kāi)始,可以通過(guò)以下兩種方式使用模塊:

  • 使用 .mjs 擴(kuò)展名保存模塊;
  • 在最近的文件夾中創(chuàng)建一個(gè) type="module" 的 package.json 文件。

那如何在小于等于 12 版本的 Node.js 中使用 ES 模塊呢?可以在執(zhí)行腳本啟動(dòng)時(shí)加上 --experimental-modules,不過(guò)這一用法要求相應(yīng)的文件后綴名必須為 .mjs:

node --experimental-modules module1.mjs
import module1 from './module1.mjs'
module1


責(zé)任編輯:武曉燕 來(lái)源: 前端充電寶
相關(guān)推薦

2020-09-17 10:30:21

前端模塊化組件

2020-09-18 09:02:32

前端模塊化

2022-09-05 09:01:13

前端模塊化

2013-08-20 15:31:18

前端模塊化

2022-03-11 13:01:27

前端模塊

2019-12-02 16:05:10

前端模塊化JavaScript

2014-04-27 10:16:31

QCon北京2014Andrew Bett

2019-08-28 16:18:39

JavaScriptJS前端

2016-09-23 11:08:35

前端Javascript模塊化

2013-03-19 10:50:38

2013-03-11 10:00:13

前端模塊化

2018-12-18 11:20:28

前端模塊化JavaScript

2016-10-09 11:03:41

Javascript模塊化Web

2013-03-11 10:10:03

2015-10-10 10:01:28

前端模塊化webpack

2022-09-21 11:51:26

模塊化應(yīng)用

2017-05-18 10:23:55

模塊化開(kāi)發(fā)RequireJsJavascript

2015-10-10 11:29:45

Java模塊化系統(tǒng)初探

2010-05-28 10:31:28

模塊化IT

2021-07-14 09:26:51

UPS電源模塊化
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)