Commonjs (同步模块加载,运行时加载)
exports 和 moudle.exports 的区别?
exports 是 module 中exports 对象 的引用。当模块中使用了 moudle.export = {} 的时候,所有 exports 都会失效。exports 只能 exports.a = a; 这样使用,不能 exports = {};这种使用方式无效。
每个模块其实都相当于外层包裹了一个函数,向内传递了一个 module的参数。
Require
require是node环境中 的 全局函数
require可以导入js和json文件
可以忽略后缀名,默认后缀名是.js
目录加载:
如果有package.json,会以main字段的值作为加载路径
否则会尝试加载index.js => index.json => index.node
如果都没有找到,会直接报错
模块缓存:
require.cache缓存了已经加载过的模块,key是文件的绝对路径
可以通过 delete require.cache['/home/xxx/yyy/zzz/a.js']来清除缓存
加载机制:
导出的值是一个拷贝,而不是引用
循环引用问题: 先引用的模块提供给后引用的模块一个不完整的 exports 导出
// 循环加载,也就是a加载b,b加载a
// 如果发生循环依赖,b在加载a时,得到的是不完整的a
浏览器端的模块化方案
AMD,requirejs ,需要先加载requirejs 第三方库 来实现模块化, define 一个模块,require去请求,特殊的写法
CMD,seajs
ESModule
SModule是W3C提出的JavaScript标准的模块化方案。
ESModule模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。(依赖前置)
ESModule模块不是对象,而是通过export命令显式指定输出的代码,再通过import命令输入
规范
- import:导入一个模块所提供的接口。
- import A, {B} from './test.js'; A 为 export default 导出的值。 {} 中 为 export 导出的值的结构。A可以自己命名,B 则必须为导出的值的名称,否则无法进行结构。如果该变量和自己模块变量产生冲突,可以使用as 进行重命名。
- Import * as A from './test.js'; 导出整个Module对象 并重命名为A; A的值为下图:
当require 一个esm的模块的时候,效果同下,会返回整个模块对象,需要使用该对象的default属性才能获取到默认导出。
- export:导出当前模块需要对外提供的接口。
- export default:模块默认导出的接口,一个模块只有一个default。如果采用require 引入 esmodule 模块,则会将整个模块对象返回,需要取 a.default 才能取到default 的内容
- import():import()方法是动态导入,在代码运行时加载指定模块。
- ESModule自动启用严格模式。
Esmodule 在浏览器中使用原理
如果直接在script标签内使用 import 会提示需要在 module 内使用import。所以如果需要使用import 和 export 关键字需要在 script 标签上加上 type = "module",
在浏览器 / Node 环境中,会有一个loader下载器去帮我们查找并且加载所有的文件。具体如下:loader先要判断出入口文件,在html文件里边,loader可以通过携带type="module" 的script标签,来判断出是一个入口文件。注意,由于是通过CORS 去下载模块文件,所以在内部 使用import 引用文件名时候一定要带上后缀名.js ,否则会报错找不到该文件。
import 是静态执行,所以 import 具有提升效果,即 import 命令在模块中的位置并不影响程序的输出。
循环引用 ,export 命令会有变量声明提前的效果,在还没有执行代码赋值时,循环引用取到的指会是 undefined
如何理解esmodule 对结果的引用 以及 commonjs 对结果的拷贝?
答:在esm 中 export const a = 3; 导出的是 a的引用,也就是所有的地方都是直接访问的 a的内存中地址去找到a的值,如果a的值变了,所有使用到a的地方都会改变。 而cmj 中是对a的拷贝,虽然是浅拷贝,但是 导出的a的值会和原来的a变成两个内存地址,不会相互影响。但是如果是个对象,拷贝的还是对象的地址引用,还是会影响。
ES6 模块跟 CommonJS 模块的不同,主要有以下两个方面:
- ES6 模块输出的是值的引用,输出接口动态绑定,而 CommonJS 输出的是值的拷贝
- ES6 模块编译时执行,而 CommonJS 模块总是在运行时加载
在node 中可直接使用commonjs,因为node中已经有相关的实现,比如require全局函数,module等。在浏览器中不能使用require。同样在node 不能直接使用 esm。 esm在浏览器使用也是有条件的,需要在script 标签的type属性设置为"module"。在使用webpack打包情况下,根据webpack内部实现的模块化机制,可以同时使用esm和cmj 模块化。
esm的原理是在编译时候去 请求对应模块的js文件,同样也会增加一个Module对象进行包裹。
cmj 的原理是代码运行时去同步获取 模块的js文件
Webpack 如何打包 esm的代码的,其实原理同cmj类似,相当于内部自己实现了一套模块化方法。其内部使用了 webpack_require 函数在代码执行时,通过require 去加载代码
实际上 webpack 是可以支持 CommonJS 和 ES Module 一起混用的