• 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 懒加载构建原理图

     

  • 相关阅读:
    ajax 前台返回后台传递过来的数组
    js中push的用法
    split 的用法
    ckeditor上传图片
    FTP安装配置
    批量删除.svn文件
    Ext flex属性
    Extjs3 主题样式
    Ext.apply与Ext.applyIf
    SharePoint2010 Office Web Apps
  • 原文地址:https://www.cnblogs.com/zhaoweikai/p/10970268.html
Copyright © 2020-2023  润新知