<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      [NodeJS] JavaScript模塊化

      JavaScript誕生于1995年,一開始只是用于編寫簡單的腳本。

      隨著前端開發任務越來越復雜,JavaScript代碼也越來越復雜,全局變量沖突、依賴管理混亂等問題變得十分突出,模塊化成為一個必不可少的功能。

      模塊化發展史與方案對比

      YUI 與 JQuery

      2006年,雅虎開源了組件庫YUI Library,使用類似于Java的命名空間的方式來管理模塊:

      YUI.util.module.doSomthing();
      

      同年,JQuery發布,使用IIFE和閉包的方法創建私有作用域,避免全局變量污染,這種管理模塊的方法流行了一段時間。

      (function(root) {
          // 模塊內部變量和函數
          var data = 'Module Data';
          function getData() {
              return data;
          }
          
          // 將模塊接口掛載到全局對象上
          root.myModule = {
              getData: getData
          };
      })(window);
      
      // 使用模塊
      console.log(myModule.getData()); // 輸出: Module Data
      

      當一個模塊依賴于其它模塊時:(ModuleB依賴于ModuleA和ModuleC)

      (function(root, moduleA, moduleC) {
          // 模塊B代碼
      })(window, window.moduleA, window.moduleC);
      

      這種方法有以下缺點:

      1. 模塊掛載到了全局對象上,仍然可能存在沖突;
      2. 缺乏標準化,不同程序員對這種方案的實現可能不同;
      3. 依賴管理困難,當依賴模塊量比較大時,手動傳遞參數容易出錯。

      ServerJS, CommonJS 和 Node.js

      2009年1月,Mozilla 的工程師制定了一套JavaScript模塊化的標準規范,取名為ServerJS,規范版本為Modules/0.1。

      同年4月,ServerJS更名為CommonJS

      ServerJS最初用于服務端的JS模塊化,用于輔助自動化測試工作。

      在Node.js出現之前也有運行于服務端的JS,叫做Netscape Enterprise Server,它并不像Node.js,后者擁有訪問操作系統文件系統,以及網絡I/O等能力;而前者更像是早期的php,只是用于在服務端填充模板。更詳細的介紹可以看??Server-side JavaScript a decade before Node.js with Netscape LiveWire - DEV Community
      下面這段代碼摘自鏈接里的文章。

      <!-- Welcome to mid-90s HTML. 
      Tags are SCREAMED, because everybody is very excited about THE INTERNET. -->
      <HTML>
        <HEAD>
          <TITLE>My awesome web app</TITLE>
        </HEAD>
          <BODY>  
          <H1>
            <SERVER>
            /* This tag and its content will be processed on the server side,
            and replaced by whatever is passed to `write()` before being sent to the client. */
            if(client.firstname != null) {
              write("Hello " + client.firstname + " !")  
            }
            else {
              write("What is your name?")
            }
            </SERVER>
          </H1>
      
          <FORM METHOD="post" ACTION="app.html">
            <P>
              <LABEL FOR="firstname">Your name</LABEL>
              <INPUT TYPE="text" NAME="firstname"/>        
            </P>
      
            <P>
              <INPUT TYPE="submit" VALUE="Send"/>
            </P>
          </FORM>
        </BODY>  
      </HTML>
      

      同年8月,Node.js閃亮登場,但是還沒有包管理器,外部依賴需要手動下載到項目文件夾中。

      而包管理器的設計則需要考慮到使用什么模塊化方案。Node.js的作者最終選擇了同年剛提出的CommonJS作為npm的模塊化方案,此時的CommonJS已經采用了Modules/1.0版本的標準規范:Modules/1.0 - CommonJS Spec Wiki

      需要注意到此時的CommonJS模塊化方案是針對運行在服務端的Node.js準備的,瀏覽器的JS出于以下原因并不能使用CommonJS:

      1. 同步加載模塊:CommonJS使用同步的require函數加載依賴模塊,但是在瀏覽器環境中,網絡請求加載模塊文件是異步操作,無法同步執行;
      2. 缺乏模塊依賴管理:Node.js可以直接從本地文件系統或node_modules文件夾加載模塊,而瀏覽器無法自動處理模塊的查找和加載;
      3. 沒有內置模塊加載器:Node.js有內置的模塊加載器,可以處理模塊的解析、加載和緩存,但是瀏覽器沒有類似的內置機制。

      社區意識到為了可以在瀏覽器環境中使用CommonJS,必須制訂新的標準規范,但是這個時候社區人員的思路出現了分歧,開始出現了不同的流派,也是從這個時候開始,出現了很多不同的模塊化方案。

      方向1:browserify

      社區的其中一種思路是在打包代碼的時候將CommonJS的模塊語法轉換成瀏覽器可以運行的代碼。

      相關的規范是 Modules/Transport規范,用于規定模塊如何轉譯。

      ??Modules/Transport - CommonJS Spec Wiki

      這個方向的著名產物是 Browserify

      方向2:AMD

      這種思路是:由于CommonJS的require函數是同步加載模塊的,沒辦法在瀏覽器環境應用,那么我們就異步地加載模塊

      AMD是 Async Module Definition的簡稱,即異步模塊定義。

      James Burke在2009年9月開發了RequireJS這個模塊加載器,可以異步地加載依賴。

      這個方向產生了AMD標準規范:Modules/AsynchronousDefinition - CommonJS Spec Wiki

      除了使用require引入依賴,還需要一個新的全局函數:define來注冊依賴。

      使用方法大致如下:

      // module1.js
      define(function() {
          return {
              data: 'Module 1 Data',
              getData: function() {
                  return this.data;
              }
          };
      });
      
      // main.js
      require(['module1'], function(module1) {
          console.log(module1.getData()); // 輸出: Module 1 Data
      });
      
      

      方向3:CMD

      2011年4月,阿里巴巴的玉伯借鑒了CommonJSAMD等模塊化方案后,寫出了SeaJS,在此基礎上,提出了 Common Module Definition 簡稱CMD規范。

      CMD規范的主要內容和AMD規范差不多,主要差別是CMD保留了CommonJS中最重要的延遲加載就近聲明特性。

      延遲加載

      CMD 模塊依賴是延遲加載的,只有在需要時才會加載依賴的模塊。而 AMD 在定義模塊的時候就需要加載依賴的模塊了。

      就近聲明

      CMD在define的回調函數中使用require函數聲明依賴,而AMD中模塊的依賴是在函數參數列表中顯式聲明的。


      AMD示例

      define(['dep1', 'dep2'], function(dep1, dep2) {
          dep1.doSomething();
          dep2.doSomething();
      });
      
      

      CMD示例

      define(function(require, exports, module) {
          // 依賴在使用時才加載
          var dep1 = require('./dep1');
          var dep2 = require('./dep2');
      
          exports.foo = function() {
              dep1.doSomething();
              dep2.doSomething();
          };
      });
      

      嘗試統一:UMD

      2014年9月,UMD被提出(Universal Module Definition),本質上不是模塊化方案,只是將CommonJS和AMD進行結合。

      通過檢查全局作用域下是否存在exportsdefine方法以及全局對象上是否定義了依賴,來決定采用哪一種方法加載模塊。

      官方方案:ES6

      到了2016年,ES6標準發布,引入了importexport兩個關鍵字,提供了 ES Module 模塊化方案。

      ESM可以在編譯時進行靜態解析和優化,并且在后續版本中支持了動態導入(異步)。

      時間線圖

      modules.drawio

      總結

      模塊方案 特點 優點 缺點
      IIFE + 閉包 IIFE 結合閉包,實現模塊化 簡單直接,不依賴特定標準或工具 依賴管理復雜,易產生全局變量沖突,代碼維護性和可讀性較差
      CommonJS 用于服務器端,同步加載 簡單直觀,廣泛支持 不適合瀏覽器,需要工具轉換
      AMD 設計用于瀏覽器,異步加載 提高性能,依賴并行加載 語法復雜,代碼冗長
      CMD 類似 AMD,更靈活,支持延遲加載 靈活性高,依賴就近聲明和延遲加載 使用較少,需要工具支持
      UMD 兼容 CommonJS 和 AMD,通用模塊定義 兼容性強,可在不同環境中使用 代碼復雜,處理環境差異
      ESM ES6 標準模塊系統,靜態分析 原生支持,靜態分析,語法簡潔 無?

      目前主流的方案是CommonJS和ESM,前者是因為歷史原因,使用范圍廣,至今仍有許多代碼使用CommonJS;而后者是官方給出的方案,簡潔高效。

      現在有許多轉譯工具例如:babeltypescript等等,可以將CommonJS轉換成瀏覽器可以執行的形式,因此在前端項目中使用哪一種方案已經不是特別重要了。


      Node.js 中的 CommonJS

      在Node.js中每個文件都被看做一個模塊,內部成員不會污染全局作用域,可以使用require函數引入其它模塊,也可以使用module.exports導出。

      模塊類型

      • Node.js核心模塊:fshttpnet等等;
      • 開發者模塊:本地的文件;
      • 第三方模塊:通常是使用 npm 安裝到 node_modules 文件夾下的模塊。

      Node.js只會將.js.json.node文件視為模塊,其它文件格式需要額外安裝其它依賴進行轉換,比如.ts

      require執行過程

      解析文件路徑 Resolve

      require作為一個函數,傳入一個字符串,首先要解析這個字符串代表哪一個模塊。

      這個解析的過程是調用了require.resolve()函數。

      解析過程如下:

      1. 嘗試查詢該名稱的Node.js核心模塊;
      2. 如果以./或者../開頭,那么嘗試解析URL,加載開發者模塊(即查詢文件);
      3. 如果找不到 2. 的文件,那么嘗試將字符串視為文件夾,查找文件夾內部的index.js
      4. 如果還沒找到,則去node_modules文件夾內部查找指定模塊并加載;
      5. 如果還沒找到,則拋出異常。

      對于同名但不同格式的文件,匹配順序是:js > json > node

      對于node_modules里的第三方模塊(文件夾),是先去查找package.json里的main字段配置的入口文件,如果沒有package.json文件或者沒有配置main字段,則找文件夾內部的index

      其實還有很多細節這里忽略了,可以去官網看詳細的匹配規則偽代碼(很長):Modules: CommonJS modules | Node.js v22.4.1 Documentation (nodejs.org)

      包裝 Wrapping

      經過上一個步驟我們已經查找到了模塊對應的文件。

      眾所周知,代碼文件都是文本文件,可以將代碼讀出得到一個字符串。

      在這一步,模塊的代碼會被包裝成一個函數,因此可以訪問特殊對象(注入)

      (function(exports, require, module, __filename, __dirname){
          // ... Module Code
      })();
      

      好處:模塊的頂層代碼不會泄露。

      我們可以寫一個簡單的小案例來測試這個包裝步驟:

      // module.js
      console.log(arguments);
      
      // ========================================
      // index.js
      require('./module');
      

      我們在index.js中引入module.js這個模塊,而在module.js中,我們只做一件很簡單的事情,那就是直接輸出arguments對象。

      執行node index.js,得到以下結果:

      image-20240709165000660

      這五個對象分別就是exportsrequiremodule__filename__dirname

      這個文件目前沒有導出內容,所以可以看到第一個對象,和第三個對象module.exports是空對象。

      接下來我們嘗試導出點內容:

      // module.js
      exports.msg = 'msg from exports'
      
      module.exports = {
        msg: 'msg from module.exports'
      }
      
      console.log(arguments);
      
      //==============================================
      // index.js
      const foo = require('./module');
      
      console.log('index.js receive: ' + foo.msg);
      
      image-20240709172434574

      在模塊加載的時候,exports默認指向module.exports,也就是說我們要導出內容的時候,既可以通過exports,也可以通過module.exports最終require返回的是module.exports

      通常在這個環節如果發生了意想不到的情況,大部分問題都是由賦值操作導致的。

      exports如果被重新賦值,那么就和module.exports不是同一個引用了。

      exports和module.exports的使用注意事項

      特性 module.exports exports
      初始化 默認為一個空對象 {},可以被賦值為任何其他對象或值。 在模塊加載時,默認與 module.exports 相同,可以通過賦值語句修改其引用。
      賦值方式 直接賦值為需要導出的對象或值,可以是任何有效的 JavaScript 對象或函數。 通過 exports 的屬性進行賦值,例如 exports.foo = ...。
      重新賦值影響 若直接賦值新對象給 module.exports,會覆蓋原有的導出對象。 若重新賦值新對象給 exports,只是修改了 exports 的引用,不會影響 module.exports。
      適用場景 適合導出單個對象、函數或類。 適合在導出多個方法或屬性時的簡便語法。
      注意事項 賦值給 module.exports 必須在模塊加載時立即完成,不能在回調中完成賦值操作。 賦值給 exports 只是修改了 exports 變量的引用,而不會改變 module.exports 的引用,因此需要注意不要混淆或誤用。

      require對象

      image-20240709175811687

      require是一個函數,當然我們都知道在JS中函數也是對象,require攜帶了一些很重要的屬性:

      • resolve一個函數,require就是使用這個函數來將模塊字符串解析為文件的具體路徑的;

      • main:我們知道每一個.js文件既可以被視為模塊,又可以被視為主程序使用node指令啟動,那么被node xxx.js執行的這個xxx.js的相關信息,就被記錄到了main這個對象里,在這個案例里是index.js

      • extensions:以前用來將非js模塊加載到Node.js中,已廢棄,不要使用,否則可能導致bug或者性能下降

      • cache:模塊的緩存記錄,這里有個細節,當前模塊還沒有執行完成,但是已經存在一條緩存記錄了,只不過當前的緩存對象可能不是最終結果(取決于console.logmodule.exports的執行順序),這個現象在下文的循環依賴問題會重點討論。

        注意:緩存的key是文件路徑,而不是傳給require的那個字符串,因此每次調用require引入已緩存的模塊仍需要走一次resolve環節獲得文件的路徑。

      執行 Execution

      由Node.js運行時執行模塊代碼。

      返回 Exports

      require函數返回值是module.exports

      緩存 Cache

      對于一個模塊來說,包裝和執行過程只會被執行一次,然后會被緩存。

      后續對相同模塊的require只會走一個解析文件路徑的環節,然后就返回緩存。

      循環依賴問題 Cycles

      graph TD index.js --> a.js a.js --> b.js b.js --> a.js

      如圖,模塊a和模塊b相互依賴,為了避免無窮循環,Node.js使用其緩存機制來處理這種問題。

      官方文檔案例

      a.js:

      console.log('a starting');
      exports.done = false;
      const b = require('./b.js');
      console.log('in a, b.done = %j', b.done);
      exports.done = true;
      console.log('a done'); COPY
      

      b.js:

      console.log('b starting');
      exports.done = false;
      const a = require('./a.js');
      console.log('in b, a.done = %j', a.done);
      exports.done = true;
      console.log('b done'); COPY
      

      main.js:

      console.log('main starting');
      const a = require('./a.js');
      const b = require('./b.js');
      console.log('in main, a.done = %j, b.done = %j', a.done, b.done); COPY
      

      輸出順序

      main starting
      a starting
      b starting
      in b, a.done = false
      b done
      in a, b.done = true
      a done
      in main, a.done = true, b.done = true
      

      解析

      1. main.js執行require('./a.js')的時候,就已經創建一個Module對象并添加到緩存里了,只不過此時a的Module對象的exports還是空的;
      2. 接下來執行a.js的模塊代碼,exports.done賦值為false
      3. 執行到require('./b.js'),發現沒有相應的緩存,創建b的Module對象并添加到緩存,此時b的exports還是空對象;
      4. 執行b.js的模塊代碼,exports.done賦值為false
      5. 執行到require('./a.js'),發現有緩存,直接返回模塊a的module.exports對象,此時的donefalse
      6. 注意,此時是直接返回模塊a的module.exports對象,沒有去重新執行a.js的代碼,因此避免了循環。
      7. 后續就是b.js完成了模塊代碼執行,a.js完成剩余代碼執行,main.js完成剩余代碼執行,程序結束。
      graph TD main.js ==> a.js a.js ==> b.js b.js ==> check check -.-> |無緩存|a.js check ==> |有緩存|模塊a的module.exports

      參考文章

      [1] 《編程時間簡史系列》JavaScript 模塊化的歷史進程 - 編程時間簡史 - SegmentFault 思否

      [2] Modules: CommonJS modules | Node.js v22.4.1 Documentation (nodejs.org)

      [3] javascript - 深入Node.js的模塊加載機制,手寫require函數 - 進擊的大前端 - SegmentFault 思否

      [4] JavaScript 模塊的循環加載 - 阮一峰的網絡日志 (ruanyifeng.com)

      posted @ 2024-07-09 21:29  feixianxing  閱讀(226)  評論(2)    收藏  舉報
      主站蜘蛛池模板: 日本久久99成人网站| 男女激情一区二区三区| 美女自卫慰黄网站| 欧美牲交a欧美牲交aⅴ图片| 欧美一本大道香蕉综合视频| 97成人碰碰久久人人超级碰oo| 国产午夜福利精品视频| 欧美日韩性高爱潮视频| 亚洲婷婷综合色香五月| 成年无码av片在线蜜芽| 国产精品久久久久久福利69堂| 波多野结衣美乳人妻hd电影欧美| 亚洲综合一区二区国产精品 | 久久香蕉欧美精品| 国产亚洲tv在线观看| 亚洲中文字幕日产无码成人片| 午夜成人无码免费看网站| 九九综合九色综合网站| 一道本AV免费不卡播放| XXXXXHD亚洲日本HD| 荣成市| 69天堂人成无码免费视频| 国产成人综合色就色综合| 浦县| 午夜福利日本一区二区无码| 三级国产在线观看| 性男女做视频观看网站| 影音先锋亚洲成aⅴ人在| 欧美亚洲一区二区三区在线| 噜噜久久噜噜久久鬼88| 性色在线视频精品| 一区二区三区一级黄色片| 国产精品久久久久9999高清| 国产在线国偷精品免费看| 午夜国人精品av免费看| 日韩精品中文女同在线播放| 亚洲国产午夜精品理论片妓女| 国产剧情福利一区二区麻豆| 国产精品亚洲二区在线看| 18禁免费无码无遮挡不卡网站| 无码激情亚洲一区|