乐趣区

关于前端:前端工程化之模块化

模块化的背景

  • 前端模块化是一种规范,不是实现
  • 了解模块化是了解前端工程化的前提
  • 前端模块化是前端我的项目规模化的必然结果

什么是前端模块化?

前端模块化就是将简单程序依据标准拆分成若干模块,一个模块包含输出和输入。而且模块的外部实现是公有的,它通过对外裸露接口与其余模块通信,而不是间接调用。当初在 HTML 文件中能够援用的 script 包含脚本和模块,其中模块具备更高的开发效率(可读性强、复用性高),而脚本具备更高的页面性能,因为模块绝对文件多,加载速度慢。须要留神的是模块在浏览器中运行会存在兼容性的问题,在 script 申明 type=”module” 即可应用 ES Module 模块化标准。

模块化的进化过程

  1. 全局 function 模式,将不同的性能封装成不同的函数,缺点是容易引发全局命名抵触。比方刚开始在一个 js 文件中中定义一个一个的函数作为模块,然而这些函数挂载 windows 上的,净化全局作用域,我在另一个 js 文件中定义同名的函数就会导致命名抵触,而且无奈治理模块依赖关系,进而早起模块化齐全依附约定。
    function api(){
        return {
            data:{
                a:1,
                b:2
            }
        }
    }
  1. 全局 namespace 模式,约定每个模块裸露一个全局对象,所有的模块成员裸露在对象下,毛病是内部可能批改模块外部数据。将函数放在对象模块中挂载到 window 上,然而这样内部也能批改对象外部的属性。
var __Module={api(){
        return {
            data:{
                a:1,
                b:2
            }
        }
    }
}

那么咱们能够通过函数作用域加闭包的个性来解决。

  1. IIFE 模式,将模块成员放在函数公有作用域中,通过自执行函数创立闭包,对于裸露给内部的成员通过挂载到全局对象这种形式实现。这种形式实现了公有成员的概念,缺点是无奈解决模块间相互依赖问题。
(function(){
    var a =1;
    function getA(){return a;}
    function setA(a){window.__module.a = a;}
    window.__module = {
        a,
        getA,
        setA
    }
})();
  1. IIFE 模式加强,反对传入自定义依赖,缺点是多依赖传入时,代码浏览艰难,无奈反对大规模的模块化开发,无特定语法反对,代码简陋。
(function(global){
    var a =1;
    function getA(){return a;}
    function setA(a){window.__module.a = a;}
    global.__Module_API = {
        a,
        getA,
        setA
    }
})(window)

(function(global,moduleAPI){
    global.__Module = {setA:moduleApi.setA}
})(window,window.__Module_API)

以上就是晚期没有工具和标准的状况下对模块化的落地形式。

通过约定的形式去做模块化,不同的开发者和我的项目会有不同的差别,咱们就须要一个规范去标准模块化的实现形式。针对与模块加载的形式,以上办法都是通过 script 标签手动引入的形式,模块加载不受代码的管制,工夫久了保护会十分麻烦。那么就须要一个模块化的规范和主动加载模块的根底库。

CommonJS 标准

介绍

CommonJS 标准是 Node.js 默认模块标准,他规定了每个文件就是一个模块,每个模块有本人的作用域,它的模块加载采纳同步加载形式,加载模块的时候必须模块加载实现后再执行后续的代码。它通过 require 来加载模块,通过 exports 或 module.exports 输出模块。

特点

  • 所有代码都运行在模块作用域,不会净化全局作用域
  • 模块能够屡次加载,第一次加载时会运行模块,模块输入后果会被缓存,再次加载时,会从缓存后果中间接读取模块输入后果
  • 模块加载的程序是依照其在代码中呈现的程序
  • 模块输入的值是值得拷贝,相似 IIFE 计划中的外部变量

打包

CommonJS 要想在浏览器中应用的话须要应用 browserify 来打包。

browserify 打包原理次要是:通过自执行函数实现模块化,将每个模块编号,存入一个对象,每个模块标记依赖模块。它外部实现了 require 办法,外围是通过 call 办法调用模块,并传入 require、module、exports 办法,通过 module 存储模块信息,通过 exports 存储模块输入信息。

AMD 标准

AMD(Asynchronous Module Definition)标准采纳异步加载模块,容许指定回调函数。Node 模块通常都位于本地,加载速度快,所以实用于同步加载,然而在浏览器运行环境中,用同步加载会阻塞浏览器渲染,所以在浏览器环境中,模块须要申请获取,实用于异步加载。因而就诞生了 AMD 标准,用于异步加载,其中 require.js 就是 AMD 的一个具体实现库。目前不论是 CMD 或是 AMD 用的都很少,在 Node 开发中通常用 CommonJS 标准,在浏览器中用 ES Module 标准。

援用了 require.js 之后,它会全局定义一个 define 函数和 require 函数,所有的模块要用 define 去定义。define 有三个参数:第一个模块名,第二个是数组用于申明模块依赖项,第三个是一个函数,函数参数与第二个参数依赖项一一对应,每一个参数为依赖项导出的成员。函数的作用能够了解为以后模块提供公有的空间,如果要向外导出成员能够通过 return 来实现。

define('module1',['jquery','./module2'],function($,module2){return {}
});

而 require 函数则用来加载模块,当调用 require 函数的时候,其外部会主动创立 script 标签来发送脚本文件的申请,并执行绝对应的模块代码。

require(['./module1'],function (module1){module1.start();
})

目前绝大部分第三方库都反对 AMD 标准,然而因为要应用频繁应用 require、define 导致 AMD 应用起来绝对简单。另外如果模块代码划分的很粗疏,那么在同一个页面中,JS 文件的申请次数绝对多,导致页面效率低下。

CMD 标准

CMD 标准整合了 CommonJS 和 AMD 的长处,通过异步加载模块。CMD 专门用于浏览器端,其中淘宝的 sea.js 就是 CMD 标准的一个具体实现库。

//CMD 标准,相似 CommonJS 标准
define(function(require,exports,module){
    // 通过 require 引入依赖
    var $ = require('jquery')
    // 通过 exports 或者 module.exports 对外裸露成员
    module.exports = function(){}
})

ESModule 标准

目前模块化标准规范曾经十分成熟对立了,在 Node.js 中遵循 CommonJS 组织模块,在浏览器端应用 ESModule 标准。目前前端模块化根本对立为 CommonJS + ESModule 标准。

ESModule 标准是目前利用最为宽泛的模块化标准,它的设计理念是在编译时就确定模块依赖关系及输入输出。而 CommonJS 和 AMD 因为采纳闭包的模式必须在运行时能力确定依赖和输出、输入。ESM 通过 import 加载模块,通过 export 输出模块。

应用 ESModule,通过给 script 标签增加 type=module 属性,就能够以 ESModule 的规范执行 JS 代码。

<script type="module"></script>

ESModule 有如下几个个性:

  • ESM 主动采纳严格模式,疏忽 ’use strict’
  • 每个 ESM 都是运行在独自的公有作用域中
  • 在 ESM 中内部的 JS 模块是通过 CORS 的形式申请的
  • ESM 的 script 标签会提早执行脚本,等同于 defer 属性

留神

在 ESM 中 export {}它只是导出成员的语法,不是导出字面量对象,import {} from 'xxx',也是语法,不是解构对象,如果想导出对象能够应用 export default {},应用import xxx from 'xxx' 获取对象。其次通过 export {name} 导出的 name 值是援用的不是拷贝,它的值会受到导出模块外部批改的影响,而且 name 只是可读不能批改。

导入模块的用法

  1. 援用门路必须是残缺的文件名称不能省略 .js 扩展名,相对路径要是用./,能够应用绝对路径和 url 的形式。
import {name} from './module.js';
import {name} from '/xxx/module.js';
import {name} from 'http://localhost:3000/module.js';
  1. 加载模块,并不导出成员
import './xxx.js';
  1. 应用 * 的形式提取所有的模块,应用 as 寄存在对象中
import * as mod from './xxx.js';
  1. 应用 import 函数,在运行阶段动静导入模块
import('./xxx.js').then((module)=>{console.log(module);
});
  1. 同时导出命名成员和默认成员
//a.js
...
export {name,age}
export default 'title'

//b.js
import {name,age,default as title} from './a.js';
// 或者
import title,{name,age} from './a.js';

导出模块的用法

通过 export 将目录下散落的模块在 index 文件中导出,不便内部应用

export {name,age} from './module.js';

如果要应用 commonjs 标准的话就要将对应的 js 文件改为 .cjs 后缀名。

Node.js 新版本反对

在 Node.js 新版本中,在 package.json 中增加 type="module" 字段就能够应用 ESModule 标准了。

CommonJS 和 ESModule 标准比照

  • CommonJS 模块输入的是值得拷贝,ES6 模块输入的是值得引用
  • CommonJS 模块是运行时加载,ES6 模块是编译时输入接口
  • CommonJS 是单个值导出,ES6 Module 能够导出多个
  • CommonJS 是同步加载,ES6 Module 是反对异步加载,通过 import()来实现
  • CommonJS 的 this 是以后模块,ES6 Module 的 this 是 undefined
  • CommonJS 和 ES6 Module 的语法不同
  • CommonJS 是 Node.js 默认采纳的模块化标准,Node14 后默认反对 ESM。ESM 是浏览器默认采纳的模块化标准。

模块打包工具

模块化解决了咱们在简单利用当中的代码组织问题,然而随着引入模块化,又产生了新的问题。例如以下问题:

  • ES Modules 存在环境兼容问题
  • 通过模块化的形式划分的模块文件比拟多,且网络申请频繁
  • 所有的前端资源都须要模块化,不仅仅是 JS 须要模块化,像 html 和 css 都须要模块化

针对第一个问题,咱们须要一个工具将代码进行编译,在开发阶段将新个性的代码转换为兼容绝大多数环境的代码。

针对第二个问题,将模块化的文件打包到一块。

针对第三个问题,将其余资源通过代码的形式进行管制,对立去保护。

针对以上问题就须要模块打包工具来解决,例如 webpack、parcel 和 rollup。

通过应用 webpack 就能够将零散的代码打包到 js 文件中,对于那些有环境兼容问题的代码就能够通过模块加载器 loader 去做兼容转换,他还具备代码拆分的能力,能够按咱们的须要去打包,不必放心将所有的代码打包到一块,导致文件比拟大的问题。咱们能够将利用加载过程中首次运行所必须的模块打包到一起,其余的模块独自寄存,等到利用运行过程中须要某个模块,再异步加载这个模块,从而实现增量加载或者渐进式加载。

对于资源文件 webpack 反对在 JS 中以模块化的形式去载入任意类型的资源文件。打包工具解决的是前端整体的模块化,并不单指 JavaScript 模块化。

看完感觉对您有所帮忙别忘记关注呦

退出移动版