模块化的背景

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

什么是前端模块化?

前端模块化就是将简单程序依据标准拆分成若干模块,一个模块包含输出和输入。而且模块的外部实现是公有的,它通过对外裸露接口与其余模块通信,而不是间接调用。当初在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.jsimport {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模块化。

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