一、模块化概述
1.概念
- 随着前端应用日趋复杂,项目代码也大量膨胀,模块化就是一种最主流的代码组织方式
- 一个模块就是一个实现特定功能的文件,它通过把我们的复杂代码按照功能的不同,划分为不同的模块单独维护的这种方式,去提高我们的开发效率,降低维护成本。要用什么功能就加载什么模块
- 模块化开发是当下最重要的前端开发范式之一,其只是思想,不包含具体实现
2.模块化开发的好处
- 避免变量污染、命名冲突等
- 提高代码复用率
- 提高维护性
- 依赖关系的管理
3.模块化演变过程
(1)文件划分方式
- 将每个功能和相关的一些状态数据单独存放在不同的文件当中,此时一个文件就是一个独立的模块。然后将这个模块引入页面当中,直接调用模块中的成员(变量/函数),一个script标签就对应一个模块,所有模块都在全局范围内工作。
- 缺点:
- 污染全局作用域
- 命名冲突问题
- 无法管理模块依赖关系
(2)对象封装
- 将所有模块成员封装在一个对象中,当要使用的时候就调用这个对象的属性
- 缺点:
- 没有私有空间,模块成员仍然可以在外部被访问或修改
- 无法管理模块依赖关系
(3)立即执行函数
- 采用该方式为模块提供私有空间,将模块中每个成员都放在一个函数提供对的私有作用域中,可确保私有成员的安全。私有成员只能在模块成员内通过闭包的形式访问
二、模块化规范
1、CommonJS规范
(1)概述
- node应用由模块组成,采用
common.js
模块规范。每一个文件就是一个模块,拥有自己独立的作用域、变量、方法等,读其他的模块不可见 - CommonJS规范规定,
module
变量代表当前模块,其是一个对象,其属性exports
,即module.exports
,是对外的接口 - require方法用于加载模块,即加载模块的
module.exports
属性
(2)CommonJS模块的特点
- 所有代码都运行在模块作用域,不会污染全局作用域
- 模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了;以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。
- 模块加载的顺序,按照其在代码中出现的顺序。
(3)moudle对象
a. module对象的属性
module.id
: 模块的识别符,通常是带有绝对路径的模块文件名。
module.filename
: 模块的文件名,带有绝对路径。
module.loaded
:返回一个布尔值,表示模块是否已经完成加载。
module.parent :
返回一个对象,表示调用该模块的模块。
module.children
: 返回一个数组,表示该模块要用到的其他模块。
module.exports
: 表示模块对外输出的值。
b. module.exports属性
module.exports
属性表示当前模块对外输出的接口,其他文件加载该模块,实际上就是读取module.exports
变量。
c. exports变量
- 为了方便,Node为每个模块提供一个exports变量,指向module.exports。这等同在每个模块头部,有一行这样的命令。
var exports = module.exports
- 在对外输出模块接口时,可以向exports对象添加方法,如
exports.area = function (r) {
return Math.PI * r * r;
};
exports.circumference = function (r) {
return 2 * Math.PI * r;
};
- 不能直接将exports变量指向一个值,因为这样等于切断了exports与module.exports的联系。下面这样的写法是无效的,因为exports不再指向module.exports了。
exports = function(x) {console.log(x)};
- 如果一个模块的对外接口,就是一个单一的值,不能使用exports输出,只能使用module.exports输出。
module.exports = function (x){ console.log(x);};
总结:尽量使用
module.exports
(4)require命令
a.基本用法
require
命令用于加载模块文件,读入并执行一个JavaScript文件,然后返回该模块的exports对象。如果没有发现指定模块,会报错。如:
example.js
exports.name = 'tom';exports.age = 50;
- 同目录下的
demo.js
文件
var example = require('./example.js');
console.log(example.name); // tom
console.log(example.age); // 50
或者写函数
example.js
function fn(){console.log(1)};
var name = 'tom'
module.exports = {fn:fn,name:name}
- 同目录下的
demo.js
文件
var example = require('./example.js');
example.fn(); // 1
console.log(example.name); // tom
b.加载规则
require
命令用于加载文件,后缀名默认为.js。
var foo = require('foo');
// 等同于
var foo = require('foo.js');
根据参数的不同格式,require命令去不同路径寻找模块文件。
c.目录的加载规则
- 通常,我们会把相关的文件会放在一个目录里面,便于组织。这时,最好为该目录设置一个入口文件,让
require
方法可以通过这个入口文件,加载整个目录。 - 在目录中放置一个
package.json
文件,并且将入口文件写入main字段,如
// package.json
{ "name" : "some-library",
"main" : "./lib/some-library.js" }
require
发现参数字符串指向一个目录以后,会自动查看该目录的package.json
文件,然后加载main字段指定的入口文件。如果package.json
文件没有main字段,或者根本就没有package.json
文件,则会加载该目录下的index.js
文件或index.node
文件。
d.模块的缓存
- 第一次加载某个模块时,Node会缓存该模块。以后再加载该模块,就直接从缓存取出该模块的
module.exports
属性。 - 如果想要多次执行某个模块,可以让该模块输出一个函数,然后每次
require
这个模块的时候,重新执行一下输出的函数。 - 所有缓存的模块保存在
require.cache
之中,删除模块的缓存:
// 删除指定模块的缓存
delete require.cache[moduleName];
// 删除所有模块的缓存
Object.keys(require.cache).forEach(function(key) {
delete require.cache[key];
})
缓存是根据绝对路径识别模块的,如果同样的模块名,但是保存在不同的路径,require命令还是会重新加载该模块。
e.模块的循环加载
- 如果发生模块的循环加载,即A加载B,B又加载A,则B将加载A的不完整版本。
f.require.main
- require方法有一个main属性,可以用来判断模块是直接执行,还是被调用执行。直接执行的时候(
node module.js
),require.main属性指向模块本身。
require.main === module
// true
- 调用执行的时候(通过
require
加载该脚本执行),上面的表达式返回false。
(5)node.js的配置
CommonJs
是服务器端模块的规范,Node.js采用了这个规范。- CommonJS 加载模块是同步的,即只有加载完成才能执行后面的操作。 像Node.js主要用于服务器的编程,加载的模块文件一般都已经存在本地硬盘,所以加载起来比较快,不用考虑异步加载的方式,所以CommonJS规范比较适用。
2. AMD规范
(1)概述
- AMD是
Asynchronous Module Definition
的缩写,意思就是"异步模块定义" - 异步模块模式AMD是当请求发出后,继续其他业务逻辑,直到模块加载完成执行后续逻辑,实现模块开发中的对模块加载完成后的引用
- 。由于Node.js主要用于服务器编程,模块文件一般都已经存在于本地硬盘,所以加载起来比较快,不需要异步加载,所以CommonJS规范比较适用。但是,如果是浏览器环境,要从服务器端加载模块,这时就必须采用异步模式,因此浏览器端一般采用AMD规范。
- AMD对应的就是很有名的
RequireJS
RequireJS
是一个工具库,主要用于客户端的模块管理。它可以让客户端的代码分成一个个模块,实现异步或动态加载,从而提高代码的性能和可维护性。它的模块管理遵守AMD规范。
(2)require.js
的使用
a.下载require.js
b.引用:
<script src="js/require.js" data-main="js/main"></script>
data-main
属性的作用是,指定网页程序的主模块,它会在require.js加载完成后首个加载,总的文件入口
c.语法
define([id], [dependencies], factory)
id
:可选,字符串类型,定义模块标识,如果没有提供参数,默认为文件名dependencies
:可选,字符串数组
AMD 推崇依赖前置,即当前模块依赖的其他模块,模块依赖
必须在真正执 行具体的factory方法前解决
factory
:必需,工厂方法,初始化模块需要执行的函数或对象。如果为函数,它只被执行一次。如果是对象,此对象会作为模块的输出值。
(3)模块的写法
require(['moduleA', 'moduleB', 'moduleC'], function (moduleA, moduleB, moduleC){
// 代码
});
- 一般情况下主模块都会依赖于其他模块,此时则需要使用AMD规范定义的的
require()
函数 require()
函数接受两个参数,第一个参数是数组,表示所依赖的模块;第二个参数是一个回调函数,当前面指定的模块都加载成功后,它将被调用。- 加载的模块会以参数形式传入该函数,从而在回调函数内部就可以使用这些模块。
require()
异步加载moduleA,moduleB和moduleC,浏览器不会失去响应;它指定的回调函数,只有前面的模块都加载成功后,才会运行,解决了依赖性的问题。
(3)定义模块define()
- 定义模块是同一个
define()
函数来定义的
//定义一个对象模块
//该对象作为输出值传递给其它引入它的模块
define({
a:'this is a ',
add:function(x,y){
return x + y
}
})
//定义模块作为函数
//此函数需要有返回值,作为它的输出值
define(function(){
//做一些什么事情...
retunn {
a:'this is a ',
add:function(x,y){
return x + y
}
}
})
//引入依赖模块moduleB
//moduleB的输出值作为函数的参数
//moduleA.js
define(['moduleB'],function(moduleB){
//调用moduleB中的log函数
moduleB.log()
retunn {
a:'this is a ',
add:function(x,y){
return x + y
}
}
})
//moduleB.js
define(function(){
return {
log:function(){
console.log('this is moduleB');
}
}
})
- 定义好模块之后就可以在其他的模块引入了
3.CMD规范
CMD,即Common Module Definition
参考
三、JS代码规范
1.变量和函数的命名规则
变量:
用有意义且常用的单词命名变量,且用驼峰法命名
函数:
- 参数越少越好
- 函数名能够“顾名思义”
- 函数只做一件事情
- 不要过多嵌套函数
- 删除重复代码
- 给对象设置默认属性
2.ESlint:代码质量规范
ESlint是代码质量规范,如定义了一个变量,但没有使用过它,ESlint会告诉你
配置ESlint
- 在VSCode中安装
ESlint
插件,按照官方文档进行配置 - 或者通过
node.js
来安装和配置,参考ESlint配置方法