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

利用webpack理解CommonJS和ES Modules的差異區(qū)別

 更新時間:2020年06月16日 15:31:40   作者:B2D1  
這篇文章主要介紹了利用webpack理解CommonJS和ES Modules的差異區(qū)別,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

前言

問: CommonJS 和 ES Modules 中模塊引入的區(qū)別?

CommonJS 輸出的是一個值的拷貝;ES Modules 生成一個引用,等到真的需要用到時,再到模塊里面去取值,模塊里面的變量,綁定其所在的模塊。

我相信很多人已經(jīng)把這個答案背得滾瓜爛熟,好,那繼續(xù)提問。

問:CommonJS 輸出的值是淺拷貝還是深拷貝?

問:你能模擬實(shí)現(xiàn) ES Modules 的引用生成嗎?

對于以上兩個問題,我也是感到一臉懵逼,好在有 webpack 的幫助,作為一個打包工具,它讓 ES Modules, CommonJS 的工作流程瞬間清晰明了。

準(zhǔn)備工作

初始化項(xiàng)目,并安裝 beta 版本的 webpack 5,它相較于 webpack 4 做了許多優(yōu)化:對 ES Modules 的支持度更高,打包后的代碼也更精簡。

$ mkdir demo && cd demo
$ yarn init -y
$ yarn add webpack@next webpack-cli
# or yarn add webpack@5.0.0-beta.17 webpack-cli

早在 webpack4 就已經(jīng)引入了無配置的概念,既不需要提供 webpack.config.js 文件,它會默認(rèn)以 src/index.js 為入口文件,生成打包后的 main.js 放置于 dist 文件夾中。

確保你擁有以下目錄結(jié)構(gòu):

├── dist
│  └── index.html
├── src
│  └── index.js
├── package.json
└── yarn.lock

在 index.html 中引入打包后的 main.js:

<!DOCTYPE html>
<html lang="en">
 <head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Document</title>
 </head>
 <body>
  <script src="main.js"></script>
 </body>
</html>

在 package.json 中添加命令腳本:

"scripts": {
 "start": "webpack"
},

運(yùn)行無配置打包:

$ yarn start

終端會提示:

WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/

webpack 要求用戶在打包時必須提供 mode 選項(xiàng),來指明打包后的資源用于開發(fā)環(huán)境還是生產(chǎn)環(huán)境,從而讓 webpack 相應(yīng)地使用其內(nèi)置優(yōu)化,默認(rèn)為 production(生產(chǎn)環(huán)境)。

我們將其設(shè)置為 none 來避免默認(rèn)行為帶來的干擾,以便我們更好的分析源碼。
修改 package.json:

"scripts": {
 "start": "webpack --mode=none"
},

重新運(yùn)行,webpack 在 dist 目錄下生成了打包后的 main.js,由于入口文件是空的,所以 main.js 的源碼只有一個 IIFE(立即執(zhí)行函數(shù)),看似簡單,但它的地位卻極其重要。

(() => {
 // webpackBootstrap
})();

我們知道無論在 CommonJS 或 ES Modules 中,一個文件就是一個模塊,模塊之間的作用域相互隔離,且不會污染全局作用域。此刻 IIFE 就派上了用場,它將一個文件的全部 JS 代碼包裹起來,形成閉包函數(shù),不僅起到了函數(shù)自執(zhí)行的作用,還能保證函數(shù)間的作用域不會互相污染,并且在閉包函數(shù)外無法直接訪問內(nèi)部變量,除非內(nèi)部變量被顯式導(dǎo)出。

var name = "webpack";

(() => {
 var name = "parcel";
 var age = 18;
 console.log(name); // parcel
})();

console.log(name); // webpack
console.log(age); // ReferenceError: age is not defined

引用 vs 拷貝

接下里進(jìn)入實(shí)踐部分,涉及源碼的閱讀,讓我們深入了解 CommonJS 和 ES Modules 的差異所在。

CommonJS

新建 src/counter.js

let num = 1;

function increase() {
 return num++;
}

module.exports = { num, increase };

修改 index.js

const { num, increase } = require("./counter");

console.log(num);
increase();
console.log(num);

如果你看過前面敘述,毫無疑問,打印 1 1.

so why?我們查看 main.js,那有我們想要的答案,去除無用的注釋后如下:

(() => {
 var __webpack_modules__ = [
  ,
  module => {
   let num = 1;

   function increase() {
    return num++;
   }

   module.exports = { num, increase };
  },
 ];

 var __webpack_module_cache__ = {};

 function __webpack_require__(moduleId) {
  // Check if module is in cache
  if (__webpack_module_cache__[moduleId]) {
   return __webpack_module_cache__[moduleId].exports;
  }
  // Create a new module (and put it into the cache)
  var module = (__webpack_module_cache__[moduleId] = {
   exports: {},
  });

  // Execute the module function
  __webpack_modules__[moduleId](module, module.exports, __webpack_require__);

  return module.exports;
 }

 (() => {
  const { num, increase } = __webpack_require__(1);

  console.log(num);
  increase();
  console.log(num);
 })();
})();

可以簡化為:

(() => {
 var __webpack_modules__ = [...];
 var __webpack_module_cache__ = {};

 function __webpack_require__(moduleId) {...}

 (() => {
  const { num, increase } = __webpack_require__(1);

  console.log(num);
  increase();
  console.log(num);
 })();
})();

最外層是一個 IIFE,立即執(zhí)行。

__webpack_modules__,它是一個數(shù)組,第一項(xiàng)為空,第二項(xiàng)是一個箭頭函數(shù)并傳入 module 參數(shù),函數(shù)內(nèi)部包含了 counter.js 中的所有代碼。

__webpack_module_cache__ 緩存已經(jīng)加載過的模塊。

function __webpack_require__(moduleId) {...} 類似于 require(),他會先去 __webpack_module_cache__ 中查找此模塊是否已經(jīng)被加載過,如果被加載過,直接返回緩存中的內(nèi)容。否則,新建一個 module: {exports: {}},并設(shè)置緩存,執(zhí)行模塊函數(shù),最后返回 module.exports

最后遇到一個 IIFE,它將 index.js 中的代碼包裝在內(nèi),并執(zhí)行 __webpack_require__(1),導(dǎo)出了 num 和 increase 供 index.js 使用。

這里的關(guān)鍵點(diǎn)在于 counter.js 中的 module.exports = { num, increase };,等同于以下寫法:

module.exports = {
 num: num,
 increase: increase,
};

num 屬于基本類型,假設(shè)其內(nèi)存地址指向 n1,當(dāng)它被 賦值 給 module.exports['num'] 時,module.exports['num'] 已經(jīng)指向了一個新的內(nèi)存地址 n2,只不過其值同樣為 1,但和 num 已是形同陌路,毫不相干。

let num = 1;
// mun 相當(dāng)于 module.exports['num']
mun = num;

num = 999;
console.log(mun); // 1

increase 是一個函數(shù),屬于引用類型,即 increase 只作為一個指針,當(dāng)它被賦值給 module.exports['increase'] 時,只進(jìn)行了指針的復(fù)制,是 淺拷貝(基本類型沒有深淺拷貝的說法),其內(nèi)存地址依舊指向同一塊數(shù)據(jù)。所以本質(zhì)上 module.exports['increase'] 就是 increase,只不過換個名字。

而由于詞法作用域的特性,counter.js 中 increase() 修改的 num 變量在函數(shù)聲明時就已經(jīng)綁定不變了,永遠(yuǎn)綁定內(nèi)存地址指向 n1 的 num.

JavaScript 采用的是詞法作用域,它規(guī)定了函數(shù)內(nèi)訪問變量時,查找變量是從函數(shù)聲明的位置向外層作用域中查找,而不是從調(diào)用函數(shù)的位置開始向上查找

function foo() {
 var x = 10;
 console.log(x);
}
function bar(f) {
 var x = 20;
 f();
}
bar(foo); // 10

調(diào)用 increase() 并不會影響內(nèi)存地址指向 n2 的 num,這也就是為什么打印 1 1 的理由。

ES Modules

分別修改 counter.js 和 index.js,這回使用 ES Modules.

let num = 1;

function increase() {
 return num++;
}

export { num, increase };
import { num, increase } from "./counter";

console.log(num);
increase();
console.log(num);

很明顯,打印 1 2.

老規(guī)矩,查看 main.js,刪除無用的注釋后如下:

(() => {
 "use strict";
 var __webpack_modules__ = [
  ,
  (__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
   __webpack_require__.d(__webpack_exports__, {
    num: () => /* binding */ num,
    increase: () => /* binding */ increase,
   });
   let num = 1;

   function increase() {
    return num++;
   }
  },
 ];

 var __webpack_module_cache__ = {};

 function __webpack_require__(moduleId) {} // 筆者注:同一個函數(shù),不再展開

 /* webpack/runtime/define property getters */
 (() => {
  __webpack_require__.d = (exports, definition) => {
   for (var key in definition) {
    if (
     __webpack_require__.o(definition, key) &&
     !__webpack_require__.o(exports, key)
    ) {
     Object.defineProperty(exports, key, {
      enumerable: true,
      get: definition[key],
     });
    }
   }
  };
 })();

 /* webpack/runtime/hasOwnProperty shorthand */
 (() => {
  __webpack_require__.o = (obj, prop) =>
   Object.prototype.hasOwnProperty.call(obj, prop);
 })();

 (() => {
  var _counter__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);

  console.log(_counter__WEBPACK_IMPORTED_MODULE_0__.num);
  (0, _counter__WEBPACK_IMPORTED_MODULE_0__.increase)();
  console.log(_counter__WEBPACK_IMPORTED_MODULE_0__.num);
 })();
})();

經(jīng)過簡化,大致如下:

(() => {
 "use strict";
 var __webpack_modules__ = [...];
 var __webpack_module_cache__ = {};

 function __webpack_require__(moduleId) {...}

 (() => {
  __webpack_require__.d = (exports, definition) => {...};
 })();

 (() => {
  __webpack_require__.o = (obj, prop) => {...}
 })();

 (() => {
  var _counter__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);

  console.log(_counter__WEBPACK_IMPORTED_MODULE_0__.num);
  (0, _counter__WEBPACK_IMPORTED_MODULE_0__.increase)();
  console.log(_counter__WEBPACK_IMPORTED_MODULE_0__.num);
 })();
})();

首先查看兩個工具函數(shù):__webpack_require__.o 和 __webpack_require__.d。

__webpack_require__.o 封裝了 Object.prototype.hasOwnProperty.call(obj, prop) 的操作。

__webpack_require__.d 則是通過 Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }) 來對 exports 對象設(shè)置不同屬性的 getter

隨后看到了熟悉的 __webpack_modules__,它的形式和上一節(jié)差不多,最主要的是以下這段代碼:

__webpack_require__.d(__webpack_exports__, {
 num: () => /* binding */ num,
 increase: () => /* binding */ increase,
});

與 CommonJS 不同,ES Modules 并沒有對 module.exports 直接賦值,而是將值作為箭頭函數(shù)的返回值,再把箭頭函數(shù)賦值給 module.exports,之前我們提過詞法作用域的概念,即這里的 num() 和 increase() 無論在哪里執(zhí)行,返回的 num 變量和 increase 函數(shù)都是 counter.js 中的。

在遇到最后一個 IIFE 時,調(diào)用 __webpack_require__(1),返回 module.exports 并賦值給 _counter__WEBPACK_IMPORTED_MODULE_0__,后續(xù)所有的屬性獲取都是使用點(diǎn)操作符,這觸發(fā)了對應(yīng)屬性的 get 操作,于是執(zhí)行函數(shù)返回 counter.js 中的值。

所以打印 1 2.

懂了詞法作用域的原理,就可以實(shí)現(xiàn)一個”乞丐版“的 ES Modules:

function my_require() {
 var module = {
  exports: {},
 };
 let counter = 1;

 function add() {
  return counter++;
 }

 module.exports = { counter: () => counter, add };
 return module.exports;
}

var obj = my_require();

console.log(obj.counter()); // 1
obj.add();
console.log(obj.counter()); // 2

總結(jié)

多去看源碼,會有不少的收獲,這是一個思考的過程。
ES Modules 已經(jīng)寫入了 ES2020 規(guī)范中,意味著瀏覽器原生支持 import 和 export,有興趣的小伙伴可以試試 Snowpack,它能直接 export 第三方庫供瀏覽器使用,省去了 webpack 中打包的時間。

到此這篇關(guān)于利用webpack理解CommonJS和ES Modules的差異區(qū)別的文章就介紹到這了,更多相關(guān)webpack CommonJS和ES Modules 內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • js實(shí)現(xiàn)點(diǎn)擊切換和自動播放的輪播圖

    js實(shí)現(xiàn)點(diǎn)擊切換和自動播放的輪播圖

    這篇文章主要為大家詳細(xì)介紹了js實(shí)現(xiàn)點(diǎn)擊切換和自動播放的輪播圖,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-07-07
  • JavaScript架構(gòu)前端不能沒有監(jiān)控系統(tǒng)原因

    JavaScript架構(gòu)前端不能沒有監(jiān)控系統(tǒng)原因

    這篇文章主要為大家介紹了為什么前端不能沒有監(jiān)控系統(tǒng)的原因,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-06-06
  • 詳解JS瀏覽器事件模型

    詳解JS瀏覽器事件模型

    這篇文章主要介紹了JS瀏覽器事件模型,對時間模型感興趣的同學(xué),可以參考下
    2021-05-05
  • 高性能Javascript筆記 數(shù)據(jù)的存儲與訪問性能優(yōu)化

    高性能Javascript筆記 數(shù)據(jù)的存儲與訪問性能優(yōu)化

    在JavaScript中,數(shù)據(jù)的存儲位置對代碼的整體性能有著重要的影響。有四種數(shù)據(jù)訪問類型:直接量,局部變量,數(shù)組項(xiàng),對象成員
    2012-08-08
  • 防止頁面url緩存中ajax中post請求的處理方法

    防止頁面url緩存中ajax中post請求的處理方法

    這篇文章主要介紹了防止頁面url緩存中ajax中post請求的處理方式的相關(guān)資料,希望通過本文能幫助到大家,需要的朋友可以參考下
    2017-10-10
  • 在模板頁面的js使用辦法

    在模板頁面的js使用辦法

    在使用了母板頁面的項(xiàng)目中 使用js獲取其中的標(biāo)簽id 需要先加載到本地看看他在瀏覽時候的頁面源代碼然后確定他的id
    2010-04-04
  • 老生常談JavaScript中的this關(guān)鍵字

    老生常談JavaScript中的this關(guān)鍵字

    相對于很多其他的面向?qū)ο笳Z言來說,this代表的就是當(dāng)前對象。本篇文章通過實(shí)例給大家介紹js中的this關(guān)鍵字,感興趣的朋友一起看看吧
    2016-10-10
  • ie支持function.bind()方法實(shí)現(xiàn)代碼

    ie支持function.bind()方法實(shí)現(xiàn)代碼

    在 google 一番技術(shù)資料后,發(fā)現(xiàn) firefox 原生支持一個 bind 方法,該方法很好的滿足了我們的初衷,調(diào)用方法與 call 和 apply 一樣,只是定義完成后,在后期調(diào)用時該方法才會執(zhí)行,需要的朋友可以了解下
    2012-12-12
  • JavaScript類的繼承方法小結(jié)【組合繼承分析】

    JavaScript類的繼承方法小結(jié)【組合繼承分析】

    這篇文章主要介紹了JavaScript類的繼承方法,結(jié)合實(shí)例形式總結(jié)分析了JavaScript繼承的概念、原理及組合繼承相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下
    2018-07-07
  • 利用javascript實(shí)現(xiàn)的三種圖片放大鏡效果實(shí)例(附源碼)

    利用javascript實(shí)現(xiàn)的三種圖片放大鏡效果實(shí)例(附源碼)

    這篇文章主要介紹了利用javascript實(shí)現(xiàn)的幾種放大鏡效果,很實(shí)用一款漂亮的js圖片放大鏡特效,常見于電商網(wǎng)站上產(chǎn)品頁,用來放大展示圖片細(xì)節(jié),很有實(shí)用性,推薦下載學(xué)習(xí)研究。文中提供了完整的源碼供大家下載,需要的朋友可以參考借鑒,一起來看看吧。
    2017-01-01

最新評論