最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

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

前言

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

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

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

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

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

對于以上兩個(gè)問題,我也是感到一臉懵逼,好在有 webpack 的幫助,作為一個(gè)打包工具,它讓 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 文件,它會(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)行無配置打包:

$ 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 要求用戶在打包時(shí)必須提供 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 的源碼只有一個(gè) IIFE(立即執(zhí)行函數(shù)),看似簡單,但它的地位卻極其重要。

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

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

最外層是一個(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)加載過的模塊。

function __webpack_require__(moduleId) {...} 類似于 require(),他會(huì)先去 __webpack_module_cache__ 中查找此模塊是否已經(jīng)被加載過,如果被加載過,直接返回緩存中的內(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 };,等同于以下寫法:

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

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

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

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

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

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

JavaScript 采用的是詞法作用域,它規(guī)定了函數(shù)內(nèi)訪問變量時(shí),查找變量是從函數(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() 并不會(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,刪除無用的注釋后如下:

(() => {
 "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ù),不再展開

 /* 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);
 })();
})();

首先查看兩個(gè)工具函數(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 中的。

在遇到最后一個(gè) IIFE 時(shí),調(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)一個(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è)思考的過程。
ES Modules 已經(jīng)寫入了 ES2020 規(guī)范中,意味著瀏覽器原生支持 import 和 export,有興趣的小伙伴可以試試 Snowpack,它能直接 export 第三方庫供瀏覽器使用,省去了 webpack 中打包的時(shí)間。

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

相關(guān)文章

  • es6函數(shù)之尾調(diào)用優(yōu)化實(shí)例分析

    es6函數(shù)之尾調(diào)用優(yōu)化實(shí)例分析

    這篇文章主要介紹了es6函數(shù)之尾調(diào)用優(yōu)化,結(jié)合實(shí)例形式分析了es6函數(shù)尾調(diào)用優(yōu)化相關(guān)功能、原理、實(shí)現(xiàn)方法及操作注意事項(xiàng),需要的朋友可以參考下
    2020-04-04
  • JavaScript實(shí)現(xiàn)的一個(gè)日期格式化函數(shù)分享

    JavaScript實(shí)現(xiàn)的一個(gè)日期格式化函數(shù)分享

    這篇文章主要介紹了JavaScript實(shí)現(xiàn)的一個(gè)日期格式化函數(shù)分享,本文給出了實(shí)現(xiàn)代碼和使用例子,需要的朋友可以參考下
    2014-12-12
  • JavaScript實(shí)現(xiàn)網(wǎng)站訪問次數(shù)統(tǒng)計(jì)代碼

    JavaScript實(shí)現(xiàn)網(wǎng)站訪問次數(shù)統(tǒng)計(jì)代碼

    每個(gè)網(wǎng)站管理者,都必須知道每天有多少人訪問了本站,需要一個(gè)網(wǎng)站訪問次數(shù)功能來滿足需求,本篇文章主要介紹了JavsScript實(shí)現(xiàn)網(wǎng)站訪問次數(shù)統(tǒng)計(jì)代碼,需要的朋友可以參考下
    2015-08-08
  • JavaScript中閉包之淺析解讀(必看篇)

    JavaScript中閉包之淺析解讀(必看篇)

    下面小編就為大家?guī)硪黄狫avaScript中閉包之淺析解讀(必看篇)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2016-08-08
  • js獲取元素的標(biāo)簽名實(shí)現(xiàn)方法

    js獲取元素的標(biāo)簽名實(shí)現(xiàn)方法

    下面小編就為大家?guī)硪黄猨s獲取元素的標(biāo)簽名實(shí)現(xiàn)方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2016-10-10
  • Javascript中的this,bind和that使用實(shí)例

    Javascript中的this,bind和that使用實(shí)例

    這篇文章主要介紹了Javascript中的this,bind和that使用實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-12-12
  • javascript中數(shù)組和字符串的方法對比

    javascript中數(shù)組和字符串的方法對比

    下面小編就為大家?guī)硪黄猨avascript中數(shù)組和字符串的方法對比。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2016-07-07
  • javaScript中indexOf用法技巧

    javaScript中indexOf用法技巧

    在本篇文章里小編給大家整理的是一篇關(guān)于javaScript中indexOf用法技巧的相關(guān)文章,有需要的朋友們學(xué)習(xí)下。
    2019-11-11
  • JS根據(jù)當(dāng)天不同時(shí)間顯示不同廣告的代碼

    JS根據(jù)當(dāng)天不同時(shí)間顯示不同廣告的代碼

    這篇文章主要介紹了JS根據(jù)當(dāng)天不同的時(shí)間段顯示不同廣告的代碼,可以精確到小時(shí)當(dāng)然如果分鐘都可以判斷出來,需要的朋友可以參考下
    2023-02-02
  • 使用axios實(shí)現(xiàn)上傳圖片進(jìn)度條功能

    使用axios實(shí)現(xiàn)上傳圖片進(jìn)度條功能

    Axios 是一個(gè)基于 promise 的 HTTP 庫,可以用在瀏覽器和 node.js 中。這篇文章主要介紹了使用axios實(shí)現(xiàn)上傳圖片進(jìn)度條,需要的朋友可以參考下
    2017-12-12

最新評論

华安县| 特克斯县| 忻城县| 沧源| 渭源县| 淳安县| 武冈市| 新昌县| 金川县| 嘉黎县| SHOW| 遂昌县| 安义县| 华池县| 宾川县| 中宁县| 玉环县| 亳州市| 漳平市| 涞水县| 乐清市| 曲阳县| 白河县| 科尔| 湖口县| 长泰县| 灵石县| 阜新市| 龙井市| 兴国县| 石泉县| 砀山县| 嘉定区| 沭阳县| 土默特左旗| 汨罗市| 崇文区| 城固县| 宜兰市| 盐亭县| 长兴县|