Webpack 知识补充之模块

2020-01-13

CommonJs

这块标准是在 2009 年提出来的,包含模块、IO、文件等。经过 Node.js 采用并做调整,所以说起 CommonJS 通常是 Node.js 中的版本了。在使用 Node.js 包管理器的年代,CommonJs 成为一颗有流量的明星了。

自身作用域

CommonJs 的模块天然有自身的作用域,所有变量和函数声明只能自己访问,外人想都别想,这个保护机制太 nice 了。

// order.js
const name = '订单系统';
// index.js
const name = '首页';
require('./order.js');
console.log(`------\n${name}`);

导出机制

模块对外暴露的方式。对于要暴露的内容可使用 module.exports来导出,其导出内容格式是一个对象。也可使用简化形式 exports

// module.exports.js
module.exports = {
  name: '订单系统',
  total: (a, b) => {
    return (a * b);
  }
}
// exports.js
exports.name = '订单系统';
exports.total = (a, b) => {
  return (a * b);
}

上面两种所要表达的功能是一样的,内在逻辑是 exports指向 module.exportsmodule.exports是初始化时建的一个空对象。所以千万不要直接给 exports赋值,还有 module.exportsexports不要并存。上面第二个文件 exports.js可这么来理解:

// 初始化(便于理解 exports 与 module.exports 的关系)
const module = {
  exports: {}
};
const exports = module.exports;

// exports.js
exports.name = '订单系统';
exports.total = (a, b) => {
  return (a, b);
}

导入机制

通过 require导入。

// 04/src/order.js
console.log('模块文件 order.js');

exports.name = 'order name';

module.exports = {
  name: '订单系统',
  total: (a, b) => {
    return a * b;
  }
};

exports.title = 'order title';
// 04/src/index.js
const title = require('./order.js').title;
const name = require('./order.js').name;

console.log(`exports.name 能展示么?-------\n${name}`);

console.log(`exports.title 能展示么?-------\n${title}`);

const total = require('./order.js').total;
setTimeout(function() {
  console.log(`module.exports 能展示么?${total(10, 10)}`);
}, 3000);

console.log('动态加载');
const modulesName = ['product.js', 'shopcar.js'];
modulesName.forEach(name => {
  require(`./${name}`).name();
});

1.缓存加载,第二次导入文件时,无需加载,因为第一次导入已经加载过了,第二次直接使用上次导入的结果; 发现没有?这是 order.js 文件这个通知在控制台里面只打印了一次,而文件 order.js实打实的引入了两次。其原理是:我们已经知道导出文件有 module这个对象,我们可能不知道的是这个对象有 loaded这么个属性(记录模块是否被夹在过),其默认值是 false,即没有加载过。当该模块第一次被加载后,loaded值会变为 true,所以第二次引入该模块就不会加载该模块了。

2.加载模块支持动态加载;

3.exportsmodule.exports不要混合使用,否则 exports会失效哦;

完整代码可查看目录 04 =>O(∩_∩)O~

ES6 Module

ES6 Module 同样是将每个文件作为一个模块,模块自身有其作用域。所不同的是,引入了关键字 import(导入)和 exports(导出),例子还是前面的例子,语法糖发生了变化。

导出机制

1.默认导出,上面例子我们都已接触过了。不过每次只能导出一个,可直接导出对象、字符串、函数等。

// 导出字符串
export default '订单系统';
// 导出对象,05/src/order.js
console.log('模块文件 order.js');

export default {
  name: '订单系统',
  total: (a, b) => {
    return a * b;
  }
};

2.命名导出,可使用关键字 as对导出变量重命名。

// 方式一,05/src/order1.js
export const name = '订单系统1';

export const total = (a, b) => {
  return a * b;
};
// 方式二,,05/src/order2.js
const name = '订单系统2';

const total = (a, b) => {
  return a * b;
};

export { name, total as getTotal };

导入机制

使用关键字 import导入,也可使用关键字 as对导入变量重命名,也可使用 as将导入变量的属性添加到后面对象(order1)中。

// 方式一
import { name, total as getProduct } from './order1';
// 方式二
import * as order2 from './order2';

示例

// 05/src/index.js
import order from './order';
import { total as getProduct } from './order1';
import * as order2 from './order2';

console.log(`order.name: ${order.name}`);
console.log(`order1 getProduct: ${getProduct(10, 10)}`);
console.log(`order2 getTotal: ${order2.getTotal(10, 10)}`);

完整代码可查看目录 05 =>O(∩_∩)O~

CommonJS 与 ES6 Module

两者本质区别在于:CommonJS 对模块依赖是“动态”的,ES6 Module 是“静态”的

1.动态,模块依赖关系是在代码运行阶段

  • require路径可动态指定;
  • 支持表达式动判断加载某个模块;

2.静态,模块依赖关系是在代码编译阶段

  • 导入、导出语句是声明式的;
  • 路径不支持表达式;
  • 导入和导出语句必须位于模块的顶层作用域(不能放在 if 语句中);

ES6 Module 优势

咋一看,CommonJS 完美 KO ES6 Module 的方式。可事实并非如此,ES6 Module 这种“静态”方式有优势:

  • 僵尸代码检测和排除,减小资源打包体积。即用静态分析工具检测模块或接口中哪些没有被调用过(比如某个组件只用了部分功能,但有可能所有代码都被加载进来了),这些加载进来未被调用的代码就是僵尸代码。静态分析可以在打包时将这些僵尸代码去掉,减小资源打包体积;
  • 编译器优化,动态模块的导入是一个对象,而 ES6 Module 可直接导入变量,减少引用层级,提高程序效率;

值拷贝和动态映射

场景:导入一个模块时,不同模块模式是不一样的。

  • CommonJS 是值拷贝,可编辑;
  • ES6 Module 是址引用,即映射,只读,即不可编辑;

// 06/src/commonJs.js
let csCount = 0;

module.exports = {
  csCount,
  csCountAdd: () => {
    csCount += 10;
  }
};
// 06/src/es6-module.js
let esCount = 0;
const esCountAdd = () => {
  esCount += 10;
};

export { esCount, esCountAdd };
// 06/src/index.js
// CommonJS Module
let csCount = require('./commonJs').csCount;
let csCountAdd = require('./commonJs').csCountAdd;

console.log(`----commonjs 初次加载----\n${csCount}`);
csCountAdd();
console.log(`----commonjs 内部自加 10 -----\n${csCount}`);
csCount += 20;
console.log(`----commonjs 启动项自加 20------\n${csCount}`);

// Es6 Module
import { esCount, esCountAdd } from './es6-module.js';
console.log(`----es6 初次加载----\n${esCount}`);
esCountAdd();
console.log(`----es6 内部自加 10 -----\n${esCount}`);
esCount += 20;
console.log(`----es6 启动项自加 20------\n${esCount}`);

通过例子及上图运行结果,可剖析

  • CommonJs 是一份值的拷贝,虽然调用了 csCount(),但是并没有造成在文件 index.js中副本(csCount)的影响;而副本(csCount)在文件 index.js中可更改;
  • ES6 是一份值的动态映射,调用了 esCount(),文件 index.js中副本(esCount)的也随之变化;而副本(esCount)在文件 index.js中是不可更改,即是只读的;

完整代码可查看目录 06 =>O(∩_∩)O~

循环依赖

通常工程中是应该尽量避免这种恶心的循环依赖的产生,因为会带来复杂度。可尽管我们知道这是不好的,也理性地避免发生。但链条长了,业务多了,人员多了,还是不知不觉中“造孽般地”写出这样的代码。

场景: A 依赖 B,B 依赖 C,C 依赖 D,D 依赖 A。其实 A 与 B 就互相依赖了。

CommonJs Module

// 07/src/a.js
const fileB = require('./b.js');
console.log(`----- a.js 文件展示 b.js 内容 ------\n`, fileB);

module.exports = '这是 a.js 文件';
// 07/src/b.js
const fileA = require('./a.js');
console.log(`----- b.js 文件展示 a.js 内容 ------\n`, fileA);

module.exports = '这是 b.js 文件';
// 07/src/index.js
require('./a.js');

我们脑海中自运行结果是

----- b.js 文件展示 a.js 内容 ------
 这是 a.js 文件
----- a.js 文件展示 b.js 内容 ------
 这是 b.js 文件

可控制台是

反复检查了代码,没错啊,可本该显示 这是 a.js 文件,为何展示 {}?不行,我还是要好好捋一捋

  • 文件 index.js导入文件 a.js,开始执行文件 a.js,第一行导入文件 b.js,此时进入文件 b.js内部;
  • 文件 b.js第一行又导入文件 a.js,循环依赖由此产生。这时执行权并没有交回给文件 a.js,而是直接取导出值,此刻文件 a.js还未执行技结束,导出值就默认为空对象,因此文件 b.js执行打印语句时,显示 ----- b.js 文件展示 a.js 内容 ------ {};
  • 文件 b.js执行结束了,执行权接着交回给文件 a.js, 文件 a.js继续执行第二行,然后在控制台打印 ----- a.js 文件展示 b.js 内容 ------ 这是 b.js 文件。至此,这个流程结束;

完整代码可查看目录 07 =>O(∩_∩)O~

ES6 Module

// 08/src/a.js
import fileB from './b.js';
console.log(`----- a.js 文件展示 b.js 内容 ------\n`, fileB);

export default '这是 a.js 文件';
// 08/src/b.js
import fileA from './a.js';
console.log(`----- b.js 文件展示 a.js 内容 ------\n`, fileA);

export default '这是 b.js 文件';
// 07/src/index.js
import a from './a.js';

我们脑海中自运行结果自然也是

----- b.js 文件展示 a.js 内容 ------
 这是 a.js 文件
----- a.js 文件展示 b.js 内容 ------
 这是 b.js 文件

可控制台是

完整代码可查看目录 08 =>O(∩_∩)O~

我晕,还是不对,这次不是空对象了,却是 undefined。那循环依赖该如何解决呢?

---> 等我下篇 <---

上一篇:webpack 简介

原文链接:juejin.im

上一篇:TypeScript 3.8 Beta
下一篇:JavaScript 对象赋值和浅拷贝的区别
相关教程
关注微信

扫码加入 JavaScript 社区

相关文章

首次访问,需要验证
微信扫码,关注即可
(仅需验证一次)

欢迎加入 JavaScript 社区

号内回复关键字:

回到顶部