ESM和CJS区别

2021-10-13  本文已影响0人  乐宝呗

ES6 Module和CommonJS区别

区别一

前者属于编译时加载,即静态加载,在编译时就能够确定模块的依赖关系,以及输入和输出的变量;后者属于运行时加载,都只有在代码运行时才能确定这些东西。ESM形式的好处是可以做到tree shaking。

区别二

前者可以加载模块的部分内容,后者需要加载模块整个对象,再取到内容。

区别一和二可以用tree shaking来解释一下是大概是怎样一个流程。

按照webpack官网的解释,tree shaking通常用于描述移除JavaScript上下文中的未引用代码(dead-code)。它依赖于ESM的静态分析能力,例如import和export。用大白话解释就是,如果是使用模块化开发的话,就可以删除那些引入某个模块中用不到的函数。

为什么叫tree shaking, 我个人觉得这个名次叫的很形象呀。你可以将应用程序想象成一棵树。绿色表示实际用到的源码和 library,是树上活的树叶。灰色表示无用的代码,是秋天树上枯萎的树叶。为了除去死去的树叶,你必须摇动这棵树,使它们落下。

1.index.js的作用是 创建一个DOM元素,然后引入math里面的cube函数来使用,只用了cube函数。

import { cube } from './math'

function component() {

var element = document.createElement('div')

    element.innerHTML = [

'hello webpack!',

'5 cubed is equal to' + {cube(5)

    ].join('\n\n')

return element

}

document.body.appendChild(component())

而在math.js里,export了两个工具函数。实际上我们要看的就是,只有cube在index里面使用了,但是看下square在不同的模块系统下是否被引入。

export function square (x) {

return x * x

}

export function cube (x) {

return x * x * x

}

 最后我们运行npm run build后,会在dist目录下生成app.bunble.js到文件,这个文件是被压缩过的,我这边格式化下代码。

根据结果我们看到,index.js里面只使用了cube函数,在最终的结果bundle.js中,也只把math中的cube函数引入了进来,而没有处理square函数。因为它没有在实际过程中使用到。这就通过ESM的import和export的方式实现了tree shaking。也就是我们在区别一、二中说到的,ESM可以在编译时静态分析模块之间的依赖,并且只加载模块中被使用的内容。

2.ok看到这里,我们已经实现了tree shaking。但是我们要琢磨下,不同的import和export方式或者使用CommonJS的情况下,能否实现tree shaking。

我们把index.js里面的引入方式改成如下

import * as math from './math'

// 使用的方式改成math.cube(5)

math.js的export方式不变,但是我们改变了index.js的引入方式。在运行下npm run build后,我们发现bundle.js也只是输出了cube函数,square函数就被tree shaking掉了。实际上跟上面那种的结果是一样的。

改变index.js中引入math的方式

3. 按照ESM的语法规定,我们可以混用export和export default,那么我们在math.js再加上export default ,index.js引入math的方式可以使用两种。

// math.js

export ..

export ..

export default {

square,

cube

}

// index.js

import { cube } from './math'

// 或者 import math from './math'

运行npm run build后,格式化bunble.js文件,我们会发现当我们只引入cube函数时,可以做到tree shaking,但是当我们直接import math来后,就把没有用到的square函数也引入了。

import { cube } from './math'的方式

import math from './math'的方式

仔细想想,这也就说明了ESM要实现加载模块的部分内容,export某个模块是ok的,但是export default整个模块出来,就无法做tree shaking了。

4. 最后看看CommonJS的结果是怎样的。我们知道,CommonJS属于运行时加载,它会在代码运行到那一行时将整个模块加载进来。我们将index.js的引入方式改成如下,而math还是按照原始状态,单独export 两个 工具函数。

// index.js

const { cube } = require('./math')

// 写法二:const math = require('./math')

// math.js 

// 注:webpack支持ESM和CommonJS模块的相互转换

export function square (x) {

    return x * x

}

export function cube (x) {

    return x * x * x

}

运行npm run build后,我们发现不管index.js里面的写法有什么不同,只要是CommonJS的语法,他们的结果都是一样的,即无法实现tree shaking,将无关的square函数引入到bunble.js中。

index.js中两种写法都是一样的结果

ok,以上三种引用方式,进一步说明了ESM和CommonJS的区别。即区别一二指出的

另外,实现tree shaking的好处就不言而喻了,可以极大减少build后js的大小。

tree shaking的实现依赖于ESM的静态分析能力,import和export可以实现tree shaking,但是直接export default 整个对象或者使用CommonJS的语法是无法实现的。

区别三

前者输出的是值的引用,后者输出的是值的拷贝。这个其实在阮一峰老师关于Module的加载实现里面有谈过这个。这边我觉得这种模块之间的引入,一般很少会说去改变模块引入进来的内容。所以具体的例子可以参考阮一峰老师的。传送门:Module 的加载实现

区别四

由于前者属于编译时加载,无法像后者一般,做到运行时加载。所以,有一个提案,引入import()函数,完成运行时加载,或者叫动态加载。import()和require()相同点都是运行时加载;区别在于,import()属于异步加载,require()属于同步加载。

运行时加载的好处:按需加载;条件加载;动态的模块路径。

什么叫运行时加载呢?就是说 你可以在一些比如条件判断语句里引入模块。而编译时加载就不行。如果使用import或者export时会报错,会告诉你这两个关键字的使用必须是在顶层的作用域才行。

if(type===1){

    constmath=require('./math')

}// 报错,错误类似于// 'import' and 'export' may only appear at the top levelif(type===1){importmathfrom'./math'}

为了解决ESM中无法实现运行时加载的问题,ESM提出了import()函数的概念,来完成运行时加载。而且从例子中,我们也可以明显的看出,import函数是异步加载的,而CommonJS是同步加载的。

if(type===1){

    import('./math.js').then(res=>{

        // do something

    }).catch(err=>{

        // oops!

    })

 }

if(type===1){

    const math=require('./math')

    // do something

}

文章摘抄自 https://zhuanlan.zhihu.com/p/71098263

上一篇下一篇

猜你喜欢

热点阅读