日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

“约见”面试官系列之常见面试题第四十四篇之webpack打包原理解析?(建议收藏)

發布時間:2023/12/10 编程问答 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 “约见”面试官系列之常见面试题第四十四篇之webpack打包原理解析?(建议收藏) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

webpack打包是如何運行的

  • 也可以稱為,webpack是如何實現模塊化的
  • CommonJS是同步加載模塊,一般用于node。因為node應用程序運行在服務器上,程序通過文件系統可以直接讀取到各個模塊的文件,特點是響應快速,不會因為同步而阻塞了程序的運行;
  • AMD是異步加載模塊,所以普遍用于前端。而前端項目運行在瀏覽器中,每個模塊都要通過http請求加載js模塊文件,受到網絡等因素的影響如果同步的話就會使瀏覽器出現“假死”(卡死)的情況,影響到了用戶體驗。
  • ESModule 旨在實現前后端模塊化的統一。而webpack就是把ES6的模塊化代碼轉碼成CommonJS的形式,從而兼容瀏覽器的。
  • 為什么webpack打包后的文件,可以用在瀏覽器:此時webpack會將所有的js模塊打包到bundle.js中(異步加載的模塊除外,異步模塊后面會講),讀取到了內存里,就不會再分模塊加載了。

webpack對CommonJS的模塊化處理

  • 舉例:
    • index.js文件,引入foo.js文件
    const foo = require('./foo');console.log(foo); console.log('我是高級前端工程師~');
    • foo.js文件
    module.exports = {name: 'quanquan',job: 'fe', };
  • 當我們執行webpack之后,打包完成,可以看到bundle.js內的代碼
// modules 即為存放所有模塊的數組,數組中的每一個元素都是一個函數 (function(modules) {// 安裝過的模塊都存放在這里面// 作用是把已經加載過的模塊緩存在內存中,提升性能var installedModules = {};// 去數組中加載一個模塊,moduleId 為要加載模塊在數組中的 index// __webpack_require__作用和 Node.js 中 require 語句相似function __webpack_require__(moduleId) {// require 模塊時先判斷是否已經緩存, 已經緩存的模塊直接返回if(installedModules[moduleId]) {return installedModules[moduleId].exports;}// 如果緩存中不存在需要加載的模塊,就新建一個模塊,并把它存在緩存中var module = installedModules[moduleId] = {// 模塊在數組中的indexi: moduleId,// 該模塊是否已加載完畢l: false,// 該模塊的導出值,也叫模塊主體內容, 會被重寫exports: {}};// 從 modules 中獲取 index 為 moduleId 的模塊對應的函數// 再調用這個函數,同時把函數需要的參數傳入,this指向模塊的主體內容modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);// 將模塊標記為已加載module.l = true;// 返回模塊的導出值,即模塊主體內容return module.exports;}// 向外暴露所有的模塊__webpack_require__.m = modules;// 向外暴露已緩存的模塊__webpack_require__.c = installedModules;......// Webpack 配置中的 publicPath,用于加載被分割出去的異步代碼,這個暫時還沒有用到__webpack_require__.p = "";// Load entry module and return exports// 準備工作做完了, require 一下入口模塊, 讓項目跑起來// 使用 __webpack_require__ 去加載 index 為 0 的模塊,并且返回該模塊導出的內容// index 為 0 的模塊就是 index.js文件,也就是執行入口模塊// __webpack_require__.s 的含義是啟動模塊對應的 indexreturn __webpack_require__(__webpack_require__.s = 0); }) /***** 華麗的分割線 上邊時 webpack 初始化代碼, 下邊是我們寫的模塊代碼 *******/ // 所有的模塊都存放在了一個數組里,根據每個模塊在數組的 index 來區分和定位模塊 ([/* 模塊 0 對應 index.js */(function(module, exports, __webpack_require__) {// 通過 __webpack_require__ 規范導入 foo 函數,foo.js 對應的模塊 index 為 1const foo = __webpack_require__(1);console.log(foo);console.log('我是高級前端工程師~');}),/* 模塊 1 對應 foo.js */(function(module, exports) {// 通過 CommonJS 規范導出對象module.exports = {name: 'quanquan',job: 'fe',};}) ]);
  • 上面是一個立即執行函數,簡單點寫:
(function(modules) {// 模擬 require 語句function __webpack_require__(index) {return [/*存放所有模塊的數組中,第index個模塊暴露的東西*/]}// 執行存放所有模塊數組中的第0個模塊,并且返回該模塊導出的內容return __webpack_require__(0);})([/*存放所有模塊的數組*/])
  • bundle.js 能直接運行在瀏覽器中的原因在于:
    • webpack通過 _webpack_require_ 函數(該函數定義了一個可以在瀏覽器中執行的加載函數)模擬了模塊的加載(類似于Node.js 中的 require 語句),把定義的模塊內容掛載到module.exports上;
    • 同時__webpack_require__函數中也對模塊緩存做了優化,執行加載過的模塊不會再執行第二次,執行結果會緩存在內存中,當某個模塊第二次被訪問時會直接去內存中讀取被緩存的返回值。
  • 原來一個個獨立的模塊文件被合并到了一個單獨的 bundle.js 的原因在于,瀏覽器不能像 Node.js 那樣快速地去本地加載一個個模塊文件,而必須通過網絡請求去加載還未得到的文件。 如果模塊數量很多,加載時間會很長,因此把所有模塊都存放在了數組中,執行一次網絡加載。

webpack對es6 Module模塊化的處理

  • 舉例
    • index.js文件,引入foo.js文件
    const foo = require('./foo');? import foo from './foo';?console.log(foo); console.log('我是高級前端工程師~');
    • foo.js文件
    module.exports = {? export default {?name: 'quanquan',job: 'fe', };
  • 打包完后bundle.js代碼如下
(function(modules) {var installedModules = {};function __webpack_require__(moduleId) {if(installedModules[moduleId]) {return installedModules[moduleId].exports;}var module = installedModules[moduleId] = {i: moduleId,l: false,exports: {}};modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);module.l = true;return module.exports;}__webpack_require__.m = modules;__webpack_require__.c = installedModules;__webpack_require__.d = function(exports, name, getter) {if(!__webpack_require__.o(exports, name)) {Object.defineProperty(exports, name, {configurable: false,enumerable: true,get: getter});}};__webpack_require__.n = function(module) {var getter = module && module.__esModule ?function getDefault() { return module['default']; } :function getModuleExports() { return module; };__webpack_require__.d(getter, 'a', getter);return getter;};__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };__webpack_require__.p = "";return __webpack_require__(__webpack_require__.s = 0); })([相關模塊]);
  • 打包好的內容和commonjs模塊化方法差不多
function(module, __webpack_exports__, __webpack_require__) {"use strict";// 在__webpack_exports__上定義__esModule為true,表明是一個模塊對象Object.defineProperty(__webpack_exports__, "__esModule", { value: true });var __WEBPACK_IMPORTED_MODULE_0__foo__ = __webpack_require__(1);console.log(__WEBPACK_IMPORTED_MODULE_0__foo__["a"]);console.log('我是高級前端工程師~'); }, function(module, __webpack_exports__, __webpack_require__) {"use strict";__webpack_exports__["a"] = ({name: 'quanquan',job: 'fe',}); }
  • 和 commonjs 不同的地方
    • 首先, 包裝函數的參數之前的 module.exports 變成了_webpack_exports_
    • 其次, 在使用了 es6 模塊導入語法(import)的地方, 給__webpack_exports__添加了屬性__esModule
    • 其余的部分和 commonjs 類似

webpack文件的按需加載

  • 以上webpack把所有模塊打包到主文件中,所以模塊加載方式都是同步方式。但在開發應用過程中,按需加載(也叫懶加載)也是經常使用的優化技巧之一。
  • 按需加載,通俗講就是代碼執行到異步模塊(模塊內容在另外一個js文件中),通過網絡請求即時加載對應的異步模塊代碼,再繼續接下去的流程。
  • 在給單頁應用做按需加載優化時,一般采用以下原則:
    • 把整個網站劃分成一個個小功能,再按照每個功能的相關程度把它們分成幾類。
    • 把每一類合并為一個 Chunk,按需加載對應的 Chunk。
    • 對于用戶首次打開你的網站時需要看到的畫面所對應的功能,不要對它們做按需加載,而是放到執行入口所在的 Chunk 中,以降低用戶能感知的網頁加載時間。
    • 對于個別依賴大量代碼的功能點,例如依賴 Chart.js 去畫圖表、依賴 flv.js 去播放視頻的功能點,可再對其進行按需加載。
  • 被分割出去的代碼的加載需要一定的時機去觸發,也就是當用戶操作到了或者即將操作到對應的功能時再去加載對應的代碼。 被分割出去的代碼的加載時機需要開發者自己去根據網頁的需求去衡量和確定。
  • 由于被分割出去進行按需加載的代碼在加載的過程中也需要耗時,你可以預言用戶接下來可能會進行的操作,并提前加載好對應的代碼,從而讓用戶感知不到網絡加載時間。
  • 舉個例子
    • 網頁首次加載時只加載 main.js 文件,網頁會展示一個按鈕,main.js 文件中只包含監聽按鈕事件和加載按需加載的代碼。當按鈕被點擊時才去加載被分割出去的 show.js 文件,加載成功后再執行 show.js 里的函數。
    • main.js 文件
    window.document.getElementById('btn').addEventListener('click', function () {// 當按鈕被點擊后才去加載 show.js 文件,文件加載成功后執行文件導出的函數import(/* webpackChunkName: "show" */ './show').then((show) => {show('Webpack');}) });
    • show.js 文件
    module.exports = function (content) {window.alert('Hello ' + content); };
    • 代碼中最關鍵的一句是 import(/* webpackChunkName: “show” / ‘./show’),Webpack 內置了對 import() 語句的支持,當 Webpack 遇到了類似的語句時會這樣處理:
      • 以 ./show.js 為入口新生成一個 Chunk;
      • 當代碼執行到 import 所在語句時才會去加載由 Chunk 對應生成的文件。
      • import 返回一個 Promise,當文件加載成功時可以在 Promise 的 then 方法中獲取到 show.js 導出的內容。
  • webpack有個require.ensure api語法來標記為異步加載模塊,最新的webpack4推薦使用新的import() api(需要配合@babel/plugin-syntax-dynamic-import插件)。

  • 因為require.ensure是通過回調函數執行接下來的流程,而import()返回promise,這意味著可以使用最新的ES8 async/await語法,使得可以像書寫同步代碼一樣,執行異步流程。

按需加載輸出代碼分析

  • 舉例
    • main.js
    // main.js import Add from './add' console.log(Add, Add(1, 2), 123)// 按需加載 // 方式1: require.ensure // require.ensure([], function(require){ // var asyncModule = require('./async') // console.log(asyncModule.default, 234) // })// 方式2: webpack4新的import語法 // 需要加@babel/plugin-syntax-dynamic-import插件 let asyncModuleWarp = async () => await import('./async') console.log(asyncModuleWarp().default, 234)
    • async.js
    // async.js export default function() {return 'hello, aysnc module' }
  • 打包后會生成兩個chunk文件,分別是主文件執行入口文件 bundle.js 和 異步加載文件 0.bundle.js。
// 0.bundle.js // 異步模塊 // window["webpackJsonp"]是連接多個chunk文件的橋梁 // window["webpackJsonp"].push = 主chunk文件.webpackJsonpCallback (window["webpackJsonp"] = window["webpackJsonp"] || []).push([[0], // 異步模塊標識chunkId,可判斷異步代碼是否加載成功// 跟同步模塊一樣,存放了{模塊路徑:模塊內容}{"./src/async.js": (function(module, __webpack_exports__, __webpack_require__) {__webpack_require__.r(__webpack_exports__);__webpack_exports__["default"] = (function () {return 'hello, aysnc module';});})} ]);
  • 異步模塊打包后的文件中保存著異步模塊源代碼,同時為了區分不同的異步模塊,還保存著該異步模塊對應的標識:chunkId。以上代碼主動調用window[“webpackJsonp”].push函數,該函數是連接異步模塊與主模塊的關鍵函數,該函數定義在主文件中,實際上window[“webpackJsonp”].push = webpackJsonpCallback,詳細源碼咱們看看主文件打包后的代碼bundle.js:
(function(modules) {// 獲取到異步chunk代碼后的回調函數// 連接兩個模塊文件的關鍵函數function webpackJsonpCallback(data) {var chunkIds = data[0]; //data[0]存放了異步模塊對應的chunkIdvar moreModules = data[1]; // data[1]存放了異步模塊代碼// 標記異步模塊已加載成功var moduleId, chunkId, i = 0, resolves = [];for(;i < chunkIds.length; i++) {chunkId = chunkIds[i];if(installedChunks[chunkId]) {resolves.push(installedChunks[chunkId][0]);}installedChunks[chunkId] = 0;}// 把異步模塊代碼都存放到modules中// 此時萬事俱備,異步代碼都已經同步加載到主模塊中for(moduleId in moreModules) {modules[moduleId] = moreModules[moduleId];}// 重點:執行resolve() = installedChunks[chunkId][0]()返回promisewhile(resolves.length) {resolves.shift()();}};// 記錄哪些chunk已加載完成var installedChunks = {"main": 0};// __webpack_require__依然是同步讀取模塊代碼作用function __webpack_require__(moduleId) {...}// 加載異步模塊__webpack_require__.e = function requireEnsure(chunkId) {// 創建promise// 把resolve保存到installedChunks[chunkId]中,等待代碼加載好再執行resolve()以返回promisevar promise = new Promise(function(resolve, reject) {installedChunks[chunkId] = [resolve, reject];});// 通過往head頭部插入script標簽異步加載到chunk代碼var script = document.createElement('script');script.charset = 'utf-8';script.timeout = 120;script.src = __webpack_require__.p + "" + ({}[chunkId]||chunkId) + ".bundle.js"var onScriptComplete = function (event) {var chunk = installedChunks[chunkId];};script.onerror = script.onload = onScriptComplete;document.head.appendChild(script);return promise;};var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];// 關鍵代碼: window["webpackJsonp"].push = webpackJsonpCallbackjsonpArray.push = webpackJsonpCallback;// 入口執行return __webpack_require__(__webpack_require__.s = "./src/main.js");})({"./src/add.js": (function(module, __webpack_exports__, __webpack_require__) {...}),"./src/main.js": (function(module, exports, __webpack_require__) {// 同步方式var Add = __webpack_require__("./src/add.js").default;console.log(Add, Add(1, 2), 123);// 異步方式var asyncModuleWarp =function () {var _ref = _asyncToGenerator( regeneratorRuntime.mark(function _callee() {return regeneratorRuntime.wrap(function _callee$(_context) {// 執行到異步代碼時,會去執行__webpack_require__.e方法// __webpack_require__.e其返回promise,表示異步代碼都已經加載到主模塊了// 接下來像同步一樣,直接加載模塊return __webpack_require__.e(0).then(__webpack_require__.bind(null, "./src/async.js"))}, _callee);}));return function asyncModuleWarp() {return _ref.apply(this, arguments);};}();console.log(asyncModuleWarp().default, 234)}) });
  • webpack實現模塊的異步加載有點像jsonp的流程。

    • 在主js文件中通過在head中構建script標簽方式,異步加載模塊信息;
    • 再使用回調函數webpackJsonpCallback,把異步的模塊源碼同步到主文件中,所以后續操作異步模塊可以像同步模塊一樣。
  • 源碼具體實現流程:

    • 遇到異步模塊時,使用_webpack_require_.e函數去把異步代碼加載進來。該函數會在html的head中動態增加script標簽,src指向指定的異步模塊存放的文件。
    • 加載的異步模塊文件會執行webpackJsonpCallback函數,把異步模塊加載到主文件中。
    • 所以后續可以像同步模塊一樣,直接使用_webpack_require_("./src/async.js")加載異步模塊。
  • 這里的 bundle.js 和上面所講的 bundle.js 非常相似,區別在于:

    • 多了一個 webpack_require.e 用于加載被分割出去的,需要異步加載的 Chunk 對應的文件;
    • 多了一個 webpackJsonp 函數用于從異步加載的文件中安裝模塊。
    • 在使用了 CommonsChunkPlugin 去提取公共代碼時輸出的文件和使用了異步加載時輸出的文件是一樣的,都會有 webpack_require.e 和 webpackJsonp。 原因在于提取公共代碼和異步加載本質上都是代碼分割。

總結

  • webpack對于ES模塊/CommonJS模塊的實現,是基于自己實現的webpack_require,所以代碼能跑在瀏覽器中。
  • 從 webpack2 開始,已經內置了對 ES6、CommonJS、AMD 模塊化語句的支持。但不包括新的ES6語法轉為ES5代碼,這部分工作還是留給了babel及其插件。
  • 在webpack中可以同時使用ES6模塊和CommonJS模塊。因為 module.exports很像export default,所以ES6模塊可以很方便兼容 CommonJS:import XXX from ‘commonjs-module’。反過來CommonJS兼容ES6模塊,需要額外加上default:require(‘es-module’).default。
  • webpack異步加載模塊實現流程跟jsonp基本一致。

?

本面試題為前端常考面試題,后續有機會繼續完善。我是歌謠,一個沉迷于故事的講述者。

歡迎一起私信交流。

“睡服“面試官系列之各系列目錄匯總(建議學習收藏)?

創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎

總結

以上是生活随笔為你收集整理的“约见”面试官系列之常见面试题第四十四篇之webpack打包原理解析?(建议收藏)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。