JavaScript模块化学习
资料
什么是模块
把一些复杂的程序根据一定的规则拆分乘多个单个文件,并最终组合在一起。
这些被拆分出来的文件是模块,模块内部数据是私有的,只有向外部暴露一些方法才能与外部其他模块进行通行。
Node.js 已经提供这个能力很长时间了,还有很多的 Javascript 库和框架 已经开始了模块的使用(例如, CommonJS 和基于 AMD 的其他模块系统 如 RequireJS, 以及最新的 Webpack 和 Babel。
好消息是,最新的浏览器开始原生支持模块功能了,这是本文要重点讲述的。这会是一个好事情 — 浏览器能够最优化加载模块,使它比使用库更有效率:使用库通常需要做额外的客户端处理。
为什么要模块化
- 降低代码的复杂度,提高解耦性。高内聚,低耦合。如果一个文件是写的一个功能,那么它是不能在拆分的,只能提高它的内聚性,让代码配合更加紧密。低耦合,不同功能点(模块)之间,把修改的影响降到最低。[高内聚低耦合_百度百科 (baidu.com)](https://baike.baidu.com/item/高内聚低耦合#:~:text=高内聚低耦合,是 软件工程 中的概念,是判断软件设计好坏的标准,主要用于程序的 面向对象 的设计,主要看类的内聚性是否高, 耦合度 是否低。. 目的是使程序模块的可重用性、移植性大大增强。.,外文名. High cohesion %26 Low coupling. 范 围.)
- 避免命名冲突。特别人是多人开发的时候,特别容易命名冲突。
- 更好的分离,按需加载,而不是怼一堆的js文件在页面(多个script标签)。
- 提高代码的复用性,高维护性。
模块化规范
现在较为流行的模块化规范是 CommonJS(既可以写浏览器端的代码,也可以写nodejs的代码)、ES6、AMD (异步加载,在浏览器端)。
CommonJS
规范
CommonJS规范 – JavaScript 标准参考教程(alpha) (ruanyifeng.com)
- 每一个文件都是模块。
- CommonJS 模块化的代码可以在服务端运行,也能在浏览器端运行
- 服务器端:模块化的代码可以直接运行。
- 浏览器端:模块化的代码要经过Browserify编译运行
需要注意的是,如果引用使用CommonJS规范引入模块,那么引入的那个模块文件中的所有代码都会运行一次,不管某些内容是否被暴露。
CommonJS规范加载模块是同步的,也就是说,只有加载完成,才能执行后面的操作。
基本语法
下述的代码都是在node端运行的,如果不是将会做出说明。
1. 暴露语法
module.exports = value;
exprort.xxx = value;
并且最终暴露出的是module.exports,而不是exports。
2. 内置关系
我们使用代码验证它们两个指向的是否为同一个对象。
console.log(module.exports === exports);
确实是指向的同一个对象。但是浏览器是没有这两个东西的。
3. 引入语法
require
命令的基本功能是,读入并执行一个JavaScript文件,然后返回该模块的exports对象。如果没有发现指定模块,会报错。
const xxx = require("模块的路径");// xxx 最好与模块名相同
let name = 'fjx';
let age = 21;
let obj = {
say : function () {
console.log("你好的!");
}
}
// 写多个不会多个暴露。赋值操作啊
// 暴露,不暴露的内容是不会被外部引用的,而且使用CommonJS引用模块的时候模块里面的所以代码都是运行一次的
//module.exports = obj;
//module.exports.a = obj;
exports.a = obj //必须这么写
// 最后暴露出去的是 module.exports
exports.sum = function () {
let ans = 0;
for(let i=0; i<arguments.length; i++){
ans += arguments[i];
}
return ans;
}
// 暴露的是 module.exports指向的对象
// 如果module.exports = newObj; 那么 exports.xxx = asd; 这个暴露就没有用了,引用的知识
// 所以别混用,尽量只用一个
引入的代码
const {person,sum} = require('./module1');
console.log(module.exports === exports);
console.log(person);
// require 的导入路径的问题
// requir 导入第三问文件
为什么不能这样设置exports = xxx
,这样设置的结果就是引用的使用空对象{}
。原因如下:
exports = module.exports = {},而且最后返回的是module.exports的内容,
exprots 和 module.exports 都是对一个空对象的引用,那么既然exports = xxx,就意味着它不在指向那个空对象了,所以返回的就是一个空对象,而不是设置的值。
但是使用module.exports = xxx 就可以正确返回,这是应该返回的就是module.exports,与初始的那个空对象没有关系了。
这里有几点注意:
- 如果没有加文件后缀,会按照以下顺序后缀加载文件
- .js fs模块同步读取文件编译执行
- .json fs模块同步读取文件,用JSON.parse()解析返回结果,它返回的是一个对象,而且json文件不需要使用暴露。
- .node 这是c/c++编写的扩展文件,通过dlopen()方法编译
- 其他扩展名 会以.js文件载入
- 如果是文件夹则会默认加载该文件夹下 package.json 文件中 main 属性对应的文件(一般是index.js)
- 根据这个查找顺序,如果不是核心模块或者安装的模块(也就是所加了路径的,是"./xxx"或者加了路径的require),如果文件和文件夹同名,先查找文件再是文件夹。
- 如果 main 属性对应的文件不存在,则自动找 index.js index.json
- 如果是内置模块或者是 npm 安装的模块,直接使用包名字即可(不需要路径,而且不需要加.js,我们是通过对于包文件夹下的的package.json中的main中的xxx.js 去加载包的,切记,加了.js 还会报错,因为node_modules 下根本就没有你要的包.js文件,而在是在对应的文件夹下),其实就是引用了那个包的
package.json
里面的main - npm 引入包时,如果当前文件夹下的 node_modules 没有,则会自动向上查找(所以就不要加路径了,要不然无法自动向上找)
4. CommonJS的拷贝机制
拷贝机制是浅拷贝,如果某个模块中的引用类型的属性或者方法发送变化,那么外部引用这个模块的数量或者方法也会发生变化,但是基本数据类型就不会存在这个问题。
5.在浏览器端使用CommonJS
在浏览器端是没有module.exports
、exports
和 require
的,所以要使用一个库browserify
。
yarn global add browserify
编译指定的文件
browserify 指定路径下的文件(使用require的那个文件) -o 指向路径下的文件
大致原理就只作者写了一个require吧。
这样浏览器就可以正常使用了。
ES6模块化
阮一峰大佬已经总结的很好了,直接读它的文章即可。