es6 Module 原理

82 阅读5分钟
// 文件a:入口文件
import {b1,sayB} from './b.js'
export var a1 = 100;
setTimeout(()=>{a1 = 101},1000)
export function sayA(){
  console.log('a.js sayA',b1,sayB)
}
var a2 = 100;
export default a2

// 文件b
import {sayC} from './c.js'
export var b1 = 200;
setTimeout(()=>{b1 = 201},1000)
export function sayB(){
  console.log('a.js sayB',sayC)
}
var b2 = 100;
export default b2

// 文件c
import {sayB} from './b.js'
export var c1 = 300;
setTimeout(()=>{c1 = 301},1000)
export function sayC(){
  console.log('a.js sayC',sayB);
}
var c2 = 100;
export default c2

webpack编译后:

(() => { 
// 存放依赖的模块,就是所有通过import导入的文件都会保存在这,我们书写的模块带码会被编译成函数,以url为key保存在__webpack_modules__对象上
var __webpack_modules__ = {
  // 模块b文件的代码,导出对象都是我们执行函数时传入的
  "./src/b.js":(__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
    __webpack_require__.r(__webpack_exports__);
    // 1.往导出对象__webpack_exports__上绑定我们导出的内容
    __webpack_require__.d(__webpack_exports__, {
      b1: () => (/* binding */ b1),
      sayB: () => (/* binding */ sayB),
      "default": () => (__WEBPACK_DEFAULT_EXPORT__),
    });
    // 2.有依赖则加载依赖模块
    var _c_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./c.js */ "./src/c.js");
    // 3. 等依赖加载完成再,执行模块代码
    var b1 = 200;
    setTimeout(function () {
      b1 = 201;
    }, 1000);
    function sayB() {
      console.log('a.js sayB', _c_js__WEBPACK_IMPORTED_MODULE_1__.sayC);
    }
    var b2 = 100;
    const __WEBPACK_DEFAULT_EXPORT__ = (b2);
  },
  // 模块c文件的代码,导出对象都是我们执行函数时传入的
  "./src/c.js":(__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
    __webpack_require__.r(__webpack_exports__);
    // 1.往导出对象__webpack_exports__上绑定我们导出的内容
    __webpack_require__.d(__webpack_exports__, {
        c1: () => (/* binding */ c1),
        sayC: () => (/* binding */ sayC),
        "default": () => (__WEBPACK_DEFAULT_EXPORT__),
    });
    // 2.有依赖则加载依赖模块
    var _b_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./b.js */ "./src/b.js");
    // 3. 等依赖加载完成再,执行模块代码
    var c1 = 300;
    setTimeout(function () {
      c1 = 301;
    }, 1000);
    function sayC() {
      console.log('a.js sayC', _b_js__WEBPACK_IMPORTED_MODULE_1__.sayB);
    }
    var c2 = 100;
    const __WEBPACK_DEFAULT_EXPORT__ = (c2);
  }
}
// 缓存模块的导出对象{"./src/a.js":{exports:{}}}
var __webpack_module_cache__ = {};
// 加载模块:读取缓存__webpack_module_cache__[url],有缓存有则直接返回对应值,没有缓存则执行模块代码__webpack_modules__[url],然后将导出内容缓存
function __webpack_require__(moduleId) {
  // 1.查找缓存,有则直接返回缓存的导出对象
  var cachedModule = __webpack_module_cache__[moduleId];
  if (cachedModule !== undefined) {
    return cachedModule.exports;
  }
  // 2.没有缓存则创建一个导出对象exports
  var module = __webpack_module_cache__[moduleId] = {
    exports: {}
  };
  // 3.传入创建的导出对象exports,执行模块对应的函数
  //       1.往导出对象exports上绑定我们导出的内容,通过Object.defineProperty(__webpack_exports__,key,{get(){xxx}})设置get,
  //       2.有依赖文件则递归加载文件模块
  //       3.等依赖加载完成再,再执行模块代码
  __webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);
  // 4.返回导出对象
  return module.exports;
}

// 入口文件的代码:
// 1.创建一个导出对象
var __webpack_exports__ = {};
__webpack_require__.r(__webpack_exports__);
//2.往导出对象上绑定导出内容,通过Object.defineProperty(__webpack_exports__,key,{get(){xxx}})设置get
__webpack_require__.d(__webpack_exports__, {
  a1: () => (/* binding */ a1),
  sayA: () => (/* binding */ sayA),
  "default": () => (__WEBPACK_DEFAULT_EXPORT__),
});
// 3.有依赖则加载依赖模块,__webpack_require__(moduleId:url)
var _b_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./b.js */ "./src/b.js");
// 4.执行模块代码
var a1 = 100;
setTimeout(function () {
  a1 = 101;
}, 1000);
function sayA() {
  console.log('a.js sayA', _b_js__WEBPACK_IMPORTED_MODULE_1__.b1, _b_js__WEBPACK_IMPORTED_MODULE_1__.sayB);
},
var a2 = 100;
const __WEBPACK_DEFAULT_EXPORT__ = (a2);
})()

我们在a.js中导入了b.js import {b1,sayB} from './b.js'

我们在b.js中导入了c.js import {sayC} from './c.js'

我们在c.js中导入了b.js import {sayB} from './b.js'

只要被任何一个import 导入过的js文件(例如这里的b.js、c.js)代码都会被编译成函数保存__webpack_modules__:{}对象上,通过__webpack_require__()函数来加载模块

没有被任何文件import 导入过的js文件(一般是入口文件,例如这里的a.js)则不会被编译成函数,代码直接在自执行函数内执行

入口文件会被webpack编译成一个自执行的函数(() => { 这里放模块相关的代码})()

模块相关的代码:

存放依赖的模块: var webpack_modules = {url:(module,exports,webpack_require)=>{模块代码}}

缓存模块导出对象: var webpack_module_cache = {"./src/a.js":{exports:{}}};

加载模块的函数: function webpack_require(moduleId) {}

// 入口文件的代码: 1.创建一个导出对象__webpack_exports__={};

2.往导出对象上绑定导出内容,通过Object.defineProperty(webpack_exports,key,{get(){xxx}})设置get 3.是否有依赖文件,没有则直接进入到下一步,有则递归加载文件模块,通过__webpack_require__(moduleId:url),加载执行完成才会进入到下一步;

1.查找缓存__webpack_module_cache__[moduleId:url],有则直接返回缓存的导出对象exports

2.没有缓存则创建一个空导出对象exports={},保存到__webpack_module_cache__[moduleId]={exports:{}}上

3.传入新创建的导出对象exports,执行模块对应的函数__webpack_modules__[moduleId].call(exports)

1.往导出对象exports上绑定我们导出的内容,通过Object.defineProperty(webpack_exports,key,{get(){xxx}})设置get,

2.是否有依赖文件,没有则直接进入到下一步,有则递归加载文件模块,通过__webpack_require__(moduleId:url),加载执行完成才会进入到下一步;

3.执行模块代码

4.执行模块代码

所以代码的执行顺序是深度优先的,先执行c.js===>在执行b.js===>最后执行a.js

文件b.js和文件c.js是循环引用的为什么不会栈溢出报错?

在执行a.js时我们第一次加载b.js,此时缓存__webpack_module_cache__中不存在b.js的导出对象,则会执行b.js对应的函数modelB(),函数执行时会先去加载c.js, 此时缓存__webpack_module_cache__中不存在c.js的导出对象,则会执行c.js对应的函数modelC(),函数modelC中我们第二次加载b.js,这时会直接读取__webpack_module_cache__缓存中的值直接返回, 如果没有缓存值判断,则会一直循环调用直到栈溢出。这里使用缓存后则不会再去执行b.js对应的函数modelC(),这样就可以避免的循环调用。

export 和export default 导出的值是不一样的,export导出的是值的引用(导入和导出使用的是导出文件中的同一个变量),export default导出的是值的拷贝(导出文件中的变量,与导入文件使用的是不同的变量)