Node:03.Node模塊化開發(fā)

require細節(jié)

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


require查找規(guī)則

require的查找規(guī)則是什么:完整的文檔很多,這里說下常見的規(guī)則。

  • 情況一:X是一個核心模塊,如path,http... 則直接返回核心模塊,并且停止查找。
  • 情況二:X是以./或../或/(根目錄)開頭,說明是本地文件,會從本地目錄開始查找。
    第一步:將X當作一個文件在對應的目錄下查找對應的文件。
    如果沒有后綴名,會按如下順序查找:文件X,X.js文件,X.json文件,X.node文件。

第二步:沒有找到對應的文件,將X作為一個目錄。
查找目錄下的index文件,查找X/index.js,X/index.json和X/index.node。

如果都沒有找到,那就報錯:not found。

情況三:直接是一個X(沒有路徑),并且X不是核心模塊。
先查找X是不是核心模塊,若無,則在paths里面按順序查詢node_modules。
這個paths是module對象的一個屬性module.paths。

查找paths

這里不是說我們不需要加后綴名,而是node/webpack幫我們做了一個拼接,所以不加也可以生效。

一.模塊的加載過程

  1. 模塊在被第一次引入時,模塊中的js代碼會被運行一次。實際上就是按當前文件的代碼順序來執(zhí)行。
  2. 模塊被多次引入時會被緩存,最終只加載(運行)一次。
  • 每個模塊對象的module都有一個屬性叫l(wèi)oaded。
  • loaded為false表示還未加載,為true表示已被加載。
// bar.js
let name = "wwq";
console.log(name);
name = "11111";
console.log(name);
// foo.js
require('./bar');
// main.js
require('./foo');
console.log('main中被執(zhí)行');
// 結果
wwq
11111
main中被執(zhí)行
  1. 循環(huán)引用的加載順序是什么


    循環(huán)引用:圖結構

    實際上就是圖結構的遍歷,這里是深度遍歷,也就是廣度優(yōu)先(數(shù)據(jù)結構基礎)。

二.cjs規(guī)范的缺點

  1. cjs加載模塊是同步的;
  • 同步也就是說只有對應的模塊加載完畢,當前模塊中的內(nèi)容才能被運行。在服務器的時候影響不大,但在瀏覽器中就有影響了。
  • 瀏覽器加載js文件需要先從服務器將文件下載下來,之后再加在運行。那么采用同步也就意味著后續(xù)的js代碼無法正常運動,即使是一些簡單的dom操作。
  • 所以在瀏覽器中我們通常不用cjs,但在webpack中使用cjs是另外的事情,因為它會將我們的代碼轉成瀏覽器可以直接執(zhí)行的代碼。
  • 早期為了在瀏覽器中使用模塊化,通常會采用AMD或CMD,但是現(xiàn)在瀏覽器已經(jīng)支持了ESM,另一方面借助于webpack等工具可以實現(xiàn)對cjs/esm代碼的轉換。AMD和CMD使用已經(jīng)很少了,但也做個實例。

三.AMD規(guī)范(了解一下

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

  • 是Asynchronous Module Definition(異步模塊定義)的縮寫。
  • 采用的是異步加載模塊。
  • 事實上AMD要早于cjs,但是現(xiàn)在使用已經(jīng)很少了。
    AMD的實現(xiàn),常用的庫有require.js,curl.js。


    目錄結構(lib中有require.js源代碼)
// 代碼結構
// index.html
  <script src="./lib/require.js" data-main="./index.js"></script>

// index.js
(function () {
  require.config({
    baseUrl: "",
    paths: {
      bar: "./modules/bar",
      foo: "./modules/foo",
    },
  });
  require(["foo"], function (foo) {});
})();

// modules/bar.js
define(function () {
  const name = "coderwwq";
  const age = 18;
  const sayHello = function (name) {
    console.log("你好", name);
  };
  return {
    name,
    age,
    sayHello,
  };
});

// modules/foo.js
define(["bar"], function (bar) {
  console.log(bar.name);
  console.log(bar.age);
  bar.sayHello("12312wqwq");
});

四.CMD規(guī)范(了解一下

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

  • 是Common Module Definition(異步模塊定義)的縮寫。
  • 采用的是異步加載模塊,擁有cjs的有點,現(xiàn)在使用也很少了。
  • 優(yōu)秀的實現(xiàn)方案,SeaJS。
  <script src="./lib/sea.js"></script>
  <script>
    // 核心代碼
    seajs.use("./index.js")
  </script>

  // index.js
  define(function(require, exports, module) {
    const foo = require('./modules/foo');
    console.log(foo.name);
    console.log(foo.age);
    foo.sayHello('123123');
  })

  // ./modules/foo.js
  define(function(require, exports, module) {
    const name="wwq";
    const age = 20;
    const sayHello = function(name) {
      console.log("你好", name);
    }
    module.exports = {
      name,
      age,
      sayHello,
    }
  })

五.ES Module規(guī)范

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

  • ESM使用了import和export關鍵字(解析的時候,交給js引擎對關鍵字進行解析)。
  • 另一方面采用編譯期的靜態(tài)分析,并且也加入了動態(tài)引用的方式。
  • 使用ES Module將會自動開啟嚴格模式,use strict。
瀏覽器演示ES6模塊化開發(fā)

type="module"表示這個模塊是異步加載.

// index.html
    <script type="module" src="./index.js"></script>
// index.js
   console.log('hello 123');
報錯

但是這些么寫的話會有這個跨域的報錯。
原因是:出于js模塊安全性需要,通過本地加載html文件(比如file://,不支持file協(xié)議)時,會出現(xiàn)CORS錯誤。
解決方法:需要通過一個服務器來測試。在vscode里面有個插件叫「live server」,這個插件會開啟一個本地服務,并且對我們的代碼進行熱更新。

常見導出方式有三種
// 1. 方式一:
export const name = "wwq";
export const age = 20;
export const sayHello = function (name) {
  console.log("你好", name);
};

// 2. 方式二:大括號內(nèi)統(tǒng)一導出,但不是一個對象,
// { 大括號內(nèi)放置要導出的變量的引用列表 }
export { name, age, sayHello };

// 3. 方式三:{}導出時,可以給變量起別名
export { name as fname, age as fAge, sayHello as fSayhello };
常見導入方式有三種
// 常見的導入方式
// 方式一:普通導入
import { name, age, sayHello } from "./modules/foo.js";

// 方式二:導出變量之后可以起別名
import {
  fName as wName,
  fAge as wAge,
  fSayHello as wSayHello,
} from "./modules/foo";

// 方式三:* as foo
import * as foo from "./modules/foo.js";
Export和Import結合使用

將希望暴露的所有接口放到一個文件中, 方便指定統(tǒng)一的接口規(guī)范, 也方便閱讀.

// index.js
export { sum as barSum, reduce as barReduce } from './foo.js'
// foo.js
export { sum, reduce }
default用法

上面的一些代碼示例是具名導出(named exports).

  • 這是因為在export的時候指定了名字.
  • 通過default, 在導入的時候不需要使用{}, 并且可以自己來指定名字.
  • 也方便我們和現(xiàn)有的cjs等規(guī)范互相操作.
  • 一個模塊里面只能有一個.
// 導出
export default function() { 
  console.log('格式化');
}
// 導入
import format from './modules/foo.js';
format();
補充
  • import加載模塊的時候, 不能放入邏輯代碼中(esm在被js引擎解析的時候, 必須確定依賴關系, 屬于解析時加載, 這個和cjs的require要區(qū)分開來)
  • 因為require本質(zhì)是一個函數(shù), 屬于運行時(webpack環(huán)境下).
  let flag = true;
  if(flag) {
    import format from './modules/foo.js';
  }

在純ES Module環(huán)境下可以使用import()函數(shù), 屬于異步加載

  • 大多數(shù)腳手架cli是基于webpack的, 所以可以使用import()函數(shù), 在使用這個函數(shù)時, webpack會對相應的模塊單獨打包到一個js文件中, 有助于首屏渲染.
  const promise = import('./modules/foo.js').then(res => {
    clg(res.name);
    clg(res.age);
  }

下面表示了esm的引用圖, 具體就不細說了, 有疑問的同學可以留言.


image.png
來自coderwhy

五.Node對ESModule的支持

image.png

該文件是js文件, 默認情況下一個js文件就是一個模塊, 但我們這里指的模塊是cjs模塊, 并不是es6的模塊, 所以不被當成一個esmodule.
上圖Warning提示我們需要在package.json里添加“type”: “module”, 或者使用.mjs作為文件的拓展名.
但是加了之后還不可以運行, 這回提示我們沒有找到模塊. 這是因為之前導入的時候拓展名是foo.js而不是foo.mjs, 改正之后就可以正常運行了.


image.png

五.Node對ESModule的支持

結論一:通常情況下,cjs不能還在esm

  • cjs是同步加載的,但esm必須經(jīng)過靜態(tài)分析等,無法在這個時候執(zhí)行js代碼;
  • 但這個不絕對,某些平臺在實現(xiàn)的時候可以對代碼進行針對性的解析,可能會支持;
  • Node中不支持;
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容