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

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

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

      Node.js中的模塊

        CommonJS模塊
        CommonJS模塊使用require() 引用模塊,它接受模塊標識作為參數,將一個模塊引入到當前運行環境中,使用exports對象,導出當前模塊的方法或者變量,并且它是唯一的導出出口。

        如果JS文件中存在 exports 或 require,該 JS文件就是一個模塊,模塊內的所有代碼均為 隱藏代碼,包括變量、函數,對其他文件不可見,也不會對全局變量造成污染。如果一個模塊需要暴露一些API給外部使用,需要通過exports 導出,exports 是一個空對象,你可以為該對象添加任何需要導出的內容。如果一個模塊需要導入其他模塊,通過require 實現,require 是一個函數,傳入模塊的路徑即可返回該模塊導出的整個內容。

        Node.js 實現了CommonJS 模塊,它主要做了三件事情,路徑的解析,文件的查找,編譯執行,就是當require一個模塊標識時,怎么才能找到模塊,并把exports對象獲取到,引入當前運行環境中。模塊標識是一個字符串,它主要有兩種情況,以'/,'./' 或'../' 為主的路徑和沒有路徑標識的字符串。如果是路徑,就直接查找路徑對應的模塊。如果不是路徑,Node.js先查找是不是核心模塊,比如fs,http,如果不是,就在當前目錄下的 node_modules 目錄查找,如果沒有,在父級目錄的 node_modules 查找,如果沒有,在父級目錄的父級目錄的 node_modules 中查找。沿著路徑向上遞歸,直到根目錄下的 node_modules 目錄

        找到路徑,它可以是一個文件,它還可以是一個文件夾。如果是一個文件,還會看有沒有后綴,如果有,它就會直接加載文件。如果沒有后比綴,它就會先查找.js,再查找.json,最后查找.node文件。 如果路徑指的是一個文件夾,它先查找有沒有package.json文件,如果有,就會找 package.json 下 main 屬性指向的文件,如果沒有 package.json ,在 node 環境下會以index.js ,index.json ,index.node。也就是說,在Node.js中,模塊可以是一個文件,也可以是一個文件夾,還可以是一個包。包就是一個文件夾中包含package.json。只要使用require方法引入的,都稱為模塊。

        找到了要加載的文件,為了隱藏模塊中的代碼,同時提供export 和require方法,nodejs 執行模塊時,會將模塊中JS代包括到一個函數中。

      (function (exports, require, module, __filename, __dirname) {
          // 模塊中的js代碼
      })

         當然,為了高效的執行,Node.js在CommonJS模塊上做了一些改進, 

        1,運行時加載:Node.js 執行到require函數時才會加載模塊并執行,然后將模塊的exports對象返回。加載模塊是同步的,只有當模塊加載完成后,才能執行后面的操作。加載,執行,返回exports對象, require就像一個普通的函數調用,把返回值exports對象賦值給一個變量。比如number.js

      let num = 1
      function add() {
          num++
      }
      exports.num = num
      exports.add = add

        在index.js中引入

      const number = require('./number.js')
      
      console.log(number.num)

        當require('./number.js')時,Node.js執行number.js,exports.num = 1; exports.add = add, 執行完畢,然后把exports對象返回,相當于把{num:1, add: add} 對象賦值給index.js中的number,require函數執行完華,number變量和模塊就沒有關系了。這時即使調用number.add() 也不會必改變number 對象中num的值,因為add函數引用的是它自己作用域中的num,而不是index.js中number對象的屬性。相反,你可以把修改number變量中num屬性的值。

      number.add()
      console.log(number.num) //1
      
      number.num = 3
      console.log(number.num) //3

        2,緩存:當require一個模塊時,會先將模塊緩存,然后執行代碼,以后再加載該模塊時,就直接從緩存中讀取該模塊。比如a.js

      console.log('a 模塊加載')
      exports.a = 'a'

        b.js

      console.log('b 模塊加載')
      const moduleA = require('./a.js');
      
      exports.b = 'b'

        index.js 

      const a = require('./a')
      const b = require('./b')

        執行index.js,可以發現a模塊只加載了一次。當b.js中再require a.js時,a.js已經緩存了,所以就沒有加載,執行了。模塊的緩存也有助于解決循環依賴。a.js改為

      const { getMes } = require('./b')
      
      console.log('我是 a 文件')
      
      exports.say = function () {
          const message = getMes()
          console.log(message)
      }

        b.js 

      const say = require('./a');
      
      console.log('我是 b 文件') 
      exports.getMes = function(){return "Hello"}

         執行index.js,先加載a.js 放入緩存中,然后執行a.js,它又加載 b.js,b.js放入緩存,并執行,它又引入a.js,因為a模塊已經在緩存中,所以直接讀取緩存中的a, 實現上緩存中的a模塊,只是一個空對象,加載完之后,b.js繼續執行,控制臺輸出"我是b文件"。b.js執行完以后,再執行a.js,輸出 “我是b文件”

        當然,也可以刪除緩存,緩存是按照路徑的形式將模塊進行緩存,放到 require.cache對象上。通過delete require.cache[modulePath]將緩存的模塊刪除。需要注意的是modulePath是絕對路徑。delete require.cache[path.resolve('./myclass')];

        3,exports 和 module.exports。CommonJS模塊化只規定exports對象向外導出。想要導出什么,只給exports對象,添加屬性,但只想導出方法,對象就有點麻煩,所以Node.js 可以直接使用module.exports 進行導出。

      (function(module){
         module.exports = {};
         var exports = module.exports
         // a.js 寫入的代碼
         exports.a = 'a';
       
         return module.exports;
      })()

        到了commonjs2,module.exports也可以導出。exports只是module.exports的引用,相當于exports = modules.exports ={},整個模塊只暴露modules.exports指向的一個對象出去,exports只能用來添加屬性,所以exports 不能再被賦值給任何對象,即使賦值了,它就不能指向module.exports了,打破了引用,也就不能export出去(module.exports 是真正暴露出的對象),要想export一個對象出去,只能給module.exports賦值。exports.myFun 就是module.exports.myFunc.

        ES模塊

        ES模塊是ECMAScript官方推出的模塊化解決方案,它吸取CommonJS的優點,一個JS文件就是一個獨立的模塊,模塊內部的變量都是私有化的,其他模塊無法訪問;要想讓其它模塊進行訪問,就要暴露出去,其它模塊需要引入才能使用。但語法更簡潔

        1,使用export 導出模塊,不僅可以export對象,還可以export 變量,函數等,其實,在ES模塊下,可以導出任意的標識符

      export const a = 'a';
      export function sayHello() { console.log('hello , world') }

        export導出的是標識符,也就是內存地址,而不是值,所下面兩種是錯誤的寫法:

      // 報錯,是個值=
      export 1;
      
      var m = 1;
      export m;  

        2,使用 import并配合 from關鍵詞進行導入。注意,from后面的文件名要加后綴。

      import { a, sayHello } from'./a.js' //引入的文件要加后綴名

        import 導入的就是變量名, 相當于內存地址,因此,導入的是只讀引用,不能修改a 和sayHello 的值。a=1就會報錯。也正因為是import的是變量名,導出模塊內部的變量一旦發生變化,對應導入模塊的變量也會跟隨變化。假設模塊導出一個變量名 title 和 setTitle函數,setTitle能更改title:

      export let title = 'java';
      export function setTitle(newValue) {
          title = newValue;
      }

        如果要導入兩者,則可以通過調用 setTitle 間接更改 title 的值

      import { title, setTitle } from './title_master'
      console.log(title);  //  "Java"
      setTitle('Script')
      console.log(title) // "Script"

        3,以上的import 和export 稱為有名字的import和 export。ES模塊還定義default export和import。就是導出的時候,使用 export default,

      export default class Logger {
          log (message) {
              console.log(`${message}`)
          }
      }

        導出是default, 而不是Logger,導出的內容被注冊到default上,所以后面的logger 被忽略了, 正是由于導出的是default,所以export default 后面跟的是值,而不是變量, default在某種意義上來說,可以稱為變量聲明了,所以需要提供值。

      export default 1; // 正確
      export default const a = 1; // 錯誤

        導出default 還有一個影響, 就是,一個模塊中只能有一個默認的導出。默認導出的import 是

      import MyLogger from './logger.js'

        導入的時候,不加{}, 并且可以隨意命名變量。實際上在內部,模塊導出的就是default

      import * as loggerModule from './logger.js'
      console.log(loggerModule) // [Module] { default: [Function: Logger] }

        但你不能是用 import {default} from './logger.js',語法錯誤,default是關鍵字。

        有時您只是想導入一個模塊來產生副作用,這意味著您希望執行模塊中的代碼,但不需要引用來使用該模塊中的任何值。

      import './google_analytics'

        4,模塊標識符(要import的模塊的位置):

          相對路徑標識符,就是 ‘./a.js’, '../a.js'

          絕對路徑標識符: file:// 本地url, 比如"file:///opt/nodejs/config.js" , ES 模塊絕對路徑標識符,只有這一種格式,直接使用/ 或// 作為絕對路徑不起作用

          無路徑標識符,就是node 核心模塊和node_modules中的第三方包,比如 fs, http 和fastify

          深度import 標識符,比如fastify/lib/logger.js。node_modules中fastify下的lib下的logger.js

        5,ES模塊的加載方式是靜態化的,只有在編譯時加載,不會在執行時加載,也就是說引入模塊的語句必須在模塊的最頂層,不能在任何控制語句中。并且引入的模塊名稱也只能是常量字符串,不能是需要運行期動態求值的表達式,因為編譯不會運算求值。靜態化加載,也有好處,比如也可以是異步的。如果想要動態加載模塊,只能調用import()函數,它接受模塊標識符作為參數,返回一個promise, promise  resolve之后,就是模塊對象。

        Node 14中實現了ES 模塊,來看一下它是怎么解析和執行ES模塊的? 解析入口文件,生成模塊記錄(Module Record),知道import模塊,尋找模塊,再解析成Module Record,深度優先遍歷,構建整個項目的模塊依賴圖(dependency graph),根據module Record 構建Module instance,建立各個模塊之間的依賴關系。 這個過程又分為三個階段

        1,構建或解析階段:根據路徑,加載模塊,解析成Module Record。加載入口文件,生成Module Record,識別它的依賴,根據依賴路徑,加載依賴模塊,再生成Module Record,再加載依賴,層層遞進,深度優先,直到依賴沒有依賴為止。

        加載依賴的過程中,會把加載完成的依賴(Module Record)緩存起來,放到module map中, 如果以后再加載相同的依賴,就不執行加載操作,所以每一個模塊只會加載一次,

       

        最終形成完整的module record的依賴樹。

        2, 實例化階段:從依賴樹的最底端module record 開始,JS引擎會為每一個module record 創建模塊環境記錄(module environment record) 管理里面的變量,同時,為export出去的變量的內存中找一塊空間,沿著依賴樹向上,module record中 有import 也有export, 先為export變量在內存中找空間,然后再為import 的依賴建立聯系。由于import 的模塊在依賴樹的底層,我們是從是樹底層向上走的,所以import的依賴都已經export 出去了,只要為import 找到export 就可以了,import和export都指向內存的同一位置。這一個過程是一個樹的后序遍歷的過程。

        3,執行階段:執行代碼,也是按照后序遍歷的順序,從下向上,依次執行每一個module instance中的代碼,得到的值放到export 指向的內存中的位置,每一個模塊的代碼只執行一次。此時,可以從入口文件開始執行代碼,整個項目開始執行。

        這三個階段相互分離,在構建完整個依賴圖之前,沒有代碼會執行,因此模塊的導入或導出都是靜態的。

        理解了模塊的加載,看一下ES模塊是怎么處理循環依賴的?

      // a.js
      import * as bModule from './b.js'
      export let loaded = false
      export const b = bModule
      loaded = true
      
      // b.js
      import * as aModule from './a.js'
      export let loaded = false
      export const a = aModule
      loaded = true
      
      // main.js
      import * as a from './a.js'
      import * as b from './b.js'
      console.log('a ->', a)
      console.log('b ->', b)

        解析階段:node main.js,main.js就是入口文件。main.js 加載a.js, a.js加載b.js,b.js再加載a.js,因為,a.js已經加載過了,就不加載了,它也沒有其它import,直接返回到a.js,a.js也沒有其它import,就返回到main.js,main.js再加載b.js,由于b.js已經加載過了,也就不加載了,注意,這里只執行inport 加載,不執行代碼。

        2, 實例化階段,根據第一階段得到的依賴樹,從下到上遍歷,對于每一個模塊,解釋器找到所有export出來的屬性,然后,再建立build out a map of the exported names
      in memory:

       

       從b.js開始,它export出了loaded 和a, 再到a.js,它也export出了loaded 和b,最后到main.js, 它沒有export什么。注意,圖中exports 射只track export出來的名字,值沒有初始化。再link the exported names to the modules importing them

       

         所有的值仍然是未初始化的。

        執行階段,每一個文件中的所有代碼最終執行。執行順序,也是沿著依賴圖從下到上執行。b.js先執行,loaded設為false,a指向代表a.js模塊的aModule, loaded再調為true. 至此b.js執行完了,再執行a.js, loaded設為false,b指向模塊b.js,loaded重置為御true, a.js也執行完了,再執行main.js, 所有export出來的屬性都執行完了,引入的模塊a, b 都是引用,直接找到a, b 進行輸出。在ES 模塊中,每一個模塊都能引用到其它模塊實時更新的或最新的狀態。

       

        模塊使用

        Node.js 中同時存在兩種模塊機制,要怎么使用呢?.js文件默認是CommonJS模塊(語法),不能使用ESM語法,否則報錯。要想使用ESM語法,可以把文件命名為.mjs,或者文件名是.js, 但在項目的package.json中加個type字段, 值為"module", "type": "module",為了后者進行對應,CommonJS解析也進行了相應的變化,1,文件以.cjs結尾,2,如果有package.json, package.json中沒有type 字段或type 字段設為comonjs, 以 .js結尾的文件以CommonJS 解析。因此,Node.js 團隊強烈建議包的作者在package.json文件中注明type 字段

      {
        "type": "module"
      }

        當使用CommonJS時,Node向模塊中注入了__dirname, __firename 等全局對象。但ES模塊是通過 import/export關鍵詞來實現,沒有對應的函數包裹,所以在 ES模塊中,沒法使用這些CommonJS對象。但可能通過import.meta來獲取到當前文件的URL的引用。import.meta是 ECMA 實現的一個包含模塊元數據的特定對象,主要用于存放模塊的 url,而 node 中只支持加載本地模塊,所以 url 都是使用 file:協議。import.meta.url is a reference to the current module
      file in a format similar to file:///path/to/current_module.js. This value can be
      used to reconstruct __filename and __dirname in the form of absolute paths:

      import { fileURLToPath } from 'url';
      import {dirname} from 'path';
      
      const __firname = fileURLToPath(import.meta.url);
      const __dirname = dirname(__firname);

        在ES模塊文件中,可以引用CommonJS模塊的內容,使用default import可以import進來CommonJS模塊exports出來的整個對象, 使用name import 可以單獨import 進來CommonJS模塊exports出來的某個屬性。假設cmj.js中 exports.a = 3; 在m.mjs 中,

      import aa from './cmj.js' // 整個對象 {a: 3}
      import {a} from './cmj.js' // 單個屬性 a, 3

        在CommonJS模塊文件中,也可以引用ES 模塊中的內容, 不過,不能使用require, 而是使用import()函數,動態加載。

      import('./m.mjs').then(data => {
          console.log(data) // [Module] { a: 3 }
      })

        有些包還包含子包,除了直接引用整個包外,Node.js還允許我們引用包里的某個模塊。這時require 或import接受的文件標識符參數,就變成了包名/引用的模塊。以mine包為例,你可以 require('mine') 引用整個包,也可以引用require('mine/lite'). import 就是import 'mine' 或import 'mine/lite'。如果遇到這樣的模塊標識符, Node.js先在node_moudules中找到主包,在這里是mine。然后再根據模塊標識符找主包下面的文件。模塊標識符也標識出了路徑,mine目錄下面的lite(主包mine也是一個目錄),lite可以是lite.js, 也可以是lite目錄,它里面包含index.js。

        像這種深入引用的模塊標識符,包的作者也可以在package.json中定義,而不用使用上面的路徑對應的方式。

      {
          "exports": {
              "./cjsmodule": "./src/cjs-module.js",
              "./es6module": "./src/es6-module.mjs"
          }
      }
          

        require('module-name/cjsmodule') or  import 'module-name/es6module' , 就可以加載相應的子模塊或子包。

      posted @ 2022-11-05 08:59  SamWeb  閱讀(351)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 西西人体大胆444WWW| 国产精品中文字幕久久| 熟女视频一区二区三区嫩草| 久久亚洲精品人成综合网| 日韩精品人妻系列无码av东京| 国产午夜精品福利91| 性人久久久久| 国产精品麻豆成人AV电影艾秋| 托克逊县| 色综合久久综合中文综合网| 亚洲人成电影在线天堂色| 日本丰满护士bbw| 国产精品第二页在线播放| 华人在线亚洲欧美精品| 唐海县| jlzz大jlzz大全免费| 一本一本久久A久久精品综合不卡| 久久久久高潮毛片免费全部播放| 欧美黑人乱大交| 国产高清一区二区不卡| 凸凹人妻人人澡人人添| 日韩一区二区黄色一级片| 精品亚洲国产成人av| 综合无码一区二区三区| 国产成人无码免费视频麻豆| 亚洲一区二区三区水蜜桃| 伊人成人在线视频免费| 中国亚州女人69内射少妇| av永久免费网站在线观看| 野外少妇被弄到喷水在线观看| 成人年无码av片在线观看| 高潮迭起av乳颜射后入| 久久综合久久美利坚合众国| 石台县| 国产精品亚洲精品日韩已满十八小| 少妇午夜啪爽嗷嗷叫视频| 遵化市| 日本一区二区三区四区黄色| 国产三级精品三级在线看| 亚洲精品熟女一区二区| 蜜桃AV抽搐高潮一区二区|