【js 知识】Commonjs 和 Es Module 的对比

前言

今天简单学习下模块化的知识,准备跳槽面试的同学可以看起来了,基础打牢,啥都不怕了

模块化

  • 模块化的意义是将程序划分成一个一个小的模块结构,每个模块有属于自己小世界,不会影响到其他的结构
  • 没有模块化可能造成全局污染,引入多个 js 文件,可能出现同样命名的方法
  • 没有模块化,依赖管理也是一个难以处理的问题

commonjs

  • 模块同步加载并执行模块文件,拷贝一份值出来
common 变量
  • module 当前模块信息
  • require 引入模块的方法
  • exports 导出模块的属性
require
模块导入规则
  • ./ 和 …/ 相对路径的文件模块, / 绝对路径的文件模块,会被当作文件模块处理,require() 方法会将路径转换成真实路径* 没有路径开头则视为导入一个包* fs、http、path 等标识符,为 nodejs 的核心模块,将被优先处理* 不是核心模块 会从当前文件的同级目录的 node_modules 寻找 -> package.json 下 main 属性指向的文件 -> index.js ,index.json ,index.node* 如果再没有,在父级目录的 node_modules 查找,一直递归到根目录下的 node_modules,直到找不到为止结束
缓存
  • 模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存* 每个模块内部,module 变量代表当前模块。这个变量是一个对象,它的 exports 属性(即 module.exports)是对外的接口。加载某个模块,其实是加载该模块的 module.exports 属性* 代码分析```
    function require(id) {const cachedModule = Module._cache[id];// 模块是否被加载过,加载过则取出来走缓存if (cachedModule) {return cachedModule.exports;}const module = { exports: {} ,loaded: false , …}// 对模块进行缓存Module._cache[id] = module// 这一步执行文件内容// 为 true 则加载完成,返回对象module.loaded = truereturn module.exports
    }

##### 循环引用

// a 文件
exports.a = 1;
const b = require(‘./b’);
console.log(b);
exports.a = 2;
// b 文件
exports.b = 3;
const a = require(‘./a’);
console.log(a);
exports.b = 4;
// app 文件
const a = require(‘./a’);
console.log(a);


* 代码流程分析1.执行 app.js 代码,获取 require('./a') -> 无缓存 ->加入缓存 -> 执行模块2.执行 require('./b') -> 无缓存 -> 加入缓存执行模块,进行 b -> require('./a') -> 有缓存,直接读取 -> 回到 b.js3.打印 { a: 1 } -> 执行 exports.b = 4,修改 b 等于 44.回到 a.js -> b 获取完毕,打印 { b: 4 } -> 执行 exports.a = 2,修改 a 为 25.回到 app.js -> 获取 a 完毕,输出 { a: 2 }

#### module.exports 和 exports

##### exports

* 正确写法

// a 模块
exports.name = ‘breeze’;
exports.age = ‘18’;
exports.fun = function () {console.log(‘bao’);
};

// app.js 文件引用
const a = require(‘./a’);
console.log(a); // { name: ‘breeze’, age: ‘18’, fun: [Function] }


* 错误写法:不能直接赋值

// 上文 a.js 修改成如下写法
exports = {name: ‘breeze’,age: ‘18’,fun: function () {console.log(‘bao’);},
};

// app.js 文件引用
const a = require(‘./a’);
console.log(a); // { }


* 错误原因:exports、module、require 都是通过形参的方式传入到模块中,如果直接 exports 新赋值一个对象的话,改变了引用类型的地址
* 模拟一下函数的流程

// js 代码中写的内容
const script = const a = require('./a');module.exports = function say() {return {a: a,};};;

// 通过参数的形式传入函数,所以不能改变引用对象的地址,否则只能输出为空
function wrapper(script) {return ‘(function (exports, require, module, __filename, __dirname) {’ + script + ‘\n})’;
}

// 执行包装函数
const modulefunction = wrapper(script);


##### module.exports

* 和 exports 是同一个引用地址* 支持的写法,可以放在一个对象里```
module.exports = {name: 'breeze',age: '18',fun: function () {console.log('bao');},
}; 
  • 不同的是 module.exports 可以直接赋值,不导出对象,以下情况生效
module.exports = '1';
module.exports = [1, 2, 3];
module.exports = function () {}; //导出方法 

Es Module

  • 提前加载并执行模块文件
  • 在预处理阶段分析依赖关系,在执行阶段执行模块(深度优先遍历,执行顺序先子后父)
  • ES6 模块不会缓存运行结果,而是动态地去被加载的模块取值,并且变量总是绑定其所在的模块
import、export
  • 引入和导出都是静态的,import 自动提升至顶层* import、export 不能放在块级作用域或条件语句中* 不可以在导入的文件中修改,可以在导出的文件中操作,可以理解为无论是否基本类型都是进行的引用传递
// a 文件
export let count = 1;
export addCount = () => {count++
}

// app 文件
import { count, addCount } from './a'
count = 2 // 报错,不可以修改
addCount() // 可以正常修改 
import()
  • import() 返回 Promise 对象
  • 可以进行动态使用
// a 文件
export name = 'breeze';
export default function fun () {console.log('bao')
}

// app 文件
setTimeout(() => {import('./a').then(res=>{console.log(res) // { name: 'breeze', default: f fun() }})res
}, 0); 
  • 经常使用于路由加载:react 路由懒加载
import React, { lazy, Suspense } from 'react';
import { Routes, Route } from 'react-router-dom';

const dynamicImport = (dirName: string) => {const Module = lazy(() => import(`../pages/${dirName}`)); // 动态加载return <Module />;
};

export const Router = () => {return (<Routes><Route path="/todo" element={dynamicImport('todo')} /></Routes>);
};

export default () => {return (<Suspense fallback={loading}><Router /></Suspense>);
}; 
tree shaking
  • tree shaking 原理就是利用的 es6 的 import、export,用来尽可能的删除没有被使用过的代码,不进行打包
// a 文件
export count = 1;
export const addCount = () => {count++
}
export const reduceCount = () => {count--
}

// app 文件
import { addCount } from './a'
addCount() 
  • 以上引用 reduceCount 将不被打包,因为并没有使用到

两者区别

  • CommonJs 的导出是拷贝一份变量出来,ES6 Module 导出的是变量是引用类型的,不能随意更改
  • CommonJs 是可以写在判断里的动态语法,ES6 Module 需要先进行静态分析
  • CommonJs 只能导出单个值,ES6 Module 导出的值数量不限制

最后,整理了一套《前端大厂面试宝典》,包含了HTML、CSS、JavaScript、HTTP、TCP协议、浏览器、VUE、React、数据结构和算法,一共201道面试题,并对每个问题作出了回答和解析。

有需要的小伙伴,可以点击文末卡片领取这份文档,无偿分享

部分文档展示:



文章篇幅有限,后面的内容就不一一展示了

有需要的小伙伴,可以点下方卡片免费领取

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值