成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

「Node.js系列」深入淺出Node模塊化開發——CommonJS規范

開發 前端
本文將為大家透徹的介紹關于Node的模塊化——CommonJS的一切。

[[351111]]

前言

 本文將為大家透徹的介紹關于Node的模塊化——CommonJS的一切。

看完本文可以掌握,以下幾個方面:

  • 什么是模塊化,以及沒有模塊化會帶來哪些問題,是如何解決的;
  • JavaScript的設計缺陷;
  • CommonJS規范;
  • 它的規范特性;
  • 如何配合Node完成模塊化開發;
  • exports如何導出的;
  • module.exports是如何導出變量的,值類型和引用類型導出間的差異;
  • 從內存角度深度分析module.exports和exports又有怎樣的區別和聯系;
  • require的細節,以及模塊的加載執行順序;
  • CommonJS的加載過程;
  • CommonJS規范的本質;

一.什么是模塊化?

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

而事實上,模塊化開發最終的目的是將程序劃分成一個個小的結構;

  • 在這個結構中編寫屬于自己的邏輯代碼,有自己的作用域,不會影響到其他的結構;
  • 這個結構可以將自己希望暴露的變量、函數、對象等導出給其結構使用;
  • 也可以通過某種方式,導入另外結構中的變量、函數、對象等;

上面說提到的結構,就是模塊;

按照這種結構劃分開發程序的過程,就是模塊化開發的過程;

二.JavaScript設計缺陷

在網頁開發的早期,由于JavaScript僅僅作為一種腳本語言,只能做一些簡單的表單驗證或動畫實現等,它還是具有很多的缺陷問題的,比如:

  • var定義的變量作用域問題;
  • JavaScript的面向對象并不能像常規面向對象語言一樣使用class;
  • 在早期JavaScript并沒有模塊化的問題,所以也就沒有對應的模塊化解決方案;

但隨著前端和JavaScript的快速發展,JavaScript代碼變得越來越復雜了;

  • ajax的出現,前后端開發分離,意味著后端返回數據后,我們需要通過JavaScript進行前端頁面的渲染;
  • SPA的出現,前端頁面變得更加復雜:包括前端路由、狀態管理等等一系列復雜的需求需要通過JavaScript來實現;
  • 包括Node的實現,JavaScript編寫復雜的后端程序,沒有模塊化是致命的硬傷;

所以,模塊化已經是JavaScript一個非常迫切的需求:

  • 但是JavaScript本身,直到ES6(2015)才推出了自己的模塊化方案;
  • 在此之前,為了讓JavaScript支持模塊化,涌現出了很多不同的模塊化規范:AMD、CMD、CommonJS等;

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

那如果沒有模塊化會帶來什么問題呢?

三.沒有模塊化的問題

當我們在公司面對一個大型的前端項目時,通常是多人開發的,會把不同的業務邏輯分步在多個文件夾當中。

2.1 沒有模塊化給項目帶來的弊端

假設有兩個人,分別是小豪和小紅在開發一個項目

項目的目錄結構是這樣的

小豪開發的bar.js文件

  1. var name = "小豪"
  2.  
  3. console.log("bar.js----"name); 

小豪開發的baz.js文件

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

小紅開發的foo.js文件

  1. var name = "小紅"
  2.  
  3. console.log("foo.js----"name); 

引用路徑如下:

  1. <body> 
  2.   <script src="./bar.js"></script> 
  3.   <script src="./foo.js"></script> 
  4.   <script src="./baz.js"></script> 
  5. </body> 

最后當我去執行的時候,卻發現執行結果:

當我們看到這個結果,有的小伙伴可能就會驚訝,baz.js文件不是小豪寫的么?為什么會輸出小紅的名字呢?

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

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

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

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

在早期,因為函數是有自己的作用域,所以可以采用立即函數調用表達式(IIFE),也就是自執行函數,把要供外界使用的變量作為函數的返回結果。 

小豪——bar.js

  1. var moduleBar = (function () { 
  2.   var name = "小豪"
  3.   var age = "18"
  4.  
  5.   console.log("bar.js----"name, age); 
  6.  
  7.   return { 
  8.     name
  9.     age, 
  10.   }; 
  11. })(); 

小豪——baz.js

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

小紅——foo.js

  1. (function () { 
  2.   var name = "小紅"
  3.   var age = 20; 
  4.  
  5.   console.log("foo.js----"name, age); 
  6. })(); 

來看一下,解決之后的輸出結果,原調用順序不變;

但是,這又帶來了新的問題:

  • 我必須記得每一個模塊中返回對象的命名,才能在其他模塊使用過程中正確的使用;
  • 代碼寫起來雜亂無章,每個文件中的代碼都需要包裹在一個匿名函數中來編寫;
  • 在沒有合適的規范情況下,每個人、每個公司都可能會任意命名、甚至出現模塊名稱相同的情況;

所以現在急需一個統一的規范,來解決這些缺陷問題,就此CommonJS規范問世了。

三.Node模塊化開發——CommonJS規范

3.1 CommonJS規范特性

CommonJS是一個規范,最初提出來是在瀏覽器以外的地方使用,并且當時被命名為ServerJS,后來為了體現它的廣泛性,修改為CommonJS規范。

  • Node是CommonJS在服務器端一個具有代表性的實現;
  • Browserify是CommonJS在瀏覽器中的一種實現;
  • webpack打包工具具備對CommonJS的支持和轉換;

正是因為Node中對CommonJS進行了支持和實現,所以它具備以下幾個特點;

  • 在Node中每一個js文件都是一個單獨的模塊;
  • 該模塊中,包含CommonJS規范的核心變量: exports、module.exports、require;
  • 使用核心變量,進行模塊化開發;

無疑,模塊化的核心是導出和導入,Node中對其進行了實現:

  • exports和module.exports可以負責對模塊中的內容進行導出;
  • require函數可以幫助我們導入其他模塊(自定義模塊、系統模塊、第三方庫模塊)中的內容;

3.2 CommonJS配合Node模塊化開發假設現在有兩個文件: 

bar.js

  1. const name = "時光屋小豪"
  2. const age = 18; 
  3.  
  4. function sayHello(name) { 
  5.   console.log("hello" + name); 

main.js

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

執行node main.js之后,會看到

這是因為在當前main.js模塊內,沒有發現name這個變量;

這點與我們前面看到的明顯不同,因為Node中每個js文件都是一個單獨的模塊。

那么如果要在別的文件內訪問bar.js變量

  • bar.js需要導出自己想要暴露的變量、函數、對象等等;
  • main.js從bar.js引入想用的變量、函數、對象等等;

3.3 exports導出

exports是一個對象,我們可以在這個對象中添加很多個屬性,添加的屬性會導出。 

bar.js文件導出:

  1. const name = "時光屋小豪"
  2. const age = 18; 
  3.  
  4. function sayHello(name) { 
  5.   console.log("hello" + name); 
  6.  
  7. exports.name = name
  8. exports.age = age; 
  9. exports.sayHello = sayHello; 

main.js文件導入:

  1. const bar = require('./bar'); 
  2.  
  3. console.log(bar.name);  // 時光屋小豪 
  4. console.log(bar.age);   // 18 

其中要注意的點:

 main.js中的bar變量等于exports對象;

  1. bar = exports 
  • 所以我們通過bar.xxx來使用導出文件內的變量,比如name,age;
  • require其實是一個函數,返回值是一個對象,值為“導出文件”的exports對象;

3.4 從內存角度分析bar和exports是同一個對象

在Node中,有一個特殊的全局對象,其實exports就是其中之一。

如果在文件內,不再使用exports.xxx的形式導出某個變量的話,其實exports就是一個空對象。

模塊之間的引用關系

  • 當我們在main.js中require導入的時候,它會去自動查找特殊的全局對象exports,并且把require函數的執行結果賦值給bar;
  • bar和exports指向同一個引用(引用地址相同);
  • 如果發現exports上有變量,則會放到bar對象上,正因為這樣我們才能從bar上讀取想用的變量;

為了進一步論證,bar和exports是同一個對象: 

我們加入定時器看看

所以綜上所述,Node中實現CommonJS規范的本質就是對象的引用賦值(淺拷貝本質)。

把exports對象的引用賦值bar對象上。

  • CommonJS規范的本質就是對象的引用賦值

3.5 module.exports又是什么?

但是Node中我們經常使用module.exports導出東西,也會遇到這樣的面試題:

module.exports和exports有什么關系或者區別呢?

3.6 require細節

require本質就是一個函數,可以幫助我們引入一個文件(模塊)中導入的對象。

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

3.7 require模塊的加載順序

結論一: 模塊在被第一次引入時,模塊中的js代碼會被運行一次

  1. // aaa.js 
  2. const name = 'coderwhy'
  3.  
  4. console.log("Hello aaa"); 
  5.  
  6. setTimeout(() => { 
  7.   console.log("setTimeout"); 
  8. }, 1000); 
  1. // main.js 
  2. const aaa = require('./aaa'); 

aaa.js中的代碼在引入時會被運行一次

結論二:模塊被多次引入時,會緩存,最終只加載(運行)一次

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

ccc中的代碼只會運行一次。

為什么只會加載運行一次呢?

  • 每個模塊對象module都有一個屬性:loaded;
  • 為false表示還沒有加載;
  • 為true表示已經加載;

結論三:如果有循環引入,那么加載順序是什么?

如果出現下面模塊的引用關系,那么加載順序是什么呢? 

  • 這個其實是一種數據結構:圖結構;
  • 圖結構在遍歷的過程中,有深度優先搜索(DFS, depth first search)和廣度優先搜索(BFS, breadth first search);
  • Node采用的是深度優先算法:main -> aaa -> ccc -> ddd -> eee ->bbb;

多個模塊的引入關系

四.module.exports

4.1 真正導出的是module.exports

以下是通過維基百科對CommonJS規范的解析:

  • CommonJS中是沒有module.exports的概念的;
  • 但是為了實現模塊的導出,Node中使用的是Module的類,每一個模塊都是Module的一個實例module;
  • 所以在Node中真正用于導出的其實根本不是exports,而是module.exports;
  • exports只是module上的一個對象

但是,為什么exports也可以導出呢?

  • 這是因為module對象的exports屬性是exports對象的一個引用;
  • 等價于module.exports = exports = main中的bar(CommonJS內部封裝);

4.2 module.exports和exports有什么關系或者區別呢?

聯系:module.exports = exports

進一步論證module.exports = exports

  1. // bar.js 
  2. const name = "時光屋小豪"
  3.  
  4. exports.name = name
  5.  
  6. setTimeout(() => { 
  7.   module.exports.name = "哈哈哈"
  8.   console.log("bar.js中1s之后", exports.name); 
  9. }, 1000); 
  1. // main.js 
  2. const bar = require("./bar"); 
  3.  
  4. console.log("main.js", bar.name); 
  5.  
  6. setTimeout((_) => { 
  7.   console.log("main.js中1s之后", bar.name); 
  8. }, 2000); 

在上面代碼中,只要在bar.js中修改exports對象里的屬性,導出的結果都會變,因為即使真正導出的是 module.exports,而module.exports和exports是都是相同的引用地址,改變了其中一個的屬性,另一個也會跟著改變。

注意:真正導出的模塊內容的核心其實是module.exports,只是為了實現CommonJS的規范,剛好module.exports對exports對象使用的是同一個引用而已

區別:有以下兩點

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

  • module.exports 也就和 exports沒有任何關系了;
  • 無論exports怎么改,都不會影響最終的導出結果;
  • 因為module.exports = { xxx }這樣的形式,會在堆內存中新開辟出一塊內存空間,會生成一個新的對象,用它取代之前的exports對象的導出
  • 那么也就意味著require導入的對象是新的對象;

圖解module.exports和exports的區別

講完它們兩個的區別,來看下面這兩個例子,看看自己是否真正掌握了module.exports的用法

4.3 關于module.exports的練習題

練習1:導出的變量為值類型

  1. // bar.js 
  2. let name = "時光屋小豪"
  3.  
  4. setTimeout(() => { 
  5.   name = "123123"
  6. }, 1000); 
  7.  
  8. module.exports = { 
  9.   namename
  10.   age: "20"
  11.   sayHello: function (name) { 
  12.     console.log("你好" + name); 
  13.   }, 
  14. }; 
  1. // main.js 
  2. const bar = require("./bar"); 
  3.  
  4. console.log("main.js", bar.name); // main.js 時光屋小豪 
  5.  
  6. setTimeout(() => { 
  7.   console.log("main.js中2s后", bar.name); // main.js中2s后 時光屋小豪 
  8. }, 2000); 

練習2:導出的變量為引用類型

  1. // bar.js 
  2. let info = { 
  3.   name"時光屋小豪"
  4. }; 
  5.  
  6. setTimeout(() => { 
  7.   info.name = "123123"
  8. }, 1000); 
  9.  
  10. module.exports = { 
  11.   info: info, 
  12.   age: "20"
  13.   sayHello: function (name) { 
  14.     console.log("你好" + name); 
  15.   }, 
  16. }; 
  1. // main.js 
  2. const bar = require("./bar"); 
  3.  
  4. console.log("main.js", bar.info.name); // main.js 時光屋小豪 
  5.  
  6. setTimeout(() => { 
  7.   console.log("main.js中2s后", bar.info.name); // main.js中2s后 123123 
  8. }, 2000); 

從main.js輸出結果來看,定時器修改的name變量的結果,并沒有影響main.js中導入的結果。

  • 因為name為值類型,基本類型,一旦定義之后,就把其屬性值,放到了module.exports的內存里(練1)
  • 因為info為引用類型,所以module.exports里存放的是info的引用地址,所以由定時器更改的變量,會影響main.js導入的結果(練2)

五.CommonJS的加載過程

CommonJS模塊加載js文件的過程是運行時加載的,并且是同步的: 

  • 運行時加載意味著是js引擎在執行js代碼的過程中加載模塊;
  • 同步的就意味著一個文件沒有加載結束之前,后面的代碼都不會執行;
  1. const flag = true
  2.  
  3. if (flag) { 
  4.   const foo = require('./foo'); 
  5.   console.log("等require函數執行完畢后,再輸出這句代碼"); 

CommonJS通過module.exports導出的是一個對象:

  • 導出的是一個對象意味著可以將這個對象的引用在其他模塊中賦值給其他變量;
  • 但是最終他們指向的都是同一個對象,那么一個變量修改了對象的屬性,所有的地方都會被修改;

六.CommonJS規范的本質

CommonJS規范的本質就是對象的引用賦值

后續文章

《JavaScript模塊化——ES Module》

在下一篇文章中,

  • 會重點講解ES Module規范的一切;
  • 及CommonJS和ES Module是如何交互的;
  • 類比CommonJS和ES Module優缺點,如何完美的回答這道面試題;

 【編輯推薦】

 

責任編輯:姜華 來源: 前端時光屋
相關推薦

2020-11-06 09:24:09

node

2020-10-12 14:59:31

V8引擎如何執行Jav

2021-08-11 07:54:47

Commonjs

2023-12-07 08:07:47

Node流程代碼

2015-03-10 10:59:18

Node.js開發指南基礎介紹

2010-07-26 12:57:12

OPhone游戲開發

2021-03-16 08:54:35

AQSAbstractQueJava

2011-07-04 10:39:57

Web

2015-07-16 09:59:55

PHP Node.js討論

2013-03-11 10:10:03

2020-08-31 15:00:17

Node.jsrequire前端

2019-12-17 11:40:44

Node.js模塊前端

2010-07-26 13:55:10

OPhone游戲開發

2021-09-26 05:06:04

Node.js模塊機制

2019-01-07 15:29:07

HadoopYarn架構調度器

2021-07-20 15:20:02

FlatBuffers阿里云Java

2012-05-21 10:06:26

FrameworkCocoa

2017-07-02 18:04:53

塊加密算法AES算法

2021-10-16 05:00:32

.js Buffer模塊

2022-09-26 09:01:15

語言數據JavaScript
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 国产99久久精品一区二区永久免费 | 亚洲国产成人精品女人久久久野战 | 精品乱码一区二区三四区 | 国产精品一区二区欧美黑人喷潮水 | www.久久| 国产精品一区二区久久 | 亚洲va欧美va人人爽午夜 | 精品欧美一区二区三区免费观看 | 中文字幕乱码亚洲精品一区 | 日本一区二区在线视频 | 国产精品高清一区二区三区 | 看毛片网站 | 精品国产区 | 成人综合伊人 | 国产日韩一区二区三区 | 黑人巨大精品 | 在线成人免费视频 | 亚洲视频区 | 中文字幕在线免费 | 九九热这里 | 污书屋| 亚州视频在线 | 国产精品久久久久久久久久久免费看 | 国产精品毛片一区二区三区 | 欧美在线色| av乱码| 美女午夜影院 | 精品国产一区二区三区久久久四川 | 中文字幕成人在线 | 日本久久久久久 | 国产 欧美 日韩 一区 | 久久小视频 | 午夜小电影 | 国产在线精品一区 | 涩涩视频在线观看 | 精品国产一区二区在线 | 又黑又粗又长的欧美一区 | 成年免费大片黄在线观看岛国 | 欧美精品久久久久久久久久 | 操操日 | 亚洲va欧美va人人爽午夜 |