溫馨提示×

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

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

什么是CommonJS規(guī)范

發(fā)布時(shí)間:2021-10-25 11:40:08 來(lái)源:億速云 閱讀:276 作者:iii 欄目:web開發(fā)

這篇文章主要介紹“什么是CommonJS規(guī)范”,在日常操作中,相信很多人在什么是CommonJS規(guī)范問題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”什么是CommonJS規(guī)范”的疑惑有所幫助!接下來(lái),請(qǐng)跟著小編一起來(lái)學(xué)習(xí)吧!

一.什么是模塊化?

在很多開發(fā)的情況下,我們都知道要使用模塊化開發(fā),那為什么要使用它呢?

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

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

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

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

上面說(shuō)提到的結(jié)構(gòu),就是模塊;

按照這種結(jié)構(gòu)劃分開發(fā)程序的過程,就是模塊化開發(fā)的過程;

二.JavaScript設(shè)計(jì)缺陷

在網(wǎng)頁(yè)開發(fā)的早期,由于JavaScript僅僅作為一種腳本語(yǔ)言,只能做一些簡(jiǎn)單的表單驗(yàn)證或動(dòng)畫實(shí)現(xiàn)等,它還是具有很多的缺陷問題的,比如:

  • var定義的變量作用域問題;

  • JavaScript的面向?qū)ο蟛⒉荒芟癯R?guī)面向?qū)ο笳Z(yǔ)言一樣使用class;

  • 在早期JavaScript并沒有模塊化的問題,所以也就沒有對(duì)應(yīng)的模塊化解決方案;

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

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

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

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

所以,模塊化已經(jīng)是JavaScript一個(gè)非常迫切的需求:

  • 但是JavaScript本身,直到ES6(2015)才推出了自己的模塊化方案;

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

到此,我們明白了為什么要用模塊化開發(fā)?

那如果沒有模塊化會(huì)帶來(lái)什么問題呢?

三.沒有模塊化的問題

當(dāng)我們?cè)诠久鎸?duì)一個(gè)大型的前端項(xiàng)目時(shí),通常是多人開發(fā)的,會(huì)把不同的業(yè)務(wù)邏輯分步在多個(gè)文件夾當(dāng)中。

2.1 沒有模塊化給項(xiàng)目帶來(lái)的弊端

假設(shè)有兩個(gè)人,分別是小豪和小紅在開發(fā)一個(gè)項(xiàng)目

項(xiàng)目的目錄結(jié)構(gòu)是這樣的

什么是CommonJS規(guī)范

小豪開發(fā)的bar.js文件

var name = "小豪";  console.log("bar.js----", name);

小豪開發(fā)的baz.js文件

console.log("baz.js----", name);

小紅開發(fā)的foo.js文件

var name = "小紅";  console.log("foo.js----", name);

引用路徑如下:

<body>   <script src="./bar.js"></script>   <script src="./foo.js"></script>   <script src="./baz.js"></script> </body>

最后當(dāng)我去執(zhí)行的時(shí)候,卻發(fā)現(xiàn)執(zhí)行結(jié)果:

什么是CommonJS規(guī)范

當(dāng)我們看到這個(gè)結(jié)果,有的小伙伴可能就會(huì)驚訝,baz.js文件不是小豪寫的么?為什么會(huì)輸出小紅的名字呢?

究其原因,我們才發(fā)現(xiàn),其實(shí)JavaScript是沒有模塊化的概念(至少到現(xiàn)在為止還沒有用到ES6規(guī)范),換句話說(shuō)就是每個(gè).js文件并不是一個(gè)獨(dú)立的模塊,沒有自己的作用域,所以在.js文件中定義的變量,都是可以被其他的地方共享的,所以小豪開發(fā)的baz.js里面的name,其實(shí)訪問的是小紅重新聲明的。

但是共享也有一點(diǎn)不好就是,項(xiàng)目的其他協(xié)作人員也可以隨意的改變它們,顯然這不是我們想要的。

2.2 IIFE解決早期的模塊化問題

所以,隨著前端的發(fā)展,模塊化變得必不可少,那么在早期是如何解決的呢?

在早期,因?yàn)楹瘮?shù)是有自己的作用域,所以可以采用立即函數(shù)調(diào)用表達(dá)式(IIFE),也就是自執(zhí)行函數(shù),把要供外界使用的變量作為函數(shù)的返回結(jié)果。

小豪&mdash;&mdash;bar.js

var moduleBar = (function () {   var name = "小豪";   var age = "18";    console.log("bar.js----", name, age);    return {     name,     age,   }; })();

小豪&mdash;&mdash;baz.js

console.log("baz.js----", moduleBar.name); console.log("baz.js----", moduleBar.age);

小紅&mdash;&mdash;foo.js

(function () {   var name = "小紅";   var age = 20;    console.log("foo.js----", name, age); })();

來(lái)看一下,解決之后的輸出結(jié)果,原調(diào)用順序不變;

什么是CommonJS規(guī)范

但是,這又帶來(lái)了新的問題:

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

  • 代碼寫起來(lái)雜亂無(wú)章,每個(gè)文件中的代碼都需要包裹在一個(gè)匿名函數(shù)中來(lái)編寫;

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

所以現(xiàn)在急需一個(gè)統(tǒng)一的規(guī)范,來(lái)解決這些缺陷問題,就此CommonJS規(guī)范問世了。

三.Node模塊化開發(fā)&mdash;&mdash;CommonJS規(guī)范

3.1  CommonJS規(guī)范特性

CommonJS是一個(gè)規(guī)范,最初提出來(lái)是在瀏覽器以外的地方使用,并且當(dāng)時(shí)被命名為ServerJS,后來(lái)為了體現(xiàn)它的廣泛性,修改為CommonJS規(guī)范。

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

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

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

正是因?yàn)镹ode中對(duì)CommonJS進(jìn)行了支持和實(shí)現(xiàn),所以它具備以下幾個(gè)特點(diǎn);

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

  • 該模塊中,包含CommonJS規(guī)范的核心變量: exports、module.exports、require;

  • 使用核心變量,進(jìn)行模塊化開發(fā);

無(wú)疑,模塊化的核心是導(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)容;

3.2 CommonJS配合Node模塊化開發(fā)假設(shè)現(xiàn)在有兩個(gè)文件:

bar.js

const name = "時(shí)光屋小豪"; const age = 18;  function sayHello(name) {   console.log("hello" + name); }

main.js

console.log(name); console.log(age);

執(zhí)行node main.js之后,會(huì)看到

什么是CommonJS規(guī)范

這是因?yàn)樵诋?dāng)前main.js模塊內(nèi),沒有發(fā)現(xiàn)name這個(gè)變量;

這點(diǎn)與我們前面看到的明顯不同,因?yàn)镹ode中每個(gè)js文件都是一個(gè)單獨(dú)的模塊。

那么如果要在別的文件內(nèi)訪問bar.js變量

  • bar.js需要導(dǎo)出自己想要暴露的變量、函數(shù)、對(duì)象等等;

  • main.js從bar.js引入想用的變量、函數(shù)、對(duì)象等等;

什么是CommonJS規(guī)范

3.3 exports導(dǎo)出

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

bar.js文件導(dǎo)出:

const name = "時(shí)光屋小豪"; const age = 18;  function sayHello(name) {   console.log("hello" + name); }  exports.name = name; exports.age = age; exports.sayHello = sayHello;

main.js文件導(dǎo)入:

const bar = require('./bar');  console.log(bar.name);  // 時(shí)光屋小豪 console.log(bar.age);   // 18

其中要注意的點(diǎn):

main.js中的bar變量等于exports對(duì)象;

bar = exports
  • 所以我們通過bar.xxx來(lái)使用導(dǎo)出文件內(nèi)的變量,比如name,age;

  • require其實(shí)是一個(gè)函數(shù),返回值是一個(gè)對(duì)象,值為“導(dǎo)出文件”的exports對(duì)象;

3.4 從內(nèi)存角度分析bar和exports是同一個(gè)對(duì)象

在Node中,有一個(gè)特殊的全局對(duì)象,其實(shí)exports就是其中之一。

如果在文件內(nèi),不再使用exports.xxx的形式導(dǎo)出某個(gè)變量的話,其實(shí)exports就是一個(gè)空對(duì)象。

什么是CommonJS規(guī)范

模塊之間的引用關(guān)系

什么是CommonJS規(guī)范

  • 當(dāng)我們?cè)趍ain.js中require導(dǎo)入的時(shí)候,它會(huì)去自動(dòng)查找特殊的全局對(duì)象exports,并且把require函數(shù)的執(zhí)行結(jié)果賦值給bar;

  • bar和exports指向同一個(gè)引用(引用地址相同);

  • 如果發(fā)現(xiàn)exports上有變量,則會(huì)放到bar對(duì)象上,正因?yàn)檫@樣我們才能從bar上讀取想用的變量;

為了進(jìn)一步論證,bar和exports是同一個(gè)對(duì)象:

我們加入定時(shí)器看看

什么是CommonJS規(guī)范

所以綜上所述,Node中實(shí)現(xiàn)CommonJS規(guī)范的本質(zhì)就是對(duì)象的引用賦值(淺拷貝本質(zhì))。

把exports對(duì)象的引用賦值bar對(duì)象上。

  • CommonJS規(guī)范的本質(zhì)就是對(duì)象的引用賦值

3.5 module.exports又是什么?

但是Node中我們經(jīng)常使用module.exports導(dǎo)出東西,也會(huì)遇到這樣的面試題:

module.exports和exports有什么關(guān)系或者區(qū)別呢?

3.6 require細(xì)節(jié)

require本質(zhì)就是一個(gè)函數(shù),可以幫助我們引入一個(gè)文件(模塊)中導(dǎo)入的對(duì)象。

require的查找規(guī)則https://nodejs.org/dist/latest-v14.x/docs/api/modules.html#modules_all_together

3.7 require模塊的加載順序

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

// aaa.js const name = 'coderwhy';  console.log("Hello aaa");  setTimeout(() => {   console.log("setTimeout"); }, 1000);
// main.js const aaa = require('./aaa');

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

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

// main.js const aaa = require('./aaa'); const bbb = require('./bbb');
/// aaa.js const ccc = require("./ccc");
// bbb.js const ccc = require("./ccc");
// ccc.js console.log('ccc被加載');

ccc中的代碼只會(huì)運(yùn)行一次。

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

  • 每個(gè)模塊對(duì)象module都有一個(gè)屬性:loaded;

  • 為false表示還沒有加載;

  • 為true表示已經(jīng)加載;

結(jié)論三:如果有循環(huán)引入,那么加載順序是什么?

如果出現(xiàn)下面模塊的引用關(guān)系,那么加載順序是什么呢?

  • 這個(gè)其實(shí)是一種數(shù)據(jù)結(jié)構(gòu):圖結(jié)構(gòu);

  • 圖結(jié)構(gòu)在遍歷的過程中,有深度優(yōu)先搜索(DFS, depth first search)和廣度優(yōu)先搜索(BFS, breadth first  search);

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

什么是CommonJS規(guī)范

多個(gè)模塊的引入關(guān)系

四.module.exports

4.1 真正導(dǎo)出的是module.exports

以下是通過維基百科對(duì)CommonJS規(guī)范的解析:

  • CommonJS中是沒有module.exports的概念的;

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

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

  • exports只是module上的一個(gè)對(duì)象

但是,為什么exports也可以導(dǎo)出呢?

  • 這是因?yàn)閙odule對(duì)象的exports屬性是exports對(duì)象的一個(gè)引用;

  • 等價(jià)于module.exports = exports = main中的bar(CommonJS內(nèi)部封裝);

4.2 module.exports和exports有什么關(guān)系或者區(qū)別呢?

聯(lián)系:module.exports = exports

進(jìn)一步論證module.exports = exports

// bar.js const name = "時(shí)光屋小豪";  exports.name = name;  setTimeout(() => {   module.exports.name = "哈哈哈";   console.log("bar.js中1s之后", exports.name); }, 1000);
// main.js const bar = require("./bar");  console.log("main.js", bar.name);  setTimeout((_) => {   console.log("main.js中1s之后", bar.name); }, 2000);

什么是CommonJS規(guī)范

在上面代碼中,只要在bar.js中修改exports對(duì)象里的屬性,導(dǎo)出的結(jié)果都會(huì)變,因?yàn)榧词拐嬲龑?dǎo)出的是  module.exports,而module.exports和exports是都是相同的引用地址,改變了其中一個(gè)的屬性,另一個(gè)也會(huì)跟著改變。

注意:真正導(dǎo)出的模塊內(nèi)容的核心其實(shí)是module.exports,只是為了實(shí)現(xiàn)CommonJS的規(guī)范,剛好module.exports對(duì)exports對(duì)象使用的是同一個(gè)引用而已

什么是CommonJS規(guī)范

區(qū)別:有以下兩點(diǎn)

那么如果,代碼這樣修改了:

什么是CommonJS規(guī)范

  • module.exports 也就和 exports沒有任何關(guān)系了;

  • 無(wú)論exports怎么改,都不會(huì)影響最終的導(dǎo)出結(jié)果;

  • 因?yàn)閙odule.exports = { xxx  }這樣的形式,會(huì)在堆內(nèi)存中新開辟出一塊內(nèi)存空間,會(huì)生成一個(gè)新的對(duì)象,用它取代之前的exports對(duì)象的導(dǎo)出

  • 那么也就意味著require導(dǎo)入的對(duì)象是新的對(duì)象;

什么是CommonJS規(guī)范

圖解module.exports和exports的區(qū)別

講完它們兩個(gè)的區(qū)別,來(lái)看下面這兩個(gè)例子,看看自己是否真正掌握了module.exports的用法

4.3 關(guān)于module.exports的練習(xí)題

練習(xí)1:導(dǎo)出的變量為值類型

// bar.js let name = "時(shí)光屋小豪";  setTimeout(() => {   name = "123123"; }, 1000);  module.exports = {   name: name,   age: "20",   sayHello: function (name) {     console.log("你好" + name);   }, };
// main.js const bar = require("./bar");  console.log("main.js", bar.name); // main.js 時(shí)光屋小豪  setTimeout(() => {   console.log("main.js中2s后", bar.name); // main.js中2s后 時(shí)光屋小豪 }, 2000);

練習(xí)2:導(dǎo)出的變量為引用類型

// bar.js let info = {   name: "時(shí)光屋小豪", };  setTimeout(() => {   info.name = "123123"; }, 1000);  module.exports = {   info: info,   age: "20",   sayHello: function (name) {     console.log("你好" + name);   }, };
// main.js const bar = require("./bar");  console.log("main.js", bar.info.name); // main.js 時(shí)光屋小豪  setTimeout(() => {   console.log("main.js中2s后", bar.info.name); // main.js中2s后 123123 }, 2000);

從main.js輸出結(jié)果來(lái)看,定時(shí)器修改的name變量的結(jié)果,并沒有影響main.js中導(dǎo)入的結(jié)果。

  • 因?yàn)閚ame為值類型,基本類型,一旦定義之后,就把其屬性值,放到了module.exports的內(nèi)存里(練1)

  • 因?yàn)閕nfo為引用類型,所以module.exports里存放的是info的引用地址,所以由定時(shí)器更改的變量,會(huì)影響main.js導(dǎo)入的結(jié)果(練2)

什么是CommonJS規(guī)范

五.CommonJS的加載過程

CommonJS模塊加載js文件的過程是運(yùn)行時(shí)加載的,并且是同步的:

  • 運(yùn)行時(shí)加載意味著是js引擎在執(zhí)行js代碼的過程中加載模塊;

  • 同步的就意味著一個(gè)文件沒有加載結(jié)束之前,后面的代碼都不會(huì)執(zhí)行;

const flag = true;  if (flag) {   const foo = require('./foo');   console.log("等require函數(shù)執(zhí)行完畢后,再輸出這句代碼"); }

CommonJS通過module.exports導(dǎo)出的是一個(gè)對(duì)象:

  • 導(dǎo)出的是一個(gè)對(duì)象意味著可以將這個(gè)對(duì)象的引用在其他模塊中賦值給其他變量;

  • 但是最終他們指向的都是同一個(gè)對(duì)象,那么一個(gè)變量修改了對(duì)象的屬性,所有的地方都會(huì)被修改;

六.CommonJS規(guī)范的本質(zhì)

CommonJS規(guī)范的本質(zhì)就是對(duì)象的引用賦值

到此,關(guān)于“什么是CommonJS規(guī)范”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注億速云網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)?lái)更多實(shí)用的文章!

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

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎ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