js模块化

89 阅读3分钟

JS模块化

1.不得不说的历史

背景

JS本身定位: 简单的页面你设计-页面动画 + 基本的表单提交 并无模块化 or 命名空间的概念

js的模块化的需求日益增长

2. 传统:无模块化 => CJS => AMD => CMD => ESM

幼年期:无模块化

1.开始需要在页面中增加一些不同的js:动画、表单、格式化工具 2.多种js文件被分在不同的文件中 3.不同的文件又被同一个模板所引用

//test.html
<script src = "jqery.js"></script>
<script src = "mian.js"></script>
<script src = "tools.js"></script>

认可: 文件分离是最基础的模块化,第一步

  • 追问: script标签两个参数 - async & defer
<script src="jquery.js" type="text/jacascript" async> </script>

总结: 普通- 解析到标签,立刻pending,并且下载执行 defer - 解析到标签开始异步下载,解析完成后开始执行 async - 解析到标签开始异步下载,下载完成后立刻执行并阻塞渲染,执行完成后,继续渲染 1.兼容性 > IE9 => 其他兼容性问题 2.问题方向 => 浏览器渲染原理、同步异步原理、模块化加载原理

问题出现:

  • 污染全局作用域=>不利于大型项目的开发以及多人团队共建

成长期:模块化的雏形 - IIFE(语法侧的优化)

作用域的把控

例子:

//定义一个全局变量
let count = 0;

//代码块1
const increase = {} => ++count;
//代码块2
const reset = () =>{
    count = 0;
}
increase();
console.log(count);
reset();
console.log(count);

利用函数块级作用域

(()=>{
     let count = 0;
     // ...
 })()

定义函数+立即执行 => 独立的空间 初步实现了一个最最最简单的模块

尝试去定义一个最简单的模块 - 模块 and 外部

IIFE模块(立即执行函数模块)

//iifeModule
const module = (() =>{
       let count = 0;
       return {
             increase:() => ++count;
             reset:() =>{
                   count = 0;
             }
       }
})();
module.increase();
module.reset();

** 追问:有额外依赖的时候,如何优化IIFE的相关代码 **

优化1:依赖其他模块的IIFE

const iifeModule = (()=>{
let count = 0;
const obj = {
increase:()=> ++count;
reset:()=>{
count = 0;
}
}
})()

** 追问:有额外依赖的时候,如何优化IIFE的相关代码**

优化1:依赖其他模块的IIFE

const iifeModule = (()=>{
     let count = 0;
     const obj = {
          increase:()=>++count;
          reset:()=>{
              count = 0;
          }
       }
       //__dependencyModule
  })(dependencyModule,dependencyModule2);

** 面试1:了解早期jQuery依赖处理还有模块加载方案? / 了解传统IIFE是如何解决多方依赖的问题 答:IIFE + 传参调配 **

实际上,传统框架应用了一种revealing的写法 揭示模式

const iifeModule = (()=>{
let count = 0;
const increase = () => ++count;
const reset = () =>{
     count = 0;
     //dependencyModule1.xxx
     //...
   }
   
return {
     increase,
     reset
}
})(dependencyModule,dependencyModule2)
iifeModule.increase(1);
iifeModule.reset();
//返回的是能力= 使用方传参(x) +本身逻辑能力 + 依赖的能力
//$('').attr('title','new');
  • 追问: 面试后续引导方向: 1.深入模块化实现 2.转向框架:jquery、vue、react的模块化细节,以及框架特征原理 3.转向设计模式 - 注重模块化的设计模式

成熟期

CJS - Common.js

node.js制定 特征:

  • 通过module + export 去对外暴露接口
  • 通过require来调用其他模块

模块组织方式 main.js

//引入部分
const dependencyModule1 = require(./dependencyModule1);
const dependencyModule2 = require(./dependencyModule2);


//逻辑部分
let count =  0;
const increase = () => ++ count;
const reset = val =>{
count = 0//dependencyModule.xxx
//...
}



//暴露接口部分
export.increase =  increase;
export.reset = reset;

module.exports  = {
   increase,reset
}

    const {increase,reset } = require('main.js');

    increase();
    reset();

** 可能被问到的问题 ** 实际执行处理

(function(thisValue,export,require,module){
const dependencyModule1 = require(./dependencyModule1);
const dependencyModule2 = require(./dependencyModule2);
}).call(thisValue,exports,require,module);
 //部分开源项目分别插入全局,指证,框架作为参数
 (function(window,$,undefined){
      const _show = function(){
         $("#app".val("hi zhaowa");
      }
       window.webShow = _show;
  })(window,jQuery);
  
  //window - 1.全局作用域转成局部作用域,执行不用全局调用,提升效率2.编译时候,优化压缩(function(c){}(window)
  //jQuery - 1.独立定制复写,保障稳定 2。防止全局污染
  //undefined - 防止重写

  • 优点 CJS率先在服务端实现了从框架层面解决依赖,全局变量污染的问题
  • 缺点: 针对的是服务端,对于异步以来都没有很友好的处理和考虑

新的问题 -- 异步依赖

AMD规范

通过异步加载 + 允许定制回调函数 经典的实现框架:require.js

新增定义方式:

//通过define来定义一个模块,然后用require去加载
define(id,[depend],callback);
require([module],callback);
define('amoModule',['dependencyModule1','dependencyModule2'],(dependencyModule1,dependencyModule2)
=>{
      //业务逻辑
      let count =  0;
      const increase = () => ++ count;
      const reset = val =>{
           count = 0//dependencyModule.xxx
           //...
    }
})                
    require(['amdModule'].amdModule => {
    amdModule.increase();
    );

** 面试题2,如果在AMD中想兼容已有的代码 ** 1.增加定义阶段 2.返回做回调内return

define('amdModule',[],require =>{
  //引入部分
      const dependencyModule1 = require(./dependencyModule1);
      const dependencyModule2 = require(./dependencyModule2);


//逻辑部分
      let count =  0;
      const increase = () => ++ count;
      const reset = val =>{
           count = 0//dependencyModule.xxx
           //...
    }


    return {
        increase,reset
    }
})

** 面试题3:兼容判断AMD&CJS UMD的出现

  (define('amdModule',[],(require,export,module) =>{
  //核心逻辑
   let count =  0;
      const increase = () => ++ count;
      const reset = val =>{
           count = 0//dependencyModule.xxx
           //...
    }
    
    export.increase  = increase;
    export.reset = reset;
  }))(
  //目标:一次性去区分CJS和AMD
  //1.CJS factory
  //2.module module.exports
  //3.define
  typeOf module === "Object"
  && module.exports
  && typeOf define !== "function"
       ? //是CJS
           factory => module.exports = factory(require,exports,module);
       : //是AMD
           define
  )
  • 优点:解决了浏览器中异步加载模块,可以并行加载多个模块
  • 缺点:会有引入成功,没有考虑按需加载
CMD规范

按需加载 足以应用框架 sea.js

//依赖就近
define('module',(require,exports,module)=>{
   let $ = require('jquery');
   let dependencyModule1 = require('./dependencyModule1');
})

  • 优点:按需加载,依赖就近
  • 缺陷:依赖于打包,加载逻辑存在与每个模块中,扩大了模块的体积

追问: AMD和CMD => 依赖就近,按需加载

ESM

走向新时代

新增定义:引入关键字 -- import 导出关键字 -- export

模块引入和导出的地方

 //引入区域
 import dependencyModule1 from `./dependencyModule1.js`;
 import dependencyModule2 from `./dependencyModule2.js`;
  //核心代码区域
  
  //核心逻辑
   let count = 0;
   const increase = () =>++count;
   const reset = val =>{
      count = 0;
      //dependencyModule1.xxx
      //
  }
  
  export default{
      increase,reset
  }
  

** 追问 :ESM动态模块 ** 考察 export promise

** 懒加载 ** ESM11 原生解决方案

import('./esModule.js').then(dynamicEsModule => {
dynamicEsdule.increase();
{)
//emd
  • 优点:通过一种最统一的形态整合了Js的模块化
  • 缺憾:本质上是运行时做的依赖分析

3.现代: 拓扑组合 => 工程化