溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

JavaScript高級(jí)語(yǔ)法中的模塊化怎么理解

發(fā)布時(shí)間:2022-01-27 09:37:00 來(lái)源:億速云 閱讀:115 作者:kk 欄目:web開(kāi)發(fā)

這篇文章將為大家詳細(xì)講解有關(guān)JavaScript高級(jí)語(yǔ)法中的模塊化怎么理解,文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個(gè)參考,希望大家閱讀完這篇文章后對(duì)相關(guān)知識(shí)有一定的了解。

什么是模塊化?

到底什么是模塊化、模塊化開(kāi)發(fā)呢?

  • 事實(shí)上模塊化開(kāi)發(fā)最終的目的是將程序劃分成一個(gè)個(gè)小的結(jié)構(gòu)。

  • 這個(gè)結(jié)構(gòu)中編寫(xiě)屬于自己的邏輯代碼,有自己的作用域,不會(huì)影響到其他的結(jié)構(gòu)。

  • 這個(gè)結(jié)構(gòu)可以將自己希望暴露的變量、函數(shù)、對(duì)象等導(dǎo)出給其結(jié)構(gòu)使用。

  • 也可以通過(guò)某種方式,導(dǎo)入另外結(jié)構(gòu)中的變量、函數(shù)、對(duì)象等。

上面說(shuō)提到的結(jié)構(gòu),就是模塊;按照這種結(jié)構(gòu)劃分開(kāi)發(fā)程序的過(guò)程,就是模塊化開(kāi)發(fā)的過(guò)程。

模塊化的歷史

在網(wǎng)頁(yè)開(kāi)發(fā)的早期,Brendan Eich開(kāi)發(fā)JavaScript僅僅作為一種腳本語(yǔ)言,做一些簡(jiǎn)單的表單驗(yàn)證或動(dòng)畫(huà)實(shí)現(xiàn)等,那個(gè)時(shí)候代碼還是很少的:

  • 這個(gè)時(shí)候我們只需要講JavaScript代碼寫(xiě)到

  • 并沒(méi)有必要放到多個(gè)文件中來(lái)編寫(xiě)。

但是隨著前端和JavaScript的快速發(fā)展,JavaScript代碼變得越來(lái)越復(fù)雜了:

  • ajax的出現(xiàn),前后端開(kāi)發(fā)分離,意味著后端返回?cái)?shù)據(jù)后,我們需要通過(guò)JavaScript進(jìn)行前端頁(yè)面的渲染。

  • SPA的出現(xiàn),前端頁(yè)面變得更加復(fù)雜:包括前端路由、狀態(tài)管理等等一系列復(fù)雜的需求需要通過(guò)JavaScript來(lái)實(shí)現(xiàn)。

  • 包括Node的實(shí)現(xiàn),JavaScript編寫(xiě)復(fù)雜的后端程序,沒(méi)有模塊化是致命的硬傷。

所以,模塊化已經(jīng)是JavaScript一個(gè)非常迫切的需求。所以ES6(2015)才推出了自己的模塊化方案。

在此之前,為了讓JavaScript支持模塊化,涌現(xiàn)出了很多不同的模塊化規(guī)范:AMD、CMD、CommonJS等。

沒(méi)有模塊化帶來(lái)的問(wèn)題

比如命名沖突的問(wèn)題。

通過(guò)立即函數(shù)調(diào)用表達(dá)式(IIFE)來(lái)解決上面的問(wèn)題。因?yàn)楹瘮?shù)有自己的作用域,不會(huì)造成不同文件命名沖突。

    // a.js
    var moduleA = (function() {
      var name = "llm"
      var age = 22
      var isFlag = true
      return {
        name: name,
        isFlag: isFlag
      }
    })()
    // b.js
    var moduleB = (function() {
      var name = "zh"
      var isFlag = false
      return {
        name: name,
        isFlag: isFlag
      }
    })()
    // 使用
    moduleA.name
    moduleB.name

但是,我們其實(shí)帶來(lái)了新的問(wèn)題:

  • 我必須記得每一個(gè)模塊中返回對(duì)象的命名,才能在其他模塊使用過(guò)程中正確的使用。

  • 代碼寫(xiě)起來(lái)混亂不堪,每個(gè)文件中的代碼都需要包裹在一個(gè)匿名函數(shù)中來(lái)編寫(xiě)。

  • 在沒(méi)有合適的規(guī)范情況下,每個(gè)人、每個(gè)公司都可能會(huì)任意命名、甚至出現(xiàn)模塊名稱(chēng)相同的情況。

所以,我們會(huì)發(fā)現(xiàn),雖然實(shí)現(xiàn)了模塊化,但是我們的實(shí)現(xiàn)過(guò)于簡(jiǎn)單,并且是沒(méi)有規(guī)范的。

我們需要制定一定的規(guī)范來(lái)約束每個(gè)人都按照這個(gè)規(guī)范去編寫(xiě)模塊化的代碼。這個(gè)規(guī)范中應(yīng)該包括核心功能:模塊本身可以導(dǎo)出暴露的屬性,模塊又可以導(dǎo)入自己需要的屬性。JavaScript社區(qū)為了解決上面的問(wèn)題,涌現(xiàn)出一系列好用的規(guī)范,接下來(lái)我們就學(xué)習(xí)具有代表性的一些規(guī)范。

CommonJS規(guī)范和Node

我們需要知道CommonJS是一個(gè)規(guī)范,最初提出來(lái)是在瀏覽器以外的地方使用,并且當(dāng)時(shí)被命名為ServerJS,后來(lái)為了體現(xiàn)它的廣泛性,修改為CommonJS,平時(shí)我們也會(huì)簡(jiǎn)稱(chēng)為CJS。

  • Node是CommonJS在服務(wù)器端一個(gè)具有代表性的實(shí)現(xiàn)。

  • Browserify是CommonJS在瀏覽器中的一種實(shí)現(xiàn)。

  • webpack打包工具具備對(duì)CommonJS的支持和轉(zhuǎn)換。

所以,Node中對(duì)CommonJS進(jìn)行了支持和實(shí)現(xiàn),讓我們?cè)陂_(kāi)發(fā)node的過(guò)程中可以方便的進(jìn)行模塊化開(kāi)發(fā)。

  • 在Node中每一個(gè)js文件都是一個(gè)單獨(dú)的模塊。

  • 這個(gè)模塊中包括CommonJS規(guī)范的核心變量:exports、module.exports、require。

  • 我們可以使用這些變量來(lái)方便的進(jìn)行模塊化開(kāi)發(fā)。

前面我們提到過(guò)模塊化的核心是導(dǎo)出和導(dǎo)入,Node中對(duì)其進(jìn)行了實(shí)現(xiàn):

  • exports和module.exports可以負(fù)責(zé)對(duì)模塊中的內(nèi)容進(jìn)行導(dǎo)出。

  • require函數(shù)可以幫助我們導(dǎo)入其他模塊(自定義模塊、系統(tǒng)模塊、第三方庫(kù)模塊)中的內(nèi)容。

Node.js模塊化

Node中對(duì)CommonJS進(jìn)行了支持和實(shí)現(xiàn),讓我們?cè)陂_(kāi)發(fā)node的過(guò)程中可以方便的進(jìn)行模塊化開(kāi)發(fā):

  • 在Node中每一個(gè)js文件都是一個(gè)單獨(dú)的模塊。

  • 這個(gè)模塊中包括CommonJS規(guī)范的核心變量:exports、module.exports、require。

  • exports和module.exports可以負(fù)責(zé)對(duì)模塊中的內(nèi)容進(jìn)行導(dǎo)出。

  • require函數(shù)可以幫助我們導(dǎo)入其他模塊(自定義模塊、系統(tǒng)模塊、第三方庫(kù)模塊)中的內(nèi)容。

下面我們將來(lái)介紹exports、module.exports、require的使用。

  • exports是一個(gè)對(duì)象,我們可以在這個(gè)對(duì)象中添加很多個(gè)屬性,添加的屬性會(huì)導(dǎo)出。

  • 我們也可以通過(guò)module.exports直接導(dǎo)出一個(gè)對(duì)象。

  • 我們通過(guò)require()函數(shù)導(dǎo)入一個(gè)文件。并且該文件導(dǎo)出的變量。

下面來(lái)詳細(xì)介紹一個(gè)module.exports。

CommonJS中是沒(méi)有module.exports的概念的。

但是為了實(shí)現(xiàn)模塊的導(dǎo)出,Node中使用的是Module的類(lèi),每一個(gè)模塊都是Module的一個(gè)實(shí)例,也就是module。

所以在Node中真正用于導(dǎo)出的其實(shí)根本不是exports,而是module.exports。

因?yàn)閙odule才是導(dǎo)出的真正實(shí)現(xiàn)者。

并且內(nèi)部將exports賦值給module.exports。

該方式的導(dǎo)入導(dǎo)出有以下特點(diǎn):

Node中的文件都運(yùn)行在一個(gè)函數(shù)中??梢酝ㄟ^(guò)打印console.log(arguments.callee + "")來(lái)驗(yàn)證。

JavaScript高級(jí)語(yǔ)法中的模塊化怎么理解

導(dǎo)入導(dǎo)出是值的引用,如果導(dǎo)出的是一個(gè)基本數(shù)據(jù)類(lèi)型值,那么導(dǎo)出文件改變?cè)撝担缓髮?dǎo)入文件該變量的值也不會(huì)變。

    // a.js
    const obj = require("./b.js")
    console.log(obj)
    setTimeout(() => {
      obj.name = "llm"
    }, 1000)
    // b.js
    const info = {
      name: "zh",
      age: 22,
      foo: function() {
        console.log("foo函數(shù)~")
      }
    }
    setTimeout(() => {
      console.log(info.name) // llm
    }, 2000)
    module.exports = info

他是通過(guò)require 函數(shù)來(lái)導(dǎo)入的,只有在執(zhí)行js代碼才會(huì)知道模塊的依賴(lài)關(guān)系。

代碼是同步執(zhí)行的。

模塊多次引入,只會(huì)加載一次。每個(gè)module內(nèi)部會(huì)存在一個(gè)loaded來(lái)確定是否被加載過(guò)。

代碼循環(huán)引入的時(shí)候,深度優(yōu)先來(lái)加載模塊。然后再?gòu)V度優(yōu)先。

下面來(lái)詳細(xì)介紹一個(gè)require的導(dǎo)入細(xì)節(jié)

我們現(xiàn)在已經(jīng)知道,require是一個(gè)函數(shù),可以幫助我們引入一個(gè)文件(模塊)中導(dǎo)出的對(duì)象。

那么,require的查找規(guī)則是怎么樣的呢?

詳細(xì)查找規(guī)則,請(qǐng)?jiān)L問(wèn)這里

這里我總結(jié)比較常見(jiàn)的查找規(guī)則:導(dǎo)入格式如下:require(X)

JavaScript高級(jí)語(yǔ)法中的模塊化怎么理解

模塊的加載細(xì)節(jié)

模塊在被第一次引入時(shí),模塊中的js代碼會(huì)被運(yùn)行一次

模塊被多次引入時(shí),會(huì)緩存,最終只加載(運(yùn)行)一次

為什么只會(huì)加載運(yùn)行一次呢?

這是因?yàn)槊總€(gè)模塊對(duì)象module都有一個(gè)屬性:loaded。為false表示還沒(méi)有加載,為true表示已經(jīng)加載。

如果有循環(huán)引入,那么加載順序是什么?

JavaScript高級(jí)語(yǔ)法中的模塊化怎么理解

如上圖,Node采用的是深度優(yōu)先算法:main -> aaa -> ccc -> ddd -> eee ->bbb

CommonJS規(guī)范缺點(diǎn)

CommonJS加載模塊是同步的:

同步的意味著只有等到對(duì)應(yīng)的模塊加載完畢,當(dāng)前模塊中的內(nèi)容才能被運(yùn)行。

這個(gè)在服務(wù)器不會(huì)有什么問(wèn)題,因?yàn)榉?wù)器加載的js文件都是本地文件,加載速度非???。

如果將它應(yīng)用于瀏覽器呢?

瀏覽器加載js文件需要先從服務(wù)器將文件下載下來(lái),之后再加載運(yùn)行。

那么采用同步的就意味著后續(xù)的js代碼都無(wú)法正常運(yùn)行,即使是一些簡(jiǎn)單的DOM操作。所以在瀏覽器中,我們通常不使用CommonJS規(guī)范。當(dāng)然在webpack中使用CommonJS是另外一回事。因?yàn)樗鼤?huì)將我們的代碼轉(zhuǎn)成瀏覽器可以直接執(zhí)行的代碼。

AMD規(guī)范

在早期為了可以在瀏覽器中使用模塊化,通常會(huì)采用AMD或CMD。但是目前一方面現(xiàn)代的瀏覽器已經(jīng)支持ES Modules,另一方面借助于webpack等工具可以實(shí)現(xiàn)對(duì)CommonJS或者ES Module代碼的轉(zhuǎn)換。AMD和CMD已經(jīng)使用非常少了,所以這里我們進(jìn)行簡(jiǎn)單的演練。

AMD主要是應(yīng)用于瀏覽器的一種模塊化規(guī)范:

AMD是Asynchronous Module Definition(異步模塊定義)的縮寫(xiě)。它采用的是異步加載模塊。

我們提到過(guò),規(guī)范只是定義代碼的應(yīng)該如何去編寫(xiě),只有有了具體的實(shí)現(xiàn)才能被應(yīng)用。

AMD實(shí)現(xiàn)的比較常用的庫(kù)是require.js和curl.js。

require.js的使用

定義HTML的script標(biāo)簽引入require.js和定義入口文件。data-main屬性的作用是在加載完src的文件后會(huì)加載執(zhí)行該文件

// index.html
 <script src="./require.js" data-main="./index.js"></script>
    //main.js
    require.config({
      baseUrl: '', // 默認(rèn)是main.js的文件夾路徑
      paths: {
        foo: "./foo"
      }
    })
    require(["foo"], function(foo) {
      console.log("main:", foo)
    })
    // foo.js
    define(function() {
      const name = "zh"
      const age = 22
      function sum(num1, num2) {
        return num1 + num2
      }
      return {
        name,
        age,
        sum
      }
    })

CMD規(guī)范

CMD規(guī)范也是應(yīng)用于瀏覽器的一種模塊化規(guī)范:

CMD 是Common Module Definition(通用模塊定義)的縮寫(xiě)。它也采用了異步加載模塊,但是它將CommonJS的優(yōu)點(diǎn)吸收了過(guò)來(lái)。

AMD實(shí)現(xiàn)的比較常用的庫(kù)是SeaJS。

SeaJS的使用

引入sea.js和使用主入口文件。

    // index.html
      <script src="./sea.js"></script>
      <script>
        seajs.use("./main.js")
      </script>
    //main.js
    define(function(require, exports, module) {
      const foo = require("./foo")
      console.log("main:", foo)
    })
    // foo.js
   define(function(require, exports, module) {
      const name = "zh"
      const age = 22
      function sum(num1, num2) {
        return num1 + num2
      }
      // exports.name = name
      // exports.age = age
      module.exports = {
        name,
        age,
        sum
      }
    });

ES Module

ES Module和CommonJS的模塊化有一些不同之處:

  • 一方面它使用了import和export關(guān)鍵字來(lái)實(shí)現(xiàn)模塊化。

  • 另一方面它采用編譯期的靜態(tài)分析,并且也加入了動(dòng)態(tài)引用的方式。

  • export負(fù)責(zé)將模塊內(nèi)的內(nèi)容導(dǎo)出。

  • import負(fù)責(zé)從其他模塊導(dǎo)入內(nèi)容。

  • 采用ES Module將自動(dòng)采用嚴(yán)格模式:use strict。

基本使用

    // index.html
    <script src="./main.js" type="module"></script>
    // foo.js
    let obj = {
      name: "zh",
      age: 22
    }
    
    export default sum
    // main.js
    import foo from './foo.js'
    console.log(foo)

在html文件加載入口文件的時(shí)候,需要指定type為module。

在打開(kāi)html文件時(shí),需要開(kāi)啟本地服務(wù),而不能直接打開(kāi)運(yùn)行在瀏覽器上。

這個(gè)在MDN上面有給出解釋?zhuān)?/p>

你需要注意本地測(cè)試 — 如果你通過(guò)本地加載Html 文件 (比如一個(gè) file:// 路徑的文件), 你將會(huì)遇到 CORS 錯(cuò)誤,因?yàn)镴avascript 模塊安全性需要。

你需要通過(guò)一個(gè)服務(wù)器來(lái)測(cè)試。

exports關(guān)鍵字

export關(guān)鍵字將一個(gè)模塊中的變量、函數(shù)、類(lèi)等導(dǎo)出。

我們希望將其他中內(nèi)容全部導(dǎo)出,它可以有如下的方式:

方式一:在語(yǔ)句聲明的前面直接加上export關(guān)鍵字。

    export const name = "zh"
    export const age = 22

方式二:將所有需要導(dǎo)出的標(biāo)識(shí)符,放到export后面的 {} 中。注意:這里的 {}里面不是ES6的對(duì)象字面量的增強(qiáng)寫(xiě)法,{}也不是表示一個(gè)對(duì)象的。所以: export {name: name},是錯(cuò)誤的寫(xiě)法。

    const name = "zh"
    const age = 22
    function foo() {
      console.log("foo function")
    }
    export {
      name,
      age,
      foo
    }

方式三:導(dǎo)出時(shí)給標(biāo)識(shí)符起一個(gè)別名。(基本沒(méi)用,一般在導(dǎo)入文件中起別名)。然后在導(dǎo)入文件中就只能使用別名來(lái)獲取。

    export {
      name as fName,
      age as fAge,
      foo as fFoo
    }

import關(guān)鍵字

import關(guān)鍵字負(fù)責(zé)從另外一個(gè)模塊中導(dǎo)入內(nèi)容。

導(dǎo)入內(nèi)容的方式也有多種:

方式一:import {標(biāo)識(shí)符列表} from '模塊'。注意:這里的{}也不是一個(gè)對(duì)象,里面只是存放導(dǎo)入的標(biāo)識(shí)符列表內(nèi)容。

    import { name, age } from "./foo.js"

方式二:導(dǎo)入時(shí)給標(biāo)識(shí)符起別名。

    import { name as fName, age as fAge } from './foo.js'

方式三:通過(guò) * 將模塊功能放到一個(gè)模塊功能對(duì)象(a module object)上。然后通過(guò)起別名來(lái)使用。

    import * as foo from './foo.js'

export和import結(jié)合使用

表示導(dǎo)入導(dǎo)出。

    import { add, sub } from './math.js'
    import {otherProperty} from './other.js'
    export {
      add,
      sub,
      otherProperty
    }

等價(jià)于

    // 導(dǎo)入的所有文件會(huì)統(tǒng)一被導(dǎo)出
    export { add, sub } from './math.js'
    export {otherProperty} from './other.js'

等價(jià)于

    export * from './math.js'
    export * from './other.js'

為什么要這樣做呢?

在開(kāi)發(fā)和封裝一個(gè)功能庫(kù)時(shí),通常我們希望將暴露的所有接口放到一個(gè)文件中。 這樣方便指定統(tǒng)一的接口規(guī)范,也方便閱讀。這個(gè)時(shí)候,我們就可以使用export和import結(jié)合使用。

default用法

前面我們學(xué)習(xí)的導(dǎo)出功能都是有名字的導(dǎo)出(named exports):

在導(dǎo)出export時(shí)指定了名字。

在導(dǎo)入import時(shí)需要知道具體的名字。

還有一種導(dǎo)出叫做默認(rèn)導(dǎo)出(default export)

    // foo.js
    const name = "zh"
    cconst age = 22
    export {
      name,
      // 或者這樣的默認(rèn)導(dǎo)出
      // age as default
    }
    export default age
    // 導(dǎo)入語(yǔ)句: 導(dǎo)入的默認(rèn)的導(dǎo)出
    import foo, {name} from './foo.js'
    console.log(foo, name) // 22 zh

默認(rèn)導(dǎo)出export時(shí)可以不需要指定名字。

在導(dǎo)入時(shí)不需要使用 {},并且可以自己來(lái)指定名字。

它也方便我們和現(xiàn)有的CommonJS等規(guī)范相互操作。

注意:在一個(gè)模塊中,只能有一個(gè)默認(rèn)導(dǎo)出(default export)。

import函數(shù)

通過(guò)import加載一個(gè)模塊,是不可以在其放到邏輯代碼中的,比如:

    if(true) {
        import foo from './foo.js'
    }

為什么會(huì)出現(xiàn)這個(gè)情況呢?

這是因?yàn)镋S Module在被JS引擎解析時(shí),就必須知道它的依賴(lài)關(guān)系。

由于這個(gè)時(shí)候js代碼沒(méi)有任何的運(yùn)行,所以無(wú)法在進(jìn)行類(lèi)似于if判斷中根據(jù)代碼的執(zhí)行情況。

但是某些情況下,我們確確實(shí)實(shí)希望動(dòng)態(tài)的來(lái)加載某一個(gè)模塊:

如果根據(jù)不同的條件,動(dòng)態(tài)來(lái)選擇加載模塊的路徑。

這個(gè)時(shí)候我們需要使用 import() 函數(shù)來(lái)動(dòng)態(tài)加載。import函數(shù)返回的結(jié)果是一個(gè)Promise。

    import("./foo.js").then(res => {
      console.log("res:", res.name)
    })

es11新增了一個(gè)屬性。meta屬性本身也是一個(gè)對(duì)象: { url: "當(dāng)前模塊所在的路徑" }

    console.log(import.meta)

ES Module的解析流程

ES Module是如何被瀏覽器解析并且讓模塊之間可以相互引用的呢?

ES Module的解析過(guò)程可以劃分為三個(gè)階段:

階段一:構(gòu)建(Construction),根據(jù)地址查找js文件,并且下載,將其解析成模塊記錄(Module Record)。

階段二:實(shí)例化(Instantiation),對(duì)模塊記錄進(jìn)行實(shí)例化,并且分配內(nèi)存空間,解析模塊的導(dǎo)入和導(dǎo)出語(yǔ)句,把模塊指向?qū)?yīng)的內(nèi)存地址。

階段三:運(yùn)行(Evaluation),運(yùn)行代碼,計(jì)算值,并且將值填充到內(nèi)存地址中。

JavaScript高級(jí)語(yǔ)法中的模塊化怎么理解

階段一:

JavaScript高級(jí)語(yǔ)法中的模塊化怎么理解

階段二和三:

JavaScript高級(jí)語(yǔ)法中的模塊化怎么理解

所以,從上面可以看出在導(dǎo)出文件中,修改變量的值會(huì)影響到導(dǎo)入文件中的值。而且導(dǎo)入文件被限制修改導(dǎo)出文件的值。

JavaScript的特點(diǎn)

1.JavaScript主要用來(lái)向HTML頁(yè)面添加交互行為。 2.JavaScript可以直接嵌入到HTML頁(yè)面,但寫(xiě)成單獨(dú)的js文件有利于結(jié)構(gòu)和行為的分離。 3.JavaScript具有跨平臺(tái)特性,在絕大多數(shù)瀏覽器的支持下,可以在多種平臺(tái)下運(yùn)行。

關(guān)于JavaScript高級(jí)語(yǔ)法中的模塊化怎么理解就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺(jué)得文章不錯(cuò),可以把它分享出去讓更多的人看到。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI