[ JS 基础 ] JS 模块化

时间:2020-06-24 19:30:22   收藏:0   阅读:60

JS 模块化

还是梳理一下模块化吧,以便排错

JS 最初的设计是没有模块化的,仅仅是单文件裸奔。。当然之前有很多模块化的实现方法,不过目前比较流行的模块化规范是 Nodejs 中 CommonJS 的模块化( 2019 年)以及 ES6 的模块化( 2015 年)

CommonJs

模块导出

关键字:module.exports exports

moduleexports都是对象类型

最终暴露给其他模块的对象是module.exportsmodule.exports exports两者指向的内存地址是一样的(同一个引用),其实在模块加载的最开始会执行:let exports = module.exports

导出例子:

// 导出内容作为属性
module.exports.name = ‘js‘;
module.exports.foo = () => console.log(‘foo‘);
exports.abc = 123;

// 整体导出
module.exports = {name: ‘js‘, foo: () => {}, abc: 123};
// exports = {name: ‘js‘, foo: () => {}, abc: 123};  // 不可以

注意:整体导出只能使用module.exports,因为一旦是整体导出,是用一个对象去替换module.exports 的指向。如果用exports来整体导出,并没有改变module.exports的指向,细品上面的粗体。

模块导入

关键字:require

导入例子:require(‘./moduleA‘)

导入规则

相对路径的情况下,在导入的目录下寻找moduleA

如果同一级目录下没有moduleA文件,则会去找同级的moduleA目录

绝对路径的情况下,同上

没有开头路径的模块,比如vue

模块实现

Node的模块实际上可以理解为代码被包裹在一个函数包装器里面

**require wrapper: **(从知乎上抄的)

function wrapper (script) {
    return ‘(function (exports, require, module, __filename, __dirname) {‘ + 
        script +
     ‘\n})‘
}

function require(id) {
 var cachedModule = Module._cache[id];
  if(cachedModule){
    return cachedModule.exports;
  }

  const module = { exports: {} }

  // 这里先将引用加入缓存
  Module._cache[id] = module

  //当然不是eval这么简单
  eval(wrapper(‘module.exports = "123"‘))(module.exports, require, module, ‘filename‘, ‘dirname‘)


  return module.exports
}

类似源码( TODO )的简单实现吧,主要关键是会缓存模块

注意这个缓存,在多次导入同一个模块的时候

  1. 会先判断是否已经缓存过这个模块(对象)
  2. 如果是没有缓存,将 module 先加入缓存(拷贝的方式:注意引用类型),执行这个 module (初始化),返回module.exports对象。
  3. 总之要注意多次导入的其实是同一个缓存中的对象。

可以写一下试一试

// moduleA.js
let abc = {x: ‘abc‘}
console.log(‘before: ‘, abc)
setTimeout(() => {
  abc.x = ‘bbbbbbbbxxxxx‘
  console.log(‘after 3s: ‘, abc)
}, 3000);
module.exports = abc

// index.js
const abc = require(‘./moduleA‘)
console.log(‘from index: abc‘, abc);
setTimeout(() => {
  console.log(‘after 3.5s : abc‘, abc);
}, 3500);
setTimeout(() => {
  const abcc = require(‘./moduleA‘)
  console.log(‘next require: abc‘, abcc);
}, 5000);

P.S. CommonJS 的模块化是动态的,运行时导入模块的对象(那一套缓存)

ES6 Module

参考阮一峰老师:https://es6.ruanyifeng.com/#docs/module

ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。

核心:静态

使用 CommonJS 模块是运行时加载

const { resolve } = require(‘path‘);

// 等价于
const _path = require(‘path‘);
const resolve = _path.resolve;

ES6 编译时加载(或者静态加载)

import { resolve } from ‘path‘;

这样以来不仅效率高了,而且仅加载一个方法。 不过这也导致了没法引用 ES6 模块本身,因为它不是对象。

严格模式( ES5 )

ES6 的模块自动采用严格模式

严格模式主要有以下限制。

export

将某个变量给外部开放使用接口

几种写法

// 
export var a = 1;
export function foo() {};

// 打包导出
const c = ‘123‘;
function b() {};
export {
	c as cc,
  b,
}
// 只能这样写 不然语法会报错

export语句输出的接口,与其对应的值是动态绑定关系,即通过该接口,可以取到模块内部实时的值。

export var foo = ‘bar‘;
setTimeout(() => foo = ‘baz‘, 500);

最后,export命令可以出现在模块的任何位置,只要处于模块顶层就可以。如果处于块级作用域内,就会报错。这是因为处于条件代码块之中,就没法做静态优化了,违背了 ES6 模块的设计初衷。

import

import { resolve } from ‘path‘;

接收大括号,对应模块中导出的变量名

当然也可以用 as 重新取个名字

所有import进来的都是 read-only 的,也就是 const 的,但是如果是引用类型,你知道可以怎么做吧。

注意:

export default

为模块指定默认输出,用户不需要知道这个模块有什么东西,一股脑的导入即可,可以任意命名。

// import-default.js
import customName from ‘./export-default‘;
customName(); // ‘foo‘

export default之后的有名字的函数都“名亡实存”了

注意:

本质上,export default就是输出一个叫做default的变量或方法,然后系统允许你为它取任意名字。所以,下面的写法是有效的。

export const e = ‘123123‘

export {
    e as default
}
// 等价于
// export default e
import $ from ‘lodash‘;  // 鲁大师也能是 dollar

其实通过转码之后的export default会编译为exports.default = xxxx

export 与 import 的复合写法

如果在一个模块之中,先输入后输出同一个模块,import语句可以与export语句写在一起。

export { foo, bar } from ‘my_module‘;
// 向外转发了这两个接口 实际并没有导入到当前模块

// 可以简单理解为
import { foo, bar } from ‘my_module‘;
export { foo, bar };

用到这样的场景?

// 接口改名
export { foo as myFoo } from ‘my_module‘;

// 整体输出
export * from ‘my_module‘;

其他的情况见阮一峰

import()

import是在编译时静态处理(提升到最前处理),那么我们想动态的导入模块怎么办呢?

import命令叫做“连接” binding 其实更合适

为了提升动态性,ES2020 提案引入import()函数

import(specifier).then(module => {
  // ...
}).catch(err => {
  // ...
})

返回一个 Promise 。

import()函数与所加载的模块没有静态连接关系,这点也是与import语句不相同。import()类似于 Node 的require方法,区别主要是前者是异步加载,后者是同步加载。

适用场景

注意

对比

CommonJS 的模块化是动态的,运行时导入(那一套缓存)

小结

感觉有些方面模块化这种东西大同小异,比如类比 Python , __init__.py 文件就相当于是 JS 中的 index.js 所以一个目录都可以是一个 module 或者叫 package ,但是我还是觉得 Python 中用 . 作为子模块的连接比较舒服(前提是所有路径都是模块)。

评论(0
© 2014 mamicode.com 版权所有 京ICP备13008772号-2  联系我们:gaon5@hotmail.com
迷上了代码!