前端模块化——CommonJS规范与ES6规范

发布于:2022-11-09 ⋅ 阅读:(10) ⋅ 点赞:(0) ⋅ 评论:(0)

编程领域中的模块化,就是遵守固定的规则,把一个大文件拆成独立并互相依赖的多个小模块。

把代码进行模块化拆分的好处:

  • 事实上模块化开发最终的目的是将程序划分成一个个小的结构
  • 这个结构中编写属于自己的逻辑代码,有自己的作用域,不会影响到其他的结构;
  • 这个结构可以将自己希望暴露的变量、函数、对象等导出给其结构使用
  • 也可以通过某种方式,导入另外结构中的变量、函数、对象等

上面说提到的结构,就是模块;按照这种结构划分开发程序的过程,就是模块化开发的过程。

模块化常见的两种:
CommonJS
每个文件都可以当作一个模块
在服务端:模块的加载时运行时同步加载的
在浏览器端:模块需要提前编译打包处理
在浏览器端编译打包需要用到 browserify。打包命令 browserify app.js -o build.js
ES6
依赖模块需要编译打包处理

一、CommonJS

 语法:
导入模块:require
导出模块:export

导出:

//在bar.js文件
// Node中每一个Js文件就是一个模块
const name = 'PengSir'
const age = 18
let message = 'My name is peng'

function sayHello(name) {
  console.log('hello' + name);
}

// exports 默认是空对象
// console.log(exports); // {}

// 1.导出
exports.name = name
exports.age = age
exports.sayHello = sayHello
exports.message = message

导入: 

//在main.js文件
// 导入:require 是一个函数
// const bar = require('./bar')
const {name,age,sayHello,message} = require('./bar')
console.log(name);// PengSir

在每个Node应用中都有一个 exports 对象,在其他文件导入某个文件时,其实就是拿到该对象的内存地址。bar对象是 exports 对象的浅拷贝(引用赋值)  

require的细节:

require的加载过程是同步的,意味着必须等到引入的文件(模块)加载完成之后,才会继续执行其他代码,也就是会造成阻塞(因为引入一个文件则该文件内部的所有代码都会被执行一次)。

require是一个函数,可以帮助我们引入一个文件(模块)中导出的对象。

require(X)如何查找:

情况一:X是一个核心模块,比如path、http :

        直接返回核心模块,然后停止查找。

情况二:X是以./..//(根目录)开头的:在对应的目录下查找,

        1.如果有后缀名,按照后缀名的格式查找对应的文件。

        2.没有后缀名,查找文件X -> 找X.js -> 找X.json -> 找X.node

        3.没有找到对应的文件,将X作为一个目录,查找X/index.js -> 找X/index.json -> 找X/index.node

情况三:直接是一个X(没有路径),并且X不是一个核心模块

        会逐级查找上一层目录下的node_modules

如果都没找到,那么报错 not found

二、ES6模块化详解

ES Module是ES6推出的。 即es 2015

ES Module和 Commonjs的模块化有一些不同之处:

1.它使用了 Import export 关键字 ,不是模块也不是函数。
2.它采用编译期的静态分析,并且也加入了动态引用的方式

ES Module模块采用 exportimport 关键字来实现模块化:

        export负责将模块内的内容导出
        import负责从其他模块导入内容

 语法:
导入模块:import
导出模块:export

1、分别暴露

//在module1.js文件
// 分别暴露模块
export function foo() {
    console.log('foo()');
}

export function bar() {
    console.log('bar()');
}

export let arr = [1, 2, 3, 4, 5]

2、统一暴露

//在module2.js文件
// 统一暴露
function fun() {
    console.log('fun()');
}

function fun2() {
    console.log('fun2()');
}

export { fun, fun2 }

3、默认暴露

//在module3文件
// 默认暴露
export default {
    msg: 'hello......',
    fun: () => {
        console.log('aaaaaaaaaaaaa');
    }
}

引入模块

import {foo, bar} from './module1.js'    // 如果是单个的js文件 引入时要加上后缀
import {fun, fun2} from './module2.js'
import module3 from './module3.js'    //默认暴露
import $ from 'jquery'      // 引入的是一个npm下载的包,就不需要加后缀
import express from 'express'

foo();
bar();
fun();
fun2();
console.log(module3.msg);;
console.log(module3.fun());

详解:

通常,我们把要导入的东西列在花括号 import {…} 中:

//在 main.js文件
import {sayHi, sayBye} from './say.js';

sayHi('John'); // Hello, John!
sayBye('John'); // Bye, John!

但是如果有很多要导入的内容,可以使用 import * as 将所有内容导入为一个对象,例如:

// 在main.js文件
import * as say from './say.js';

say.sayHi('John');
say.sayBye('John');

import * as写起来也很短,但是我们为什么要明确列出我们需要导入的内容?

比如说,我们向我们的项目里添加一个第三方库 say.js,它具有许多函数:
这里有几个原因。

1、现代的构建工具(webpack 和其他工具)将模块打包到一起并对其进行优化,以加快加载速度并删除未使用的代码。
比如说,我们向我们的项目里添加一个第三方库 say.js,它具有许多函数:

// 在say.js文件
export function sayHi() { ... }
export function sayBye() { ... }
export function becomeSilent() { ... }

现在,如果我们只在我们的项目里使用了 say.js 中的一个函数:

// 在main.js文件
import {sayHi} from './say.js';

那么,优化器(optimizer)就会检测到它,并从打包好的代码中删除那些未被使用的函数,从而使构建更小。

2、明确列出要导入的内容会使得名称较短:sayHi() 而不是 say.sayHi()。

3、导入的显式列表可以更好地概述代码结构:使用的内容和位置。它使得代码支持重构,并且重构起来更容易。

不用花括号的导入看起来很方便。刚开始使用模块时,一个常见的错误就是忘记写花括号。所以,import 命名的导出时需要花括号,而 import 默认的导出时不需要花括号

import 函数

通过 Import加载的模块,是不可以在其放到逻辑代码中的,比如:

let flag = true
if (flag) {
  // 错误用法,语法错误,不能在逻辑在逻辑代码中使用 import 关键字
  import format from './modules/foo.js'
}

如何解决?使用 import() 函数 或者 require()

// 注意:上边使用import时是作为关键字使用,现在是作为函数使用,
// 该函数为异步函数,返回值为promise
let flag = true
if (flag) {
  import('./modules/foo.js').then(res => {
    console.log('then里边的回调');
    console.log(res); 
  }, err => {
    console.log(err);
  })
}

设置工具库的统一出口:

在项目中经常有很多的工具函数,分散在不同的文件中,如果要引入的话,需要找到对应的文件来引入,每次都去找对应的文件比较麻烦,可以搞一个统一的出口,要用的话,直接导入这个出口文件就可以了。

/**
 * 工具的统一出口
 */

// 1.导出方式一:挨个导入再挨个导出
 import { sub, add } from './math.js'
import { timeFormat } from './format.js'

export { sub, add, timeFormat }


// 2.导出方式二:直接导出指定的
export { sub, add } from './math.js'
export { timeFormat } from './format.js'


// 3.导出方式三: 直接导出所有的
export * from './math.js'
export * from './format.js'