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

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

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

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

webpack打包是如何運行的

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

webpack對CommonJS的模塊化處理

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

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); })([相關(guān)模塊]);
  • 打包好的內(nèi)容和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 不同的地方
    • 首先, 包裝函數(shù)的參數(shù)之前的 module.exports 變成了_webpack_exports_
    • 其次, 在使用了 es6 模塊導(dǎo)入語法(import)的地方, 給__webpack_exports__添加了屬性__esModule
    • 其余的部分和 commonjs 類似

webpack文件的按需加載

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

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

按需加載輸出代碼分析

  • 舉例
    • 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文件,分別是主文件執(zhí)行入口文件 bundle.js 和 異步加載文件 0.bundle.js。
// 0.bundle.js // 異步模塊 // window["webpackJsonp"]是連接多個chunk文件的橋梁 // window["webpackJsonp"].push = 主chunk文件.webpackJsonpCallback (window["webpackJsonp"] = window["webpackJsonp"] || []).push([[0], // 異步模塊標(biāo)識chunkId,可判斷異步代碼是否加載成功// 跟同步模塊一樣,存放了{(lán)模塊路徑:模塊內(nèi)容}{"./src/async.js": (function(module, __webpack_exports__, __webpack_require__) {__webpack_require__.r(__webpack_exports__);__webpack_exports__["default"] = (function () {return 'hello, aysnc module';});})} ]);
  • 異步模塊打包后的文件中保存著異步模塊源代碼,同時為了區(qū)分不同的異步模塊,還保存著該異步模塊對應(yīng)的標(biāo)識:chunkId。以上代碼主動調(diào)用window[“webpackJsonp”].push函數(shù),該函數(shù)是連接異步模塊與主模塊的關(guān)鍵函數(shù),該函數(shù)定義在主文件中,實際上window[“webpackJsonp”].push = webpackJsonpCallback,詳細(xì)源碼咱們看看主文件打包后的代碼bundle.js:
(function(modules) {// 獲取到異步chunk代碼后的回調(diào)函數(shù)// 連接兩個模塊文件的關(guān)鍵函數(shù)function webpackJsonpCallback(data) {var chunkIds = data[0]; //data[0]存放了異步模塊對應(yīng)的chunkIdvar moreModules = data[1]; // data[1]存放了異步模塊代碼// 標(biāo)記異步模塊已加載成功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中// 此時萬事俱備,異步代碼都已經(jīng)同步加載到主模塊中for(moduleId in moreModules) {modules[moduleId] = moreModules[moduleId];}// 重點:執(zhí)行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) {// 創(chuàng)建promise// 把resolve保存到installedChunks[chunkId]中,等待代碼加載好再執(zhí)行resolve()以返回promisevar promise = new Promise(function(resolve, reject) {installedChunks[chunkId] = [resolve, reject];});// 通過往head頭部插入script標(biāo)簽異步加載到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"] || [];// 關(guān)鍵代碼: window["webpackJsonp"].push = webpackJsonpCallbackjsonpArray.push = webpackJsonpCallback;// 入口執(zhí)行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) {// 執(zhí)行到異步代碼時,會去執(zhí)行__webpack_require__.e方法// __webpack_require__.e其返回promise,表示異步代碼都已經(jīng)加載到主模塊了// 接下來像同步一樣,直接加載模塊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實現(xiàn)模塊的異步加載有點像jsonp的流程。

    • 在主js文件中通過在head中構(gòu)建script標(biāo)簽方式,異步加載模塊信息;
    • 再使用回調(diào)函數(shù)webpackJsonpCallback,把異步的模塊源碼同步到主文件中,所以后續(xù)操作異步模塊可以像同步模塊一樣。
  • 源碼具體實現(xiàn)流程:

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

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

總結(jié)

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

?

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

歡迎一起私信交流。

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

創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅持創(chuàng)作打卡瓜分現(xiàn)金大獎

總結(jié)

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

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。