likes
comments
collection
share

CommonJs和ESModule对比

作者站长头像
站长
· 阅读数 19

CommonJS

  • Node 应用由模块组成,采用 CommonJS 模块规范。

  • 在CommonJs规范中,如果想在多个文件分享变量,必须定义为global对象的属性(不推荐)

global.warning = true;
  • CommonJS规范规定,每个模块内部,module变量代表当前模块。这个变量是一个对象,它的exports属性(即module.exports)是对外的接口。加载某个模块,其实是加载该模块的module.exports属性

  • Node内部提供一个Module构建函数。所有模块都是Module的实例。

  • 每个模块内部,都有一个module对象,代表当前模块。它有以下属性。

    • module.id 模块的识别符,通常是带有绝对路径的模块文件名。
    • module.filename 模块的文件名,带有绝对路径。
    • module.loaded 返回一个布尔值,表示模块是否已经完成加载。
    • module.parent 返回一个对象,表示调用该模块的模块。
    • module.children 返回一个数组,表示该模块要用到的其他模块。
    • module.exports 表示模块对外输出的值。
  • require命令的基本功能是,读入并执行一个JavaScript文件,然后返回该模块的exports对象。如果没有发现指定模块,会报错。

  • 如果想得到require命令加载的确切文件名,使用require.resolve()方法。

  • 第一次加载某个模块时,Node会缓存该模块。以后再加载该模块,就直接从缓存取出该模块的module.exports属性。

  • 所有缓存的模块保存在require.cache之中,如果想删除模块的缓存,可以像下面这样写。

// 删除指定模块的缓存
delete require.cache[moduleName];

// 删除所有模块的缓存
Object.keys(require.cache).forEach(function(key) {
  delete require.cache[key];
})
// 缓存是根据绝对路径识别模块的,如果同样的模块名,但是保存在不同的路径,require命令还是会重新加载该模块。
  • 如果发生模块的循环加载,即A加载B,B又加载A,则B将加载A的不完整版本。

  • require方法有一个main属性,可以用来判断模块是直接执行,还是被调用执行。

  • CommonJS模块的加载机制是,输入的是被输出的值的拷贝。也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。请看下面这个例子。

  • require命令是CommonJS规范之中,用来加载其他模块的命令。它其实不是一个全局命令,而是指向当前模块的module.require命令,而后者又调用Node的内部命令Module._load

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

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

ESModule

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

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

    • 借助 Es Module 的静态导入导出的优势,实现了 tree shaking
    • Es Module 还可以 import() 懒加载方式实现代码分割。
  • ES6 module支持混合导出, 可以使用 export default 和 export 导入多个属性。

重属名导入

import {  name as bookName , say,  author as bookAuthor  } from 'module'

重定向导出

可以把当前模块作为一个中转站,一方面引入 module 内的属性,然后把属性再给导出去。

export * from 'module' // 第一种方式
export { name, author, ..., say } from 'module' // 第二种方式
export {   name as bookName ,  author as bookAuthor , ..., say } from 'module' //第三种方式

无需导入模块,只运行模块

import 'module' 

动态导入 - 为了支持这种方式,需要在 webpack 中做相应的配置处理。

const promise = import('module')
promise.then((res) => {
	...
})

静态语法

  • ES6 module 的引入和导出是静态的,import 会自动提升到代码的顶层 ,import , export 不能放在块级作用域或条件语句中。

  • 下面是对 import 属性作出总结:

    • 使用 import 被导入的模块运行在严格模式下。
    • 使用 import 被导入的变量是只读的,可以理解默认为 const 装饰,无法被赋值
    • 使用 import 被导入的变量是与原变量绑定/引用的,可以理解为 import 导入的变量无论是否为基本类型都是引用传递。

import() 可以做什么

动态加载

  • 首先 import() 动态加载一些内容,可以放在条件语句或者函数执行上下文中。
if(isRequire){
    const result  = import('./b')
}

懒加载

  • import() 可以实现懒加载,举个例子 vue 中的路由懒加载;
[
   {
        path: 'home',
        name: '首页',
        component: ()=> import('./home') ,
   },
]

React中动态加载

const LazyComponent =  React.lazy(()=>import('./text'))
class index extends React.Component{   
    render(){
        return <React.Suspense fallback={ <div className="icon"><SyncOutlinespin/></div> } >
               <LazyComponent />
           </React.Suspense>
    }
  • React.lazySuspense 配合一起用,能够有动态加载组件的效果。React.lazy 接受一个函数,这个函数需要动态调用 import()

  • import() 这种加载效果,可以很轻松的实现代码分割。避免一次性加载大量 js 文件,造成首次加载白屏时间过长的情况。

tree shaking 实现

  • Tree Shaking 在 Webpack 中的实现,是用来尽可能的删除没有被使用过的代码,一些被 import 了但其实没有被使用的代码。比如以下场景:

Commonjs 和 Es Module 总结

Commonjs 总结

  • CommonJS 模块由 JS 运行时实现。
  • CommonJs 是单个值导出,本质上导出的就是 exports 属性。
  • CommonJS 是可以动态加载的,对每一个加载都存在缓存,可以有效的解决循环引用问题。
  • CommonJS 模块同步加载并执行模块文件。

es module 总结

  • ES6 Module 静态的,不能放在块级作用域内,代码发生在编译时。
  • ES6 Module 的值是动态绑定的,可以通过导出方法修改,可以直接访问修改结果。
  • ES6 Module 可以导出多个属性和方法,可以单个导入导出,混合导入导出。
  • ES6 模块提前加载并执行模块文件。
  • ES6 Module 导入模块在严格模式下。
  • ES6 Module 的特性可以很容易实现 Tree Shaking 和 Code Splitting。

PS:如果有需要补充的内容,请在评论区留言