【青训营】- 说说前端的模块化机制

546 阅读4分钟

前言

什么是模块化?

根据功能或业务将一个大程序拆分成互相依赖的小文件,再用简单的方式拼装起来;

一个模块(module)就是一个文件。一个脚本就是一个模块


为什么要模块化?

如果没有模块化,则所有script标签必须保证顺序正确,否则会依赖报错,全局变量存在命名冲突,占用内存无法被回收,IIFE/namespace会导致代码可读性低等诸多问题。



CommonJS规范

导出模块写法

//方式1✔️
module.exports = {
    a: 'Hi',
    b: 'Axjy'
}

//方式2✔️
module.exports.a = 'Axjy';

//方式3✔️
exports.a = 'Axjy';

//错误写法❌
exports = {
    a: 'Hi',
    b: 'Axjy'
}

为什么最后一种写法是错的?

因为Node为每个模块提供一个exports变量,指向module.exports。这等同在每个模块头部,有一行这样的命令。

image-20210830221734316

造成的结果是,在对外输出模块接口时,可以向exports对象添加方法。

image-20210830221930232

但是不能直接将exports变量指向一个值,因为这样等于切断了exportsmodule.exports的联系,导致exports不再指向module.exports



加载方式:

  • 加载内置模块require('fs')【Node.js自带的模块】
  • 加载(相对/绝对)路径的文件模块
    • require('/User/../file.js')
    • require('./file.js')
  • 加载npm包require('lodash')【即通过npm i lodash下载到node_modules里的模块】

查找原则

内置模块:直接跳过路径分析和文件定位

路径模块:直接得出相对路径

npm包查找原则:

  • 当前目录node_ modules

  • 如果没有,父级目录的node_ modules

  • 如果没有,沿着路径向上递归,直到根目录下node_ modules

  • 找到之后会加载package.json main指向的文件,如果没有package.json则依次查找index.js、index.json、index. node

image-20210829130710096


  • require()node中的全局方法,所以不能直接在html里面用;

  • require不是CommonJS独有,CommonJS只是众多规范中的其中一种

  • require()的参数可以是变量或做字符串拼接

image-20210830220709062


CommonJS规范的特点

  • 所有代码都运行在模块作用域,不会污染全局作用域。

  • 模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。

  • 模块加载的顺序,按照其在代码中出现的顺序。


require.cache中缓存着加载过的模块,缓存的原因:同步加载

  • 文件模块查找耗时,如果每次require都需要重新遍历查找,性能会比较差;
  • 在实际开发中,模块可能包含副作用代码

更多模块加载规范



ES Modules (ESM)

ES Modules (ESM),语言层面的模块化规范,与环境无关,可借助babel编译

  • ESM是在ES6语言层面提出的一种模块化标准;
  • ESM中主要有import、export 两个关键词,不能console打印两个关键词

在声明前导出

以下的导出均有效

// 导出数组
export let months = ['Jan', 'Feb', 'Mar','Apr', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];

// 导出 const 声明的变量
export const MODULES_BECAME_STANDARD_YEAR = 2015;

// 导出类
export class User {
  constructor(name) {
    this.name = name;
  }
}

//导出函数
export function sayHi(user) {
  alert(`Hello, ${user}!`);
}  

导出和声明分开

export {sayHi, sayBye}; // 导出变量列表

image-20210830223424177


Export “as”

可以使用 as 让导出具有不同的名字。

export {sayHi as hi, sayBye as bye};

image-20210830224054164


import导入

写法

import {sayHi, sayBye} from './say.js';

import * as say from './say.js';

示例

我们可以把要导入的东西列在花括号 import {...}

image-20210830223527949

如果有很多要导入的内容,我们可以使用 import * as <obj> 将所有内容导入为一个对象

image-20210830223627353

推荐使用方法一,明确列出需要导入的内容


Import “as”

可以使用 as 让导入具有不同的名字。

image-20210830223935101


Export default

模块提供了一个特殊的默认导出 export default 语法,以使“一个模块只做一件事”

export default 放在要导出的实体前:

image-20210830224325693

这种方式的导出,它的导入不需要花括号


  • 可以在一个模块中同时有默认的导出和命名的导出,但是实际上通常不会混合使用它们。

  • 模块要么是命名的导出要么是默认的导出。

  • 由于每个文件最多只能有一个默认的导出,因此导出的实体可能没有名称。

  • import 命名的导出时需要花括号,

  • import 默认的导出时不需要花括号。

image-20210830224448935


如果没有default下面这些就会报错

image-20210830224856814

CommonJS VS ESM

image-20210830215116529

*可以混用,但是不建议( import commonjs || import中require)

image-20210830222538715

二者区别


其他模块规范

AMD是RequireJS在推广过程中规范化产出,异步加载,推崇依赖前置;

CMD是SeaJS在推广过程中规范化产出,异步加载,推崇就近依赖;

UMD (Universal Module Definition)规范,兼容AMD和CommonJS模式

image-20210829130916891

image-20210829130958667


结语

参考:

JavaScript标准参考教程

《The Modern JavaScript Tutorial》

如以上有错误的地方,请在评论区中指出!


如果有收获的话,就留个鼓励一下吧!🍜