前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >模块打包中CommonJS与ES6 Module的导入与导出问题详解

模块打包中CommonJS与ES6 Module的导入与导出问题详解

作者头像
砖业洋__
发布2023-05-06 20:17:35
7010
发布2023-05-06 20:17:35
举报
文章被收录于专栏:博客迁移同步博客迁移同步

CommonJS

CommonJS模块

CommonJS中规定每个文件是一个模块。每个模块是拥有各自的作用域的,各自作用域的变量互不影响。

代码语言:javascript
复制
// calculator.js
var name = 'calculator.js';

// index.js
var name = 'index.js';
require('./calculator.js');
console.log(name); // index.js

这里可以看到,导入calculator.js并不会覆盖index.js中的name字段 这样做区别于直接用<script>标签插入页面中的好处在于 插入<script>标签后顶层作用域是全局作用域,在进行变量及函数声明时会污染全局环境;而封装成CommonJS模块会形成一个属于模块自身的作用域,所有的变量及函数只有自己能访问,对外是不可见的。

CommonJS模块导出

下面两种写法实质上是一样的

代码语言:javascript
复制
module.exports = {
    name: 'calculater',
    add: function(a, b) {
        return a + b;
    }
};

等同于

代码语言:javascript
复制
exports.name = 'calculater';
exports.add = function(a, b) {
    return a + b;
};

其内在机制是将exports指向了module.exports,而module.exports在初始化时是一个空对象。我们可以简单地理解为,CommonJS在每个模块的首部默认添加了以下代码:

代码语言:javascript
复制
var module = {
    exports: {},
};
var exports = module.exports;

因此,为exports.add赋值相当于在module.exports对象上添加了一个add属性。 注意点一:不要直接给exports赋值,否则会导致其失效。 如:

代码语言:javascript
复制
exports = {
    name: 'calculater'
};

上面代码中,由于对exports进行了赋值操作,使其指向了新的对象{name: 'calculater'}module.exports却仍然是原来的空对象,因此name属性并不会被导出。

注意点二:不要把module.exportsexports混用。

代码语言:javascript
复制
exports.add = function(a, b) {
    return a + b;
};
module.exports = {
    name: 'calculater'
};

上面的代码先通过exports导出了add属性,相当于module.exports = { add: function(){...}}然后将module.exports重新赋值为另外一个对象。这会导致原本拥有add属性的对象丢失了,最后导出的只有name

注意点三:导出语句不代表模块的末尾

代码语言:javascript
复制
module.exports = {
    name: 'lcylcy'
};
console.log('end');

module.exportsexports后面的代码依旧会照常执行。比如上面的console会在控制台上打出“end”,但在实际使用中,为了提高可读性,不建议采取上面的写法,而是应该将module.exportsexports语句放在模块的末尾。

CommonJS模块导入

CommonJS中使用require进行模块导入。如:

代码语言:javascript
复制
// calculator.js
module.exports = {
    add: function(a, b) {return a + b;}
};
// index.js
const calculator = require('./calculator.js');
const sum = calculator.add(2, 3);
console.log(sum); // 5

我们在index.js中导入了calculator模块,并调用了它的add函数。当我们require一个模块时会有两种情况:

1.require的模块是第一次被加载。这时会首先执行该模块,然后导出内容。 2.require的模块曾被加载过。这时该模块的代码不会再次执行,而是直接导出上次执行后得到的结果。

请看下面的例子:

代码语言:javascript
复制
// calculator.js
console.log('running calculator.js');
module.exports = {
    name: 'calculator',
    add: function(a, b) {
        return a + b;
    }
};

// index.js
const add = require('./calculator.js').add;
const sum = add(2, 3);
console.log('sum:', sum);
const moduleName = require('./calculator.js').name;
console.log('end');

控制台的输出结果如下:

代码语言:javascript
复制
running calculator.js
sum: 5
end

从结果可以看到,两次requirecalculator.js,但console.log('running calculator.js');只执行了一遍。模块会有一个module对象用来存放其信息,这个对象中有一个属性loaded用于记录该模块是否被加载过。它的值默认为false,当模块第一次被加载和执行过后会置为true,后面再次加载时检查到module.loadedtrue,则不会再次执行模块代码。有时我们加载一个模块,不需要获取其导出的内容,只是想要通过执行它而产生某种作用,比如把它的接口挂在全局对象上,此时直接使用require即可。

代码语言:javascript
复制
require('./test.js');

另外,require函数可以接收表达式,借助这个特性我们可以动态地指定模块加载路径。

代码语言:javascript
复制
const moduleNames = ['foo.js', 'bar.js'];
moduleNames.forEach(name => {
    require('./' + name);
});

ES6 Module

ES6 模块

ES6 Module也是将每个文件作为一个模块,每个模块拥有自身的作用域,不同的是导入、导出语句。importexport也作为保留关键字在ES6版本中加入了进来(CommonJS中的module并不属于关键字)。

请看下面的例子,我们将前面的calculator.jsindex.js使用ES6的方式进行了改写。

代码语言:javascript
复制
// calculator.js
export default {
    name: 'calculator',
    add: function(a, b) {
        return a + b;
    }
};

// index.js
import calculator from './calculator.js';
const sum = calculator.add(2, 3);
console.log(sum); // 5

ES6 Module会自动采用严格模式,这在ES5ECMAScript 5.0)中是一个可选项。以前我们可以通过选择是否在文件开始时加上“use strict”来控制严格模式,在ES6 Module中不管开头是否有“use strict”,都会采用严格模式。如果将原本是CommonJS的模块或任何未开启严格模式的代码改写为ES6 Module要注意这点。

ES6 Module导出

ES6 Module中使用export命令来导出模块。 export有两种形式:

1.命名导出 2.默认导出

命名导出

一个模块可以有多个命名导出。它有两种不同的写法:

代码语言:javascript
复制
// 写法1
export const name = 'calculator';
export const add = function(a, b) { return a + b; };

// 写法2
const name = 'calculator';
const add = function(a, b) { return a + b; };
export { name, add };

第1种写法是将变量的声明和导出写在一行; 第2种则是先进行变量声明,然后再用同一个export语句导出。 两种写法的效果是一样的。

在使用命名导出时,可以通过as关键字对变量重命名。如:

代码语言:javascript
复制
const name = 'calculator';
const add = function(a, b) { return a + b; };
export { name, add as getSum }; // 在导入时即为 name 和 getSum

默认导出

与命名导出不同,模块的默认导出只能有一个。如:

代码语言:javascript
复制
export default {
    name: 'calculator',
    add: function(a, b) {
        return a + b;
    }
};

我们可以将export default理解为对外输出了一个名为default的变量,因此不需要像命名导出一样进行变量声明,直接导出值即可。

代码语言:javascript
复制
// 导出字符串
export default 'This is calculator.js';
// 导出 class
export default class {...}
// 导出匿名函数
export default function() {...}

ES6 Module导入

ES6 Module中使用import语法导入模块。首先我们来看如何加载带有命名导出的模块,请看下面的例子:

命名导入

代码语言:javascript
复制
// calculator.js
const name = 'calculator';
const add = function(a, b) { return a + b; };
export { name, add };

// index.js
import { name, add } from './calculator.js';
add(2, 3);

加载带有命名导出的模块时,那就要对应命名导入。import后面要跟{ }来将导入的变量名包裹起来,并且这些变量名需要与导出的变量名完全一致。

导入变量的效果相当于在当前作用域下声明了这些变量(nameadd),并且不可对其进行更改,也就是所有导入的变量都是只读的。

与命名导出类似,我们可以通过as关键字可以对导入的变量重命名。如:

代码语言:javascript
复制
import { name, add as calculateSum } from './calculator.js';
calculateSum(2, 3);

在导入多个变量时,我们还可以采用整体导入的方式。如:

代码语言:javascript
复制
import * as calculator from './calculator.js';
console.log(calculator.add(2, 3));
console.log(calculator.name);

使用import * as <myModule>可以把所有导入的变量作为属性值添加到<myModule>对象中,从而减少了对当前作用域的影响。

默认导入

代码语言:javascript
复制
// calculator.js
export default {
    name: 'calculator',
    add: function(a, b) { return a + b; }
};

// index.js
import myCalculator from './calculator.js';
calculator.add(2, 3);

对于默认导出来说,那就要默认导入,import后面直接跟变量名,并且这个名字可以自由指定(比如这里是myCalculator),它指代了calculator.js中默认导出的值。 从原理上可以这样去理解:

代码语言:javascript
复制
import { default as myCalculator } from './calculator.js';

混合导入

代码语言:javascript
复制
// index.js
import React, { Component } from 'react';

这里的React对应的是该模块的默认导出,而Component则是其命名导出中的一个变量。

注意:这里的React必须写在大括号前面,而不能顺序颠倒,否则会提示语法错误。

复合写法

复合写法在工程中,有时需要把某一个模块导入之后立即导出,比如专门用来集合所有页面或组件的入口文件。此时可以采用复合形式的写法:

代码语言:javascript
复制
export { name, add } from './calculator.js';

等同于

代码语言:javascript
复制
import { name, add } from './calculator.js';
export { name, add };

复合写法目前只支持当被导入模块(这里的calculator.js)通过命名导出的方式暴露出来的变量,默认导出则没有对应的复合形式,只能将导入和导出拆开写。

代码语言:javascript
复制
import calculator from "./calculator.js ";
export default calculator;

不能写成export default from './calculator.js' 除非写为

代码语言:javascript
复制
export { default } from calculator;

但是这种方式依然还是命名导出而不是默认导出,命名的变量为default而已。 参考资料:

Webpack实战:入门、进阶与调优 扩展阅读: 阮一峰:ECMAScript 6 入门----Module 的语法

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2023-05-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • CommonJS
    • CommonJS模块
      • CommonJS模块导出
        • CommonJS模块导入
        • ES6 Module
          • ES6 模块
            • ES6 Module导出
              • 命名导出
              • 默认导出
            • ES6 Module导入
              • 命名导入
              • 默认导入
              • 混合导入
            • 复合写法
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档