Commonjs 和 Es Module

模块化

早期 JavaScript 开发很容易存在 全局污染依赖管理混乱 问题。

<body>
  <script src="./js1.js"></script>
  <script src="./js2.js"></script>
  <script src="./js3.js"></script>
</body>

全局污染 - 每个加载的 js 文件都共享变量。 依赖管理混乱 - 正常情况下,执行 js 的先后顺序就是 script 标签排列的前后顺序。假设三个 js 中,都有一个公共方法,下层 js 能调用上层 js 的方法,但是上层 js 无法调用下层 js 的方法

Commonjs

Commonjs 的提出,弥补 Javascript 对于模块化,没有统一标准的缺陷。nodejs 借鉴了 Commonjs 的 Module ,实现了良好的模块化管理

目前 commonjs 广泛应用于以下几个场景:

  • Node 是 CommonJS 在服务器端一个具有代表性的实现;
  • Browserify 是 CommonJS 在浏览器中的一种实现;
  • webpack 打包工具对 CommonJS 的支持和转换;也就是前端应用也可以在编译之前,尽情使用 CommonJS 进行开发。

特点:

  • 在 commonjs 中每一个 js 文件都是一个单独的模块,我们可以称之为 module;
  • 该模块中,包含 CommonJS 规范的核心变量: exports、module.exports、require;
  • exports 和 module.exports 可以负责对模块中的内容进行导出;
  • require 函数可以帮助我们导入其他模块(自定义模块、系统模块、第三方库模块)中的内容;

exports VS module.exports: exports 会被初始化成一个对象,也就是我们只能在对象上绑定属性,但是我们可以通过 module.exports 自定义导出出对象外的其他类型元素,例如只导出一个类或者一个函数。

module.exports 当导出一些函数等非对象属性的时候,也有一些风险,就比如循环引用的情况下。对象会保留相同的内存地址,就算一些属性是后绑定的,也能间接通过异步形式访问到。但是如果 module.exports 为一个非对象其他属性类型,在循环引用的时候,就容易造成属性丢失的情况发生了

Es Module

Nodejs 借鉴了 Commonjs 实现了模块化 ,从 ES6 开始, JavaScript 才真正意义上有自己的模块化规范。

Es Module 的产生有很多优势,比如:

  • 借助 Es Module 的静态导入导出的优势,实现了 tree shaking。
  • Es Module 还可以 import() 懒加载方式实现代码分割
// 导出 export
const name = '《React进阶实践指南》'
const author = '我不是外星人'
export { name, author }
export const say = function (){
    console.log('hello , world')
}
// 导入 import:
import { name , author , say } from './a.js'
// 默认导出 export default:
const name = '《React进阶实践指南》'
const author = '我不是外星人'
const say = function (){
    console.log('hello , world')
}
export default {
    name,
    author,
    say
}
// 导入模块(对于引入默认导出的模块,import anyName from 'module', anyName 可以是自定义名称。)
import mes from './a.js'
console.log(mes) //{ name: '《React进阶实践指南》',author:'我不是外星人', say:Function }
// 混合导入|导出
export const name = '《React进阶实践指南》'
export const author = '我不是外星人'

export default  function say (){
    console.log('hello , world')
}
// 导入方式一
import theSay , { name, author as  bookAuthor } from './a.js'
console.log(
    theSay,     // ƒ say() {console.log('hello , world') }
    name,       // "《React进阶实践指南》"
    bookAuthor  // "我不是外星人"
)
// 导入方式二
import theSay, * as mes from './a'
console.log(
    theSay, // ƒ say() { console.log('hello , world') }
    mes // { name:'《React进阶实践指南》' , author: "我不是外星人" ,default:  ƒ say() { console.log('hello , world') } }
)
// 重属名导入(从 module 模块中引入 name 重命名为 bookName ,从 module 模块中引入 author 重命名为 bookAuthor。)
import { bookName as name, say, bookAuthor as author } from 'module'
console.log( bookName , bookAuthor , say ) //《React进阶实践指南》 我不是外星人
// 重定向导出(可以把当前模块作为一个中转站,一方面引入 module 内的属性,然后把属性再给导出去)
export * from 'module' // 第一种方式(重定向导出 module 中的所有导出属性, 但是不包括 module 内的 default 属性)
export { name, author, ..., say } from 'module' // 第二种方式
export { bookName as name, bookAuthor as author, ..., say } from 'module' //第三种方式

ES6 module 一些重要特性: 1、静态语法:ES6 module 的引入和导出是静态的,import 会自动提升到代码的顶层 ,import , export 不能放在块级作用域或条件语句中。import 的导入名不能为字符串或在判断语句。

// 错误写法一
function say(){
  import name from './a.js'
  export const author = '我不是外星人'
}
// 错误写法二
isexport &&  export const  name = '《React进阶实践指南》'

import() 动态引入

setTimeout(() => {
    const result  = import('./b')
    result.then(res=>{
        console.log(res)
    })
}, 0);
// 动态加载,可以放在条件语句或者函数执行上下文中
if(isRequire){
    const result  = import('./b')
}
// 懒加载,例如 vue 中的路由懒加载
[
   {
        path: 'home',
        name: '首页',
        component: ()=> import('./home') ,
   },
]

更多详情