前端模块化的理解

1,913 阅读5分钟

前端模块化的背景

js是为了页面动画+表单提交而设计的,无模块化+命名空间的概念。

js模块化演化过程

  1. 全局函数

    将不同的功能封装成不同的函数

    缺点:容易引起全局命名冲突

function m1() {}
funciton m2() {}

2.简单对象的方式

  • 减少了全局变量,解决了命名冲突

    缺点: 数据不安全,外部可以直接修改内部模块的数据

  let myModule ={
    data:'测试123',
    foo() {
      console.log(this.data);
    }
  }
  myModule.data = '123'
  myModule.foo(); // 外部调用 123

3.IIFE模式

  • 数据私有,外部只能通过暴露的方法使用

       let getName = (function(dep1,dep2){
          let data = '测试数据';
          let obj = {
            data,
          }
          function test1 () {
            data = 'ffff'
            obj.data = '对象的更改'
            console.log(data+'1');
          }
    
          return {
            test1,
            data,
            obj
          }
        })('name','age');
    
        getName.test1(); // ffff1 , 只能动态操作数据返回
        console.log(getName) //  {data:测试数据,obj:{data:对象的更改}}  
       // 简单值,不会更改; 对象的引用会更改
        
    

揭示模式 :上层无需了解底层实现,仅关注抽象

  • 继续模块化展开
  • 转向框架 :juery | vue | react
  • 设计模式

4.script 标签引入

  • 请求过多
  • 依赖模糊
  • 难以维护

问题:Script的标签的参数:normal async 和 defer 的区别?

总结:

  • normal:解析立即阻塞,立即执行当前的script,会阻塞渲染,结束之后再进行下一个script

  • Defer: 解析到开始异步加载,解析完成后开始执行,defer顺序执行;适合场景:主路径优先(先下载资源,让用户先看到页面,在执行逻辑)

  • Async::解析开始异步加载,下载完成后开始执行并阻塞渲染,执行完成之后继续渲染,async没有顺序。模块化体验优先(先下载资源,会重复执行导致页面重绘,最后执行绘制比较好)

什么是模块化

  • 将一个复杂的程序依据一定的规则封装成几个块或者文件,并组合在一起
  • 块的内部数据是私有的,只对外暴露一些接口与外部通信

模块化的好处

  • 避免命名冲突
  • 更好的代码分离,按需加载
  • 更高的复用
  • 更高的维护性

模块化规范

commonjs

nodejs的模块化规范,在服务端最先实现。简称cjs,也可以在浏览器环境使用

1.概念

  • 任何一个文件就是一个模块;
  • 一个文件中定义的变量、函数、类都是私有的,对其他文件不可见;
  • 只导出接口与别的模块交互 2.基本语法
     module.exports = value;  // 导出
     exports.xxx = value
     const a = require('sdfsdf')  // 引入

3.cjs特点

  • 模块可以多次加载,但是只会在第一次加载时运行一次;运行结果会被缓存
  • 顺序加载
  • 模块加载机制,一旦输出一个值,内部变化就不会影响不到这个值
    var counter = 3;
      var obj ={
        counter:1
      }

      function incCounter () {
        counter++;
        obj.counter = 2;
      }

      module.exports = {
        counter,
        obj,
        incCounter
      }

      const {  counter,obj,incCounter} = require('./testnode.js');
      console.log('incCounter',incCounter())
      console.log('counter',counter); // 3 ,有缓存
      console.log('obj',obj); // 对象的引用 obj:{count:2} 

4.CJS为什么要设计成同步

  • Node的模块文件一般都是已经存在于本地硬盘,所以加载起来比较快,没必要采用非同步模式

AMD 规范

1.概念

一种异步加载模块+回调函数的思想实现,主要代表库是require.js。cjs是同步加载的,在浏览器环境中希望是异步加载的

2.语法

  • define 定义代码为块;
  • 通过require方法,实现代码的模块加载。
   define('name',[dep,dep2],(dep1,dep2) => {
     // 逻辑
     return {  // 暴露接口

     }
   })

CMD 规范

1.概念 专门用于浏览器端,模块加载都是异步的。代表sea.js AMD & CJS 的结合体

2.语法

  define('module',[deps],function(require,exports,module){

    })

ES6 规范

1.概念 js模块化的官方实现

2.语法

     import a form  'module';

     export const a = 'sfs'; // 必须知道加载的变量名字或者函数名字,否则无法加载

     export default {} // 默认导出,是可以让用户不用阅读文档就能加载模块使用

3.特点 值的引用,动态的引用,不会缓存值,模块变量绑定其所在的模块

      export let counter = 3;
      export function incCounter() {
        counter++;
      }

      import {counter,incCounter} from './lib';

      console.log(counter) // 3
      incCounter();
      console.log(counter) // 4 没缓存,值的引用

CJS && ES6 Module

  • 区别

    cjs 模块输出是一个值的拷贝,es6是输出的值的引用

  • cjs运行时加载, es6是编译时输出接口

    cjs导出的是一个对象,脚本运行时才完全生成,es6对外只是一种静态定义,在代码静态解析阶段就会生成

  • cjs运行时,加载整个模块,使用的时候再读取接口; es6 是可以指定加载某个值,而不是加载整个模块。

额外问题

为何一些开源框架,为何把全局、指针以及框架本身引用作为参数

function(window,$,undefined){

      Window.webshow = function(){}

})(window,jQuery)

// 阻断思路

 Windon的全局作用域转化为局部作用域,提升执行效率 2.编译时优化

 Jquery 1.独立定制和挂载 2.防止全局串扰

 Undefined 防止重写

如果在AMD中兼容老代码

  Define(‘amdMOdule’,[],require => {
 
 })

手写兼容cjs&amd

cjs 和 es module 如何相互支持

  • cjs 支持 es module

1.在node版本未支持之前,利用一些构建工具(webpack),把js文件打包,node环境,引入打包后的文件就行了

   // webpack 打包文件
   /dist/main.js
   
   引入
   import main from './dist/main.js';

2.node 官方支持,新建文件名字后缀为.mjs

   main.mjs文件下
   import a from './main.js';
   function c() {
   }
   export defalut c;
  • es module 支持 cjs esm 可以直接加载cjs,只能整体加载。因为esm是一个支持静态解析,而cjs导出是一个对象,只能全部一起加载。