欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

JavaScript模塊化原理深入分析

 更新時間:2022年11月25日 15:29:38   作者:我不吃餅干  
JavaScript中的模塊化是指將每個js文件會被認為單獨一個的模塊。模塊之間是互相不可見的。如果一個模塊需要使用另一個模塊,那么需要通過指定語法來引入要使用的模塊,而且只能使用引入模塊所暴露的內容

1. 為什么需要 Javascipt 模塊化

  • 解決命名沖突。將所有變量都掛載在到全局 global 會引用命名沖突的問題。模塊化可以把變量封裝在模塊內部。
  • 解決依賴管理。Javascipt 文件如果存在相互依賴的情況就需要保證被依賴的文件先被加載。使用模塊化則無需考慮文件加載順序。
  • 按需加載。如果引用 Javascipt 文件較多,同時加載會花費加多時間。使用模塊化可以在文件被依賴的時候被加載,而不是進入頁面統(tǒng)一加載。
  • 代碼封裝。將相同功能代碼封裝起來方便后續(xù)維護和復用。

2. 你知道哪幾種模塊化規(guī)范

CommonJS

Node.js 采用了 CommonJS 模塊規(guī)范。

CommonJS 規(guī)范規(guī)定,每個模塊內部,module 變量代表當前模塊。這個變量是一個對象,它的 exports 屬性(即 module.exports )是對外的接口。加載某個模塊,其實是加載該模塊的 module.exports 屬性。使用 require 方法加載模塊。模塊加載的順序,按照其在代碼中出現(xiàn)的順序。

模塊可以多次加載,但是只會在第一次加載時運行一次,然后運行結果就被緩存了,以后再加載,就直接讀取緩存結果。要想讓模塊再次運行,必須清除緩存。

引入模塊得到的值其實是模塊輸出值的拷貝,如果是復雜對象則為淺拷貝。

// a.js
let count = 1;
function inc() {
    count++;
}
module.exports = {
    count: count,
    inc: inc
};
// b.js
const a = require('./a.js');
console.log(a.count); // 1
a.inc();
console.log(a.count); // 1

因為 CommonJS 輸出的是值的淺拷貝,也就是說 count 在輸出后就不再和原模塊的 count 有關聯(lián)。

在 Node 中每一個模塊都是一個對象,其有一個 exports 屬性,就是文件中指定的 module.exports,當我們通過 require 獲取模塊時,得到的就是 exports 屬性。再看另一個例子:

// a.js
module.exports = 123;
setTimeout(() => {
    module.exports = 456;
}, 1000);
// b.js
console.log(require('./a.js')); // 123
setTimeout(() => {
    console.log(require('./a.js')); // 456
}, 2000);

模塊的 module.exports 值改變了,我們通過 require 獲取模塊的值也會發(fā)生變化。

CommonJS 使用了同步加載,即加載完成后才進行后面的操作,所以比較適合服務端,如果用在瀏覽器則可能導致頁面假死。

AMD

AMD(Asynchronous Module Definition,異步加載模塊定義)。這里異步指的是不堵塞瀏覽器其他任務(dom構建,css渲染等),而加載內部是同步的(加載完模塊后立即執(zhí)行回調)。 AMD 也采用 require 命令加載模塊,但是不同于 CommonJS,它要求兩個參數(shù),依賴模塊和回調:

require([module], callback);

以 RequireJS 示例, 具體語法可以參考 requirejs.org/

簡單提供一下代碼示例,方便后續(xù)理解。

定義兩個模塊 calclog 模塊

// calc.js
define(function(require, factory) {
    function add(...args) {
        return args.reduce((prev, curr) => prev + curr, 0);
    }
    return {
        add
    }
});
// log.js
define(function(require, factory) {
    function log(...args) {
        console.log('---log.js---');
        console.log(...args)
    }
    return log
});

index.js 中引用兩個模塊

require(['./calc.js', './log.js'], function (calc, log) {
    log(calc.add(1,2,3,4,5));
});

在 HTML 中引用

<script src="./require.js"></script>
<script src="./index.js"></script>

可以看到在被依賴模塊加載完成后會把返回值作為依賴模塊的參數(shù)傳入,在被加載模塊全部執(zhí)行完成后可以去執(zhí)行加載模塊。

UMD

UMD(Universal Module Definition,通用模塊定義),所謂的通用,就是兼容了 CommonJS 和 AMD 規(guī)范,這意味著無論是在 CommonJS 規(guī)范的項目中,還是 AMD 規(guī)范的項目中,都可以直接引用 UMD 規(guī)范的模塊使用。

原理其實就是在模塊中去判斷全局是否存在 exportsdefine,如果存在 exports,那么以 CommonJS 的方式暴露模塊,如果存在 define 那么以 AMD 的方式暴露模塊:

(function (root, factory) {
  if (typeof define === "function" && define.amd) {
    define(["jquery", "underscore"], factory);
  } else if (typeof exports === "object") {
    module.exports = factory(require("jquery"), require("underscore"));
  } else {
    root.Requester = factory(root.$, root._);
  }
}(this, function ($, _) {
  // this is where I defined my module implementation
  const Requester = { // ... };
  return Requester;
}));

ESM (ES6 模塊)

CommonJS 和 AMD 模塊,都只能在運行時確定輸入輸出,而 ES6 模塊是在編譯時就能確定模塊的輸入輸出,模塊的依賴關系。

在 Node.js 中使用 ES6 模塊需要在 package.json 中指定 {"type": "module"}。

在瀏覽器環(huán)境使用 ES6 模塊需要指定 <script type="module" src="module.js"></script>

ES6 模塊通過 importexport 進行導入導出。ES6 模塊中 import 的值是原始值的動態(tài)只讀引用,即原始值發(fā)生變化,引用值也會變化。

import 命令具有提升效果,會提升到整個模塊的頭部,優(yōu)先執(zhí)行。

// a.js
export const obj = {
    a: 5
}
// b.js
console.log(obj)
import { obj } from './a.js'
// 運行 b.js 輸出: { a: 5 }

importexport 指定必須處理模塊頂層,也就是說不能在 iffor 等語句內。下面這種使用方式是不合法的。

if (expr) {
    import val from 'some_module'; // error!
}

UMD 通常是在 ESM 不起作用情況下備用,未來趨勢是瀏覽器和服務器都會支持 ESM。

由于 ES6 模塊是在編譯階段執(zhí)行的,可以更好的在編譯階段進行代碼優(yōu)化,如 Tree Shaking 就是依賴 ES6 模塊去靜態(tài)分析代碼而刪除無用代碼。

3. CommonJS 和 ES6 模塊的區(qū)別

  • CommonJS 模塊輸出的是一個值的拷貝,ES6 模塊輸出的是值的引用。
  • CommonJS 模塊是運行時加載,ES6 模塊是編譯時輸出接口。
  • CommonJs 是單個值導出,ES6 Module可以導出多個
  • CommonJs 是動態(tài)語法可以寫在判斷里,ES6 Module 靜態(tài)語法只能寫在頂層
  • CommonJs 的 this 是當前模塊,ES6 Module的 this 是 undefined

4. CommonJS 和 AMD 實現(xiàn)原理

CommonJS

我們通過寫一個簡單的 demo 實現(xiàn) CommonJS 來理解其原理。

1、實現(xiàn)文件的加載和執(zhí)行

我們在用 Node.js 時都知道有幾個變量和函數(shù)是不需要引入可以直接使用的,就是 require(),__filename,__dirname,exportsmodule。這些變量都是 Node.js 在執(zhí)行文件時注入進去的。

舉個栗子,我們創(chuàng)建一個 add.js 文件,導出一個 add() 函數(shù):

function add(a, b) {
    return a + b;
}
module.exports = add;

現(xiàn)在我們要加載并執(zhí)行這個文件,我們可以通過 fs.readFileSync 加載文件。

const fs = require("fs");
// 同步讀取文件
const data = fs.readFileSync("./add.js", "utf8"); // 文件內容

我們要在執(zhí)行時傳入 require(),__filename,__dirname,exports,module 這幾個參數(shù),可以在一個函數(shù)中執(zhí)行這段代碼,而函數(shù)的參數(shù)就是這幾個參數(shù)即可。我們簡單的創(chuàng)建一個函數(shù),函數(shù)的內容就是剛才我們加載的文件內容,參數(shù)名依次是規(guī)范要求注入的幾個參數(shù)。

// 通過 new Function 生成函數(shù),參數(shù)分別是函數(shù)的入?yún)⒑秃瘮?shù)的內容
const compiledWrapper = new Function(
    "exports",
    "require",
    "module",
    "__filename",
    "__dirname",
    data
);

現(xiàn)在我們執(zhí)行這個函數(shù),先不考慮 require,__filename__dirname,只傳 exportsmodule。

const mymodule = {};
const myexports = (mymodule.exports = {});
// 執(zhí)行函數(shù)并傳入 module 和 export
compiledWrapper.call(myexports, null, myexports, mymodule, null, null);

現(xiàn)在我們可以簡單的了解導出變量的原理,我們把 module 傳給函數(shù),在函數(shù)中,把需要導出的內容掛在 module 上,我們就可以通過 module 獲取導出內容了。

exports 只是 module.exports 的一個引用,我們可以給 module.exports 賦值,也可以通過 exports.xxx 形式賦值,這樣也相當于給 module.exports.xxx 賦值。但是如果直接給 exports 賦值將不生效,因為這樣 exports 就和 module 沒關系了,我們本質上還是要把導出結果賦值給 module.exports 。

現(xiàn)在的完整代碼貼一下:

const fs = require("fs");
// 同步讀取文件
const data = fs.readFileSync("./add.js", "utf8"); // 文件內容
// 創(chuàng)建函數(shù)
const compiledWrapper = new Function(
    "exports",
    "require",
    "module",
    "__filename",
    "__dirname",
    data
);
const mymodule = {};
const myexports = (mymodule.exports = {});
// 執(zhí)行函數(shù)并傳入 module 和 export
compiledWrapper.call(myexports, null, myexports, mymodule, null, null);
console.log(mymodule, myexports, mymodule.exports(1, 2));
// { exports: [Function: add] } {} 3

我們可以獲取了 add 函數(shù),并成功調用。

2、引用文件

我們剛才已經成功加載并執(zhí)行了文件,如何在另一個文件通過 require 引用呢。其實就是把上面的操作封裝一下。

不過現(xiàn)在我們把參數(shù)全部傳進去,require,__filename__dirname,分別是我們當前實現(xiàn)的 require 函數(shù),加載文件的文件路徑,加載文件的目錄路徑。

const fs = require('fs');
const path = require('path');
function _require(filename) {
  // 同步讀取文件
  const data = fs.readFileSync(filename, 'utf8'); // 文件內容
  const compiledWrapper = new Function(
    'exports',
    'require',
    'module',
    '__filename',
    '__dirname',
    data
  );
  const mymodule = {};
  const myexports = (mymodule.exports = {});
  const _filename = path.resolve(filename)
  const _dirname = path.dirname(_filename);
  compiledWrapper.call(myexports, _require, myexports, mymodule, _filename, _dirname);
  return mymodule.exports
}
const add = _require('./add.js')
console.log(add(12, 13)); // 25

3、模塊緩存

現(xiàn)在就實現(xiàn)了文件的加載和引用,現(xiàn)在還差一點,就是緩存。之前說過,一個模塊只會加載一次,然后在全局緩存起來,所以需要在全局保存緩存對象。

// add.js
console.log('[add.js] 加載文件....')
function add(a, b) {
  return a + b;
}
module.exports = add;
// require.js
const fs = require('fs');
const path = require('path');
// 把緩存對象原型設置為null 防止通過原型鏈查到同名的key (比如一個模塊叫 toString
const _cache = Object.create(null);
function _require(filename) {
  const cachedModule = _cache[filename];
  if (cachedModule) {
    // 如果存在緩存就直接返回
    return cachedModule.exports;
  }
  // 同步讀取文件
  const data = fs.readFileSync(filename, 'utf8'); // 文件內容
  const compiledWrapper = new Function(
    'exports',
    'require',
    'module',
    '__filename',
    '__dirname',
    data
  );
  const mymodule = {};
  const myexports = (mymodule.exports = {});
  const _filename = path.resolve(filename);
  const _dirname = path.dirname(_filename);
  compiledWrapper.call(
    myexports,
    _require,
    myexports,
    mymodule,
    _filename,
    _dirname
  );
  _cache[filename] = mymodule;
  return mymodule.exports;
}
const add1 = _require('./add.js');
const add2 = _require('./add.js');
console.log(add1(12, 13)); // [add.js] 加載文件.... 25
console.log(add2(13, 14)); // 27

可以看到加了緩存后,引用了兩次模塊,但只加載了一次。

一個簡單的 CommonJS 規(guī)范實現(xiàn)就完成了。

AMD

上面提供了 RequireJS 的示例代碼,打開控制臺可以發(fā)現(xiàn) HTML 中被添加了兩個 <script> 標簽,引入了程序中依賴的兩個文件。

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Document</title>
<script type="text/javascript" charset="utf-8" async data-requirecontext="_"data-requiremodule="A" src="././calc.js "></script>
<script type="text/javascript" charset="utf-8" async data-requirecontext="_"data-requiremodule="B" src="././log.js "></script>
</head>
<body> == $0
<script src=" . /require.js"></script>
<script src=" . / index.js"></script>
</body>
</html>

這樣我們可以推測 RequireJS 的實現(xiàn)原理,就是在執(zhí)行程序的過程中,發(fā)現(xiàn)依賴文件未被引用,就在 HTML 中插入一個 <script> 節(jié)點引入文件。

這里涉及一個知識點,我們可以看到被 RequireJS 插入的標簽都設置了 async 屬性。

  • 如果我們直接使用 script 腳本的話,HTML 會按照順序來加載并執(zhí)行腳本,在腳本加載&執(zhí)行的過程中,會阻塞后續(xù)的 DOM 渲染。
  • 如果設置了 async,腳本會異步加載,并在加載完成后立即執(zhí)行。
  • 如果設置了 defer,瀏覽器會異步的下載文件并且不會影響到后續(xù) DOM 的渲染,在文檔渲染完畢后,DOMContentLoaded 事件調用前執(zhí)行,按照順序執(zhí)行所有腳本。

所以我們可以推測 RequireJS 原理,通過引入 <script> 標簽異步加載依賴文件,等依賴文件全部加載完成,把文件的輸入作為參數(shù)傳入依賴文件。

到此這篇關于JavaScript模塊化原理深入分析的文章就介紹到這了,更多相關JS模塊化內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • js如何實現(xiàn)淡入淡出效果

    js如何實現(xiàn)淡入淡出效果

    這篇文章主要介紹了原生js如何實現(xiàn)淡入淡出效果,文章為大家提供了一個已經封裝好的代碼,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2015-11-11
  • 純js實現(xiàn)隔行變色效果

    純js實現(xiàn)隔行變色效果

    這篇文章主要為大家詳細介紹了純js實現(xiàn)隔行變色效果,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-11-11
  • 關于對TypeScript泛型參數(shù)的默認值理解

    關于對TypeScript泛型參數(shù)的默認值理解

    泛型可以理解為寬泛的類型,通常用于類和函數(shù),下面這篇文章主要給大家介紹了關于對TypeScript泛型參數(shù)默認值的相關資料,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下
    2022-07-07
  • JavaScript實現(xiàn)動態(tài)數(shù)據(jù)可視化的示例詳解

    JavaScript實現(xiàn)動態(tài)數(shù)據(jù)可視化的示例詳解

    動態(tài)數(shù)據(jù)可視化能夠將大量數(shù)據(jù)以直觀、生動的方式呈現(xiàn),幫助用戶更好地理解和分析數(shù)據(jù),本文主要為大家介紹了如何使用JavaScript實現(xiàn)這一功能,需要的可以參考下
    2024-02-02
  • Jquery對數(shù)組的操作技巧整理

    Jquery對數(shù)組的操作技巧整理

    這篇文章主要介紹了Jquery對數(shù)組的操作技巧,需要的朋友可以參考下
    2014-03-03
  • 微信小程序實現(xiàn)上傳視頻功能

    微信小程序實現(xiàn)上傳視頻功能

    這篇文章主要為大家詳細介紹了微信小程序實現(xiàn)上傳視頻功能,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-08-08
  • IE7中javascript操作CheckBox的checked=true不打勾的解決方法

    IE7中javascript操作CheckBox的checked=true不打勾的解決方法

    在IE7下,生成的Checkbox無法正確的打上勾。 原因是 chkbox控件還沒初始化(appendChild),就開始操作它的結果
    2009-12-12
  • JavaScript中的冒泡排序法

    JavaScript中的冒泡排序法

    這篇文章主要介紹了JavaScript中的冒泡排序法的知識,并通過一個例子給大家講解了js冒泡排序,非常不錯,具有參考借鑒價值,感興趣的朋友一起學習吧
    2016-08-08
  • JavaScript必備的斷點調試技巧總結(推薦)

    JavaScript必備的斷點調試技巧總結(推薦)

    打斷點操作很簡單,核心的問題在于,斷點怎么打才能夠排查出代碼的問題所在呢?下面這篇文章主要給大家總結介紹了關于JavaScript必備的斷點調試技巧,需要的朋友可以參考下
    2021-09-09
  • javascript輪播圖算法

    javascript輪播圖算法

    這篇文章主要為大家詳細介紹了javascript輪播圖算法,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2016-10-10

最新評論