亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

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

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

前言

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

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

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

問(wèn):CommonJS 輸出的值是淺拷貝還是深拷貝?

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

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

準(zhǔn)備工作

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

$ 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)引入了無(wú)配置的概念,既不需要提供 webpack.config.js 文件,它會(huì)默認(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)行無(wú)配置打包:

$ yarn start

終端會(huì)提示:

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

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

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

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

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

我們知道無(wú)論在 CommonJS 或 ES Modules 中,一個(gè)文件就是一個(gè)模塊,模塊之間的作用域相互隔離,且不會(huì)污染全局作用域。此刻 IIFE 就派上了用場(chǎng),它將一個(gè)文件的全部 JS 代碼包裹起來(lái),形成閉包函數(shù),不僅起到了函數(shù)自執(zhí)行的作用,還能保證函數(shù)間的作用域不會(huì)互相污染,并且在閉包函數(shù)外無(wú)法直接訪問(wèn)內(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);

如果你看過(guò)前面敘述,毫無(wú)疑問(wèn),打印 1 1.

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

(() => {
 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);
 })();
})();

可以簡(jiǎn)化為:

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

 function __webpack_require__(moduleId) {...}

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

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

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

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

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

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

最后遇到一個(gè) 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 };,等同于以下寫(xiě)法:

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

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

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

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

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

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

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

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

調(diào)用 increase() 并不會(huì)影響內(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,刪除無(wú)用的注釋后如下:

(() => {
 "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) {} // 筆者注:同一個(gè)函數(shù),不再展開(kāi)

 /* 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)過(guò)簡(jiǎn)化,大致如下:

(() => {
 "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);
 })();
})();

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

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

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

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

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

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

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

所以打印 1 2.

懂了詞法作用域的原理,就可以實(shí)現(xiàn)一個(gè)”乞丐版“的 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é)

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

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

相關(guān)文章

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

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

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

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

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

    詳解JS瀏覽器事件模型

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

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

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

    防止頁(yè)面url緩存中ajax中post請(qǐng)求的處理方法

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

    在模板頁(yè)面的js使用辦法

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

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

    相對(duì)于很多其他的面向?qū)ο笳Z(yǔ)言來(lái)說(shuō),this代表的就是當(dāng)前對(duì)象。本篇文章通過(guò)實(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 原生支持一個(gè) bind 方法,該方法很好的滿(mǎn)足了我們的初衷,調(diào)用方法與 call 和 apply 一樣,只是定義完成后,在后期調(diào)用時(shí)該方法才會(huì)執(zhí)行,需要的朋友可以了解下
    2012-12-12
  • JavaScript類(lèi)的繼承方法小結(jié)【組合繼承分析】

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

    這篇文章主要介紹了JavaScript類(lèi)的繼承方法,結(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圖片放大鏡特效,常見(jiàn)于電商網(wǎng)站上產(chǎn)品頁(yè),用來(lái)放大展示圖片細(xì)節(jié),很有實(shí)用性,推薦下載學(xué)習(xí)研究。文中提供了完整的源碼供大家下載,需要的朋友可以參考借鑒,一起來(lái)看看吧。
    2017-01-01

最新評(píng)論