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

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。

這里不是說我們不需要加后綴名,而是node/webpack幫我們做了一個拼接,所以不加也可以生效。
一.模塊的加載過程
- 模塊在被第一次引入時,模塊中的js代碼會被運行一次。實際上就是按當前文件的代碼順序來執(zhí)行。
- 模塊被多次引入時會被緩存,最終只加載(運行)一次。
- 每個模塊對象的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í)行
-
循環(huán)引用的加載順序是什么
循環(huán)引用:圖結構
實際上就是圖結構的遍歷,這里是深度遍歷,也就是廣度優(yōu)先(數(shù)據(jù)結構基礎)。
二.cjs規(guī)范的缺點
- 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的引用圖, 具體就不細說了, 有疑問的同學可以留言.


五.Node對ESModule的支持

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

五.Node對ESModule的支持
結論一:通常情況下,cjs不能還在esm
- cjs是同步加載的,但esm必須經(jīng)過靜態(tài)分析等,無法在這個時候執(zhí)行js代碼;
- 但這個不絕對,某些平臺在實現(xiàn)的時候可以對代碼進行針對性的解析,可能會支持;
- Node中不支持;

