Webpack 知识补充之模块

CommonJs

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

自身做用域

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

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

导出机制

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

// 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 可这么来理解:github

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

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

导入机制

经过 require 导入。web

// 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.加载模块支持动态加载;app

3.exportsmodule.exports 不要混合使用,不然 exports 会失效哦;函数

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

ES6 Module

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

导出机制

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 简介