zoukankan      html  css  js  c++  java
  • 11111111--临时保存

    4.2 __webpack_require__.e函数

    该函数主要作用就是创建一个<script>标签,然后将chunkId对应的文件通过该标签加载。

    源代码如下:

     1 __webpack_require__.e = function requireEnsure(chunkId) {
     2         var promises = [];
     3 
     4         // JSONP chunk loading for javascript
     5 
     6         var installedChunkData = installedChunks[chunkId];
     7         if (installedChunkData !== 0) { // 0 means "already installed".
     8 
     9             // a Promise means "currently loading".
    10             if (installedChunkData) {
    11                 promises.push(installedChunkData[2]);
    12             } else {
    13                 // setup Promise in chunk cache
    14                 var promise = new Promise(function (resolve, reject) {
    15                     installedChunkData = installedChunks[chunkId] = [resolve, reject];
    16                 });
    17                 promises.push(installedChunkData[2] = promise);
    18 
    19                 // start chunk loading
    20                 var script = document.createElement('script');
    21                 var onScriptComplete;
    22 
    23                 script.charset = 'utf-8';
    24                 script.timeout = 120;
    25                 if (__webpack_require__.nc) {
    26                     script.setAttribute("nonce", __webpack_require__.nc);
    27                 }
    28                 script.src = jsonpScriptSrc(chunkId);
    29 
    30                 // create error before stack unwound to get useful stacktrace later
    31                 var error = new Error();
    32                 onScriptComplete = function (event) {
    33                     // avoid mem leaks in IE.
    34                     script.onerror = script.onload = null;
    35                     clearTimeout(timeout);
    36                     var chunk = installedChunks[chunkId];
    37                     if (chunk !== 0) {
    38                         if (chunk) {
    39                             var errorType = event && (event.type === 'load' ? 'missing' : event.type);
    40                             var realSrc = event && event.target && event.target.src;
    41                             error.message = 'Loading chunk ' + chunkId + ' failed.
    (' + errorType + ': ' + realSrc + ')';
    42                             error.type = errorType;
    43                             error.request = realSrc;
    44                             chunk[1](error);
    45                         }
    46                         installedChunks[chunkId] = undefined;
    47                     }
    48                 };
    49                 var timeout = setTimeout(function () {
    50                     onScriptComplete({type: 'timeout', target: script});
    51                 }, 120000);
    52                 script.onerror = script.onload = onScriptComplete;
    53                 document.head.appendChild(script);
    54             }
    55         }
    56         return Promise.all(promises);
    57     };

    主要做了如下几个事情:

    1)判断chunkId对应的模块是否已经加载了,如果已经加载了,就不再重新加载;

    2)如果模块没有被加载过,但模块处于正在被加载的过程,不再重复加载,直接将加载模块的promise返回。

    为什么会出现这种情况?

    例如:我们将index.js中加载print.js文件的地方改造为下边多次通过ES6的import加载print.js文件:

     1 button.onclick = (
     2     e => {
     3         
     4         import('./print').then(
     5             module => {
     6                 var print = module.default;
     7                 print();
     8             }
     9         );
    10 
    11         import('./print').then(
    12             module => {
    13                 var print = module.default;
    14                 print();
    15             }
    16         )       
    17     }
    18 );

     从上边代码可以看出,当第一import加载print.js文件时,还没有resolve,就又执行第二个import文件了,而为了避免重复加载该文件,就通过将这里的判断,避免了重复加载。

    3)如果模块没有被加载过,也不处于加载过程,就创建一个promise,并将resolve、reject、promise构成的数组存储在上边说过的installedChunks缓存对象属性中。然后创建一个script标签加载对应的文件,加载超时时间是2分钟。如果script文件加载失败,触发reject(对应源码中:chunk[1](error),chunk[1]就是上边缓存的数组的第二个元素reject),并将installedChunks缓存对象中对应key的值设置为undefined,标识其没有被加载。

    4)最后返回promise

    注意:源码中,这里返回的是Promise.all(promises),分析代码发现promises好像只可能有一个元素。可能还没遇到多个promises的场景吧。留待后续研究。

     4.3 自执行函数体代码分析

    整个app.bundle.js文件是一个自执行函数,该函数中执行的代码如下:

    1     var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
    2     var oldJsonpFunction = jsonpArray.push.bind(jsonpArray); // 复制一个数组的push方法,这个方法的this是jsonpArray
    3     jsonpArray.push = webpackJsonpCallback; // TODO: 为什么要复写push,而不是直接增加一个新方法名?
    4     jsonpArray = jsonpArray.slice(); // 拷贝一个新数组
    5     for (var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);
    6     var parentJsonpFunction = oldJsonpFunction;

    这段代码主要做了如下几个事情:

    1)定义了一个全局变量webpackJsonp,改变量是一个数组,该数组变量的原生push方法被复写为webpackJsonpCallback方法,该方法是懒加载实现的一个核心方法,具体代码会在下边分析。

    该全局变量在懒加载文件中被用到。在print.bundle.js中:

    1 (window["webpackJsonp"] = window["webpackJsonp"] || []).push([ // 注意:这个push实际是webpackJsonpCallback方法
    2     ["print"],
    3     {
    4         "./src/print.js": (function(module, __webpack_exports__, __webpack_require__) {...})
    5     }
    6 ]);

    2)将数组的原生push方法备份,赋值给parentJsonpFunction变量保存。

    注意:该方法的this是全局变量webpackJsonp,也就是说parentJsonpFunction('111')后,全局数组变量webpackJsonp就增加了一个'111'元素。

    该方法在webpackJsonpCallback中会用到,是将懒加载文件的内容保存到全局变量webpackJsonp中。

    3)上边第一步中复写push的原因?

    可能是因为在懒加载文件中,调用了复写后的push,执行了原生push的功能,因此,为了更形象的表达该意思,因此直接复写了push。

    但个人认为这个不太好,不易读。直接新增一个_push或者extendPush,这样是不是读起来就很简单了。

    4.4 webpackJsonpCallback函数分析

    该函数是懒加载的一个比较核心代码。其代码如下:

     1     function webpackJsonpCallback(data) {
     2         var chunkIds = data[0];
     3         var moreModules = data[1];
     4 
     5         // add "moreModules" to the modules object,
     6         // then flag all "chunkIds" as loaded and fire callback
     7         var moduleId, chunkId, i = 0, resolves = [];
     8         for (; i < chunkIds.length; i++) {
     9             chunkId = chunkIds[i];
    10             if (installedChunks[chunkId]) {
    11                 resolves.push(installedChunks[chunkId][0]);
    12             }
    13             installedChunks[chunkId] = 0;
    14         }
    15         for (moduleId in moreModules) {
    16             if (Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
    17                 modules[moduleId] = moreModules[moduleId];
    18             }
    19         }
    20         if (parentJsonpFunction) parentJsonpFunction(data);
    21 
    22         while (resolves.length) {
    23             resolves.shift()();
    24         }
    25     };

    参数说明:

    参数是一个数组。有两个元素:第一个元素是要懒加载文件中所有模块的chunkId组成的数组;第二个参数是一个对象,对象的属性和值分别是要加载模块的moduleId和模块代码函数。

    该函数主要做的事情如下:

    1)遍历参数中的chunkId:

    判断installedChunks缓存变量中对应chunkId的属性值:如果是真,说明模块正在加载,因为从上边分析中可以知道,installedChunks[chunkId]只有一种情况是真,那就是在对应的模块正在加载时,会将加载模块创建的promise的三个信息搞成一个数组[resolve, reject, proimise]赋值给installedChunks[chunkId]。将resolve存入resolves变量中。

    将installedChunks中对应的chunkId置为0,标识该模块已经被加载过了。

    2)遍历参数中模块对象所有属性:

    将模块代码函数存储到modules中,该modules是入口文件app.bundle.js中自执行函数的参数。

    这一步非常关键,因为执行模块加载函数__webpack_require__时,获取模块代码时,就是通过moduleId从modules中查找对应模块代码。

    3)调用parentJsonpFunction(原生push方法)将整个懒加载文件的数据存入全局数组变量window.webpackJsonp。

    4)遍历resolves,执行所有promise的resolve:

    当执行了promise的resolve后,才会走到promise.then的成功回调中,查看源码可以看到:

     1             button.onclick = (
     2                 e => {
     3                     __webpack_require__.e("print")
     4                         .then(__webpack_require__.bind(null, "./src/print.js"))
     5                         .then(
     6                             module => {
     7                                 var print = module.default;
     8                                 print();
     9                             }
    10                         )
    11                 }
    12             );

    resolve后,执行了两个then回调:

    第一个回调是调用__webpack_require__函数,传入的参数是懒加载文件中的一个模块的moduleId,而这个moduleId就是上边存入到modules变量其中一个。这样就通过__webpack_require__执行了模块的代码。并将模块的返回值,传递给第二个then的回调函数;

    第二个回调函数是真正的onclick回调函数的业务代码。

    5)重要思考:

    从这个函数可以看出:

    调用__webpack_require__.e('print')方法,实际只是将对应的print.bundle.js文件加载和创建了一个异步的promise(因为并不知道什么时候这个文件才能执行完,因此需要一个异步promise,而promise的resolve会在对应的文件加载时执行,这样就能实现异步文件加载了),并没有将懒加载文件中保存的模块代码执行。

    在加载对应print.bundle.js文件代码时,通过调用webpackJsonpCallback函数,实现触发加载文件时创建的promise的resolve。

    resolve触发后,会执行promise的then回调,这个回调通过__webpack_require__函数执行了真正需要模块的代码(注意:如果print.bundle.js中有很多模块,只会执行用到的模块代码,而不是执行所有模块的代码),执行完后将模块的exports返回给promise的下一个then函数,该函数也就是真正的业务代码了。

    综上,可以看出,webpack实际是通过promise,巧妙的实现了模块的懒加载功能。

     5 懒加载构建原理图

     

  • 相关阅读:
    AtCoder Beginner Contest 205
    Codeforces Round #725 (Div. 3)
    Educational Codeforces Round 110 (Rated for Div. 2)【A
    Codeforces Round #722 (Div. 2)
    AtCoder Beginner Contest 203(Sponsored by Panasonic)
    AISing Programming Contest 2021(AtCoder Beginner Contest 202)
    PTA 520 钻石争霸赛 2021
    Educational Codeforces Round 109 (Rated for Div. 2)【ABCD】
    AtCoder Beginner Contest 200 E
    Educational Codeforces Round 108 (Rated for Div. 2)【ABCD】
  • 原文地址:https://www.cnblogs.com/zhaoweikai/p/10970268.html
Copyright © 2011-2022 走看看