webpack4.x 模塊化淺析-CommonJS

先看下webpack官方文檔中對模塊的描述:

在模塊化編程中,開發者將程序分解成離散功能塊(discrete chunks of functionality),并稱之為模塊。
每個模塊具有比完整程序更小的接觸面,使得校驗、調試、測試輕而易舉。 精心編寫的模塊提供了可靠的抽象和封裝界限,使得應用程序中每個模塊都具有條理清楚的設計和明確的目的。

webpack 的核心概念之一就是一切皆模塊,webpack 在項目中的作用就是,分析項目的結構,找到 JavaScript 模塊以及其他一些瀏覽器不能直接運行的拓展語言(less,scss,typescript),并將其打包為合適的格式以供瀏覽器使用,它從一個項目的主文件開始,根據引用路徑,找到所有其所依賴的文件,同時將這些文件進行處理(各種loader來解析,編譯處理瀏覽器不能直接使用的文件),然后打包為一個或者多個瀏覽器可識別的JavaScript文件。

本文不會詳細描述 webpack 的構建流程,畢竟官網已經說得比較詳細了,這里主要是分析下 webpack 打包后的文件,將文件打包成什么樣子,又是如何使用模塊的。webpack 最早支持的代碼模塊化方式是 CommonJS,后面慢慢支持了 ES6、AMD 等,不論使用的是哪種方式,webpack 都可以對其進行解析和打包,本文例子中使用的是 CommonJS 規范,更多規范介紹可查看官方文檔。

例子

為方便后面的說明,首先創建一個項目,也就是先建立一個文件夾 webpack-test(名字自擬),然后在里面新建一個 package.json 文件,用來做 npm 的說明,在 webpack-test 文件夾中使用命令:

npm init

執行命令后會詢問一些問題,一路回車即可。然后安裝下 webpack 的依賴包,如下命令:

npm install --save-dev webpack

再新建幾個文件:
1、在項目根目錄下新建文件夾 app 用來存放業務代碼、文件夾 public 存放打包后的文件;
2、在app中新建入口文件 main.js;
3、在app中新建功能模塊 hello.js,bye.js,to.js;
4、在項目根目錄下,建立 index.html 文件;

然后依次來給這幾個文件分別填寫以下內容:

// webpack-test/app/hello.js
const to = require('./to.js');
module.exports = function() {var hello = document.createElement('div');hello.textContent = "Say Hello to " + to.name;return hello;
};
// webpack-test/app/bye.js
const to = require('./to.js');
module.exports = function() {var bye = document.createElement('div');bye.textContent = "Say Bye to " + to.name;return bye;
};
// webpack-test/app/to.js
module.exports = {name: "小明"};
// webpack-test/app/main.js
const hello = require('./hello.js');
const bye = require('./bye.js');document.querySelector("#root").appendChild(hello()).appendChild(bye());;
// webpack-test/index.html
<!DOCTYPE html>
<html lang="en"><head><meta charset="utf-8"><title>Webpack Test Project</title></head><body><div id='root'></div>// bundle.js 文件就是一會兒我們要打包app中的文件后生成的結果文件<script src="public/bundle.js"></script></body>
</html>

業務模塊 hello.js 和 bye.js 做了各自的操作,同時引用了共同的文件 to.js;主文件 main.js 中引用并執行了模塊 hello.js 和 bye.js;index.html 文件引入了 main.js 打包后的最終文件 bundle.js。

打包

接下來進行打包操作,先確保 webpack 是全局安裝的,否則執行時需要指定 webpack 的路徑,比如在 4.0 以下版本中使用 node_modules/.bin/webpack ./app/main.js ./public/bundle.js;
如果你使用的是 webpack4.0+ 的話,使用 webpack ./app/main.js ./public/bundle.js命令,也許會報以下的錯誤:

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/concepts/mode/ERROR in multi ./app/main.js ./public/bundle.js
Module not found: Error: Can't resolve './public/bundle.js' in '/Users/zhaohaipeng/soyoung-project/webpack-test'@ multi ./app/main.js ./public/bundle.js main[1]

webpack4.0+之后,針對第一個報錯,需要指定環境 --mode development;第二個報錯,是因為我們沒有使用配置文件的方式打包,而是直接使用的命令指定的打包輸出位置,所以需要聲明輸出文件,綜上,正確的命令如下:

webpack app/main.js --output public/bundle.js --mode development

執行結果:

?  webpack-test webpack app/main.js --output public/bundle.js --mode development
Hash: a4e2f9ecc51b64891624
Version: webpack 4.25.1
Time: 90ms
Built at: 2018-11-08 17:11:01Asset      Size  Chunks             Chunk Names
bundle.js  5.16 KiB    main  [emitted]  main
Entrypoint main = bundle.js
[./app/bye.js] 165 bytes {main} [built]
[./app/hello.js] 173 bytes {main} [built]
[./app/main.js] 144 bytes {main} [built]
[./app/to.js] 30 bytes {main} [built]
?  webpack-test

瀏覽器打開 index.html 文件,即可看到結果

Say Hello to 小明
Say Bye to 小明

但是 webpack 作為一個能簡化我們開發難度和使用便捷的工具,顯然像上面那樣通過敲很多命令來打包,并不方便,所以下面采用配置文件的方式再來一次:

根目錄創建 webpack.config.js 文件,并配置下打包入口和出口:

// webpack-test/webpack.config.js
module.exports = {mode: "development",//webpack.0之后需要聲明環境entry:  __dirname + "/app/main.js",//唯一入口文件output: {path: __dirname + "/public",//打包后的文件存放目錄filename: "bundle.js"//打包后輸出文件名}
}

再次打包的時候,只需要使用命令 webpack 就可以了,webpack 默認讀取當前路徑下的 webpack.config.js 文件。

最終打包好的 bundle.js 文件,去除了多余注釋,調整了代碼格式,內容如下:

// 自執行函數,參數為所有模塊組成的,形勢為key:value,key是模塊名
(function(modules) { // webpackBootstrap// 已加載模塊的緩存,記錄模塊的加載情況,也是為了避免重復打包,節省資源var installedModules = {};// webpack 使用 require 方式加載模塊的方法(模擬ConmmonJS reqiure()),作用為根據傳進來的模塊id來處理對應的模塊,加入已加載緩存,執行,標記,返回exportsfunction __webpack_require__(moduleId) {// moduleId 為模塊路徑// 檢測模塊是否已加載,已加載的話直接返回該模塊if(installedModules[moduleId]) {return installedModules[moduleId].exports;}// 當前模塊未加載的話,新建,并存于緩存var module = installedModules[moduleId] = {i: moduleId,l: false,exports: {}};// 在當前模塊的 exports 下,也就是模塊的內部執行模塊代碼,突出作用域modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);// 標記模塊已經加載module.l = true;// 返回模塊的導出內容return module.exports;}// 掛載屬性,該模塊 (__webpack_modules__)__webpack_require__.m = modules;// 掛載屬性,模塊加載緩存__webpack_require__.c = installedModules;// 本代碼中未執行,暫時不分析// 在 exports 中定義 getter 方法__webpack_require__.d = function(exports, name, getter) {// 當 name 屬性不是定義在對象本身,而是繼承自原型鏈,則在在 exports 中定義 getter 方法if(!__webpack_require__.o(exports, name)) {Object.defineProperty(exports, name, { enumerable: true, get: getter });}};// 本代碼中未執行,暫時不分析// 在 exports 中定義 __esModule,定義key為Symbol的屬性(在__webpack_require__.t中被調用)// define __esModule on exports__webpack_require__.r = function(exports) {if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });}Object.defineProperty(exports, '__esModule', { value: true });};// 本代碼中未執行,暫時不分析// 創建一個偽命名空間的對象// create a fake namespace object// mode & 1: value is a module id, require it// mode & 2: merge all properties of value into the ns// mode & 4: return value when already ns object// mode & 8|1: behave like require__webpack_require__.t = function(value, mode) {if(mode & 1) value = __webpack_require__(value);if(mode & 8) return value;if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;var ns = Object.create(null);__webpack_require__.r(ns);Object.defineProperty(ns, 'default', { enumerable: true, value: value });if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));return ns;};// 本代碼中未執行,暫時不分析// getDefaultExport function for compatibility with non-harmony modules__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;};// Object.prototype.hasOwnProperty.call// 判斷一個屬性是定義在對象本身而不是繼承自原型鏈__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };// __webpack_public_path____webpack_require__.p = "";// 加載入口模塊 main.js ,返回 exports,從而從入口文件開始執行,以遞歸的方式,將所有依賴執行并返回return __webpack_require__(__webpack_require__.s = "./app/main.js");
})({"./app/bye.js": (function(module, exports, __webpack_require__) {eval("const to = __webpack_require__(/*! ./to.js */ \"./app/to.js\");\nmodule.exports = function() {\n  var bye = document.createElement('div');\n  bye.textContent = \"Say Bye to \" + to.name;\n  return bye;\n};\n\n//# sourceURL=webpack:///./app/bye.js?");}),"./app/hello.js": (function(module, exports) {eval("module.exports = function() {\n  var hello = document.createElement('div');\n  hello.textContent = \"Say Hello!\";\n  return hello;\n};\n\n//# sourceURL=webpack:///./app/hello.js?");}),"./app/main.js": (function(module, exports, __webpack_require__) {eval("const hello = __webpack_require__(/*! ./hello.js */ \"./app/hello.js\");\nconst bye = __webpack_require__(/*! ./bye.js */ \"./app/bye.js\");\n\ndocument.querySelector(\"#root\").appendChild(hello()).appendChild(bye());;\n\n//# sourceURL=webpack:///./app/main.js?");}),"./app/to.js": (function(module, exports) {eval("module.exports = {name: \"小明\"};\n\n//# sourceURL=webpack:///./app/to.js?");})});

分析

webpack 的運行過程可分為:讀取配置參數,實例化插件,模塊解析處理(loader),輸出打包文件;在上面例子中,僅為 JavaScript 的引用,沒有使用插件和像CSS、less、圖片之類需要loader處理的模塊,所以上面的例子,過程只有讀取配置,識別入口及其引用模塊,打包幾步,生成最終的 bundle.js 文件。

簡單描述下 webpack 在這個過程中的執行流程:在配置文件中讀取入口,如果有配置 plugins 參數,那么也是在此時進行插件的實例化和鉤子函數的綁定;模塊解析,也就是loader加入的時刻,從入口文件開始,根據入口文件對其他模塊的依賴,結合配置文件中對不同種類型文件所使用的 loader(加載器) 說明,一個一個逐級對這些模塊進行解析處理,或壓縮,或轉義,生成瀏覽器可以直接識別的內容;最后將所有模塊進行打包,輸出打包后的文件。在上面的代碼中,已經對 bundle.js 內容進行了內容注釋,下面我們來分析下 bundle.js 的執行過程:

1、自執行函數

最后的輸出的文件 bundle.js 是一個 JavaScript 文件,其本身其實是一個自執行函數

(function(參數){})(參數)。

2、參數

自執行方法的參數為所有模塊組成的對象,key 為各個模塊的路徑,值為各模塊內部的執行代碼,觀察參數內部的代碼,對比打包前的源碼,可以發現凡是 require 都變成了__webpack_require__這個webpack自定義的模塊調用方法,而且源碼中的相對路徑也變成了最終執行位置的文件的相對路徑。

{"./app/bye.js": (function(module, exports, __webpack_require__) {eval("const to = __webpack_require__(/*! ./to.js */ \"./app/to.js\");\nmodule.exports = function() {\n  var bye = document.createElement('div');\n  bye.textContent = \"Say Bye to \" + to.name;\n  return bye;\n};\n\n//# sourceURL=webpack:///./app/bye.js?");}),"./app/hello.js": (function(module, exports) {eval("module.exports = function() {\n  var hello = document.createElement('div');\n  hello.textContent = \"Say Hello!\";\n  return hello;\n};\n\n//# sourceURL=webpack:///./app/hello.js?");}),"./app/main.js": (function(module, exports, __webpack_require__) {eval("const hello = __webpack_require__(/*! ./hello.js */ \"./app/hello.js\");\nconst bye = __webpack_require__(/*! ./bye.js */ \"./app/bye.js\");\n\ndocument.querySelector(\"#root\").appendChild(hello()).appendChild(bye());;\n\n//# sourceURL=webpack:///./app/main.js?");}),"./app/to.js": (function(module, exports) {eval("module.exports = {name: \"小明\"};\n\n//# sourceURL=webpack:///./app/to.js?");})}

3、執行

(1)自執行文件開始執行后,到自執行函數最底部,首先從入口文件開始加載

return __webpack_require__(__webpack_require__.s = "./app/main.js");

(2)__webpack_require__函數被調用,傳入參數 ./app/main.js,

function __webpack_require__(moduleId) {// moduleId 為 ./app/main.js// 首次進來,未加載,模塊還沒有緩存if(installedModules[moduleId]) {return installedModules[moduleId].exports;}// 新建 ./app/main.js 模塊,并存于緩存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;}

此時方法中執行 modules[moduleId].call(module.exports, module, module.exports,__webpack_require__); 相當于在名為 ./app/main.js 的模塊中執行如下代碼:

(function(module, exports, __webpack_require__) {eval("const hello = __webpack_require__(/*! ./hello.js */ \"./app/hello.js\");\nconst bye = __webpack_require__(/*! ./bye.js */ \"./app/bye.js\");\n\ndocument.querySelector(\"#root\").appendChild(hello()).appendChild(bye());;\n\n//# sourceURL=webpack:///./app/main.js?");})()

由于引用關系,接下來會再次執行兩次__webpack_require__方法,分別傳參模塊路徑 ./app/hello.js 和 ./app/bye.js;

(3)執行第一個__webpack_require__過程,除了傳參不同、執行的模塊不同,與第二步基本一致,再次找到了依賴模塊 to.js,再次調用__webpack_require__。

"./app/hello.js": (function(module, exports, __webpack_require__) {eval("const to = __webpack_require__(/*! ./to.js */ \"./app/to.js\");\nmodule.exports = function() {\n  var hello = document.createElement('div');\n  hello.textContent = \"Say Hello to \" + to.name;\n  return hello;\n};\n\n//# sourceURL=webpack:///./app/hello.js?");}),

(4)執行第二個__webpack_require__時,在 bye.js 中找到了對于 to.js 的依賴,所以將繼續調用__webpack_require__方法,只是傳參變成了./app/to.js,達到終點。

"./app/bye.js": (function(module, exports, __webpack_require__) {eval("const to = __webpack_require__(/*! ./to.js */ \"./app/to.js\");\nmodule.exports = function() {\n  var bye = document.createElement('div');\n  bye.textContent = \"Say Bye to \" + to.name;\n  return bye;\n};\n\n//# sourceURL=webpack:///./app/bye.js?");})

(5)到此時,整個從入口文件的開始的針對所依賴模塊的解析已經完成,所有的 js 代碼也已經引用完畢且放到了 bundle.js 中。

總結

到這里可以看到,webpack對js的打包,就是封裝為一個個單獨的方法,通過對這些方法的引用,達到模塊化的效果;而打包的過程,就是查找、解析、封裝這些方法的過程,整個執行路徑類似于一棵樹,從主干出發,沿著樹枝遞歸式的執行“require”方法,而且是直到這一根樹枝走到盡頭的時候才回頭尋找其他的方法,由于node的單線程,當項目龐大或者模塊間依賴錯綜復雜時,webpack打包會更加的耗費時間。

以上為對webpack4.x中針對js模塊處理的簡單理解,主要基于官方文檔的介紹和打包后文件的分析,源碼讀起來還是比較難懂,暫時不敢照量。對于 ES6、AMD 的模塊化方式,代碼分割的等,后續再進行補充。

以上如有問題,歡迎指正!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/450279.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/450279.shtml
英文地址,請注明出處:http://en.pswp.cn/news/450279.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

設計模式--抽象工廠(個人筆記)

一、抽象工廠的應用場景以及優缺點 1 應用場景&#xff1a; 如果系統需要多套的代碼解決方案&#xff0c;并且每套的代碼解決方案中又有很多相互關聯的產品類型&#xff0c;并且在系統中我們可以相互替換的使用一套產品的時候可以使用該模式&#xff0c;客戶端不需要依賴具體的…

利用阿里云OSS對文件進行存儲,上傳等操作

--pom.xml加入阿里OSS存儲依賴 <!--阿里云OSS存儲--> <dependency><groupId>com.aliyun.oss</groupId><artifactId>aliyun-sdk-oss</artifactId><version>2.8.3</version> </dependency> --配置阿里云oss相關常量參數 /…

Java并發編程之ThreadGroup

ThreadGroup是Java提供的一種對線程進行分組管理的手段&#xff0c;可以對所有線程以組為單位進行操作&#xff0c;如設置優先級、守護線程等。 線程組也有父子的概念&#xff0c;如下圖&#xff1a; 線程組的創建 1 public class ThreadGroupCreator {2 3 public static v…

springboot 緩存ehcache的簡單使用

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 步驟&#xff1a; 1. pom文件中加 maven jar包&#xff1a; <!-- ehcache 緩存 --><dependency><groupId>net.sf.eh…

Spring boot + mybatis plus 快速構建項目,生成基本業務操作代碼。

---進行業務建表&#xff0c;這邊根據個人業務分析&#xff0c;不具體操作 --加入mybatis plus pom依賴 <!-- mybatis-plus 3.0.5--> <dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId>&l…

給手機瀏覽器減負 輕裝上陣才能速度制勝

隨著手機瀏覽器的發展&#xff0c;瀏覽器已經變得臃腫不堪&#xff0c;各種“功能”系于一身&#xff0c;有廣告、社區、樂園等等&#xff0c;我們真的需要它們嗎&#xff1f;如何才能讓瀏覽器做到輕裝上陣&#xff0c;又能高效滿足我們需求呢&#xff1f; 過多“功能”的瀏覽器…

653. Two Sum IV - Input is a BST

題目來源&#xff1a; 自我感覺難度/真實難度&#xff1a; 題意&#xff1a; 分析&#xff1a; 自己的代碼&#xff1a; class Solution(object):def findTarget(self, root, k):""":type root: TreeNode:type k: int:rtype: bool"""Allself.InO…

解決 dubbo問題:Forbid consumer 192.xx.xx.1 access service com.xx.xx.xx.rpc.api.xx from registry 116.xx1

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 我的情況是&#xff1a; 原本我把服務放在A工程中&#xff0c;后來改到B工程中了&#xff0c;所以原來的服務不存在了&#xff0c;查不…

vue學習:7、路由跳轉

2019獨角獸企業重金招聘Python工程師標準>>> <body><div id"app"></div></body><script type"text/javascript">var Login {template: <div>我是登陸界面</div>};var Register {template: <div…

Spring Retry 重試機制實現及原理

概要 Spring實現了一套重試機制&#xff0c;功能簡單實用。Spring Retry是從Spring Batch獨立出來的一個功能&#xff0c;已經廣泛應用于Spring Batch,Spring Integration, Spring for Apache Hadoop等Spring項目。本文將講述如何使用Spring Retry及其實現原理。 背景 重試&…

inline 內聯函數詳解 內聯函數與宏定義的區別

一、在C&C中   一、inline 關鍵字用來定義一個類的內聯函數&#xff0c;引入它的主要原因是用它替代C中表達式形式的宏定義。表達式形式的宏定義一例&#xff1a;#define ExpressionName(Var1,Var2) ((Var1)(Var2))*((Var1)-(Var2))為什么要取代這種形式呢&#xff0c;且…

Oracle序列更新為主鍵最大值

我們在使用 Oracle 數據庫的時候&#xff0c;有時候會選擇使用自增序列作為主鍵。但是在開發過程中往往會遇到一些不規范的操作&#xff0c;導致表的主鍵值不是使用序列插入的。這樣在數據移植的時候就會出現各種各樣的問題。當然數據庫主鍵不使用序列是一種很好的方式&#xf…

dubbo forbid service的解決辦法

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 017-05-31 10:36:54.523 [http-nio-8080-exec-5] ERROR c.h.pdl.web.APIExceptionHandler - Unknown Exception, URI /payday-loan-co…

用SSH登錄遠程的機器,在遠程機器上執行本地機器上的腳本

假設本地的機器IP為10.245.111.90&#xff0c;我們想要在10.245.111.93上執行一個保存在10.245.111.90上的腳本。經過測試通過的命令如下&#xff1a;ssh root10.245.111.93 bash -s < /root/testlocal.sh如果要帶參數的話&#xff0c;那就需要參考這篇文章中描述的代碼了。…

golang學習之旅(1)

這段時間我開始了golang語言學習&#xff0c;其實也是為了個人的職業發展的拓展和衍生&#xff0c;語言只是工具&#xff0c;但是每個語言由于各自的特點和優勢&#xff0c;golang對于當前編程語言的環境&#xff0c;是相對比較新的語言&#xff0c;對于區塊鏈&#xff0c;大數…

為什么要在Linux平臺上學C語言?用Windows學C語言不好嗎?

用Windows還真的是學不好C語言。C語言是一種面向底層的編程語言&#xff0c;要寫好C程序&#xff0c;必須對操作系統的工作原理非常清楚&#xff0c;因為操作系統也是用C寫的&#xff0c;我們用C寫應用程序直接使用操作系統提供的接口&#xff0c;Linux是一種開源的操作系統&am…

數據庫中Schema(模式)概念的理解

在學習SQL的過程中&#xff0c;會遇到一個讓你迷糊的Schema的概念。實際上&#xff0c;schema就是數據庫對象的集合&#xff0c;這個集合包含了各種對象如&#xff1a;表、視圖、存儲過程、索引等。為了區分不同的集合&#xff0c;就需要給不同的集合起不同的名字&#xff0c;默…

linux系統中打rz命令后出現waiting to receive.**B0100000023be50

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 linux系統中打rz命令后出現 waiting to receive.**B0100000023be50 而沒有出現選擇文件彈出框是什么問題&#xff1a; 我本來用的是 gi…

golang學習之旅(2)- go的數據基本數據類型及變量定義方式

叮鈴鈴&#xff0c;這不有人在評論問下一篇何時更新&#xff0c;這不就來了嘛&#xff0c;&#x1f604; 今天我們說說golang 的基本數據類型 基本類型如下&#xff1a; //基本類型 布爾類型&#xff1a;bool 即true 、flase 類似于java中的boolean 字符類型&#xff1a;s…

StackExchange.Redis 官方文檔(六) PipelinesMultiplexers

流水線和復用 糟糕的時間浪費。現代的計算機以驚人的速度產生大量的數據&#xff0c;而且高速網絡通道(通常在重要的服務器之間同時存在多個鏈路)提供了很高的帶寬&#xff0c;但是計算機花費了大量的時間在 等待數據 上面&#xff0c;這也是造成使用持久性鏈接的編程方式越來越…