第二节: 模块化Module
NodeJs 采用模块化方式,管理和组织代码,NodeJS的所有功能都存在每个模块中的
1. 模块和模块化开发的了解
1.1 什么是模块
模块: 一个具有特定功能的文件就是一个模块
模块的优点: 有了模块,我们就可以非常方式使用这些模块,因为模块总是完成了特定的功能,如果要修改模块中个功能,那么需要修改这个自己的模块文件即可,模块独立于每一个文件中,不影响模块的代码
模块之间相互独立,如果一个模块引入另一模块,要使用里面的值,我们就需要在被引入的模块中暴露这些值.
1.2 什么是模块化
模块化: 将一个复杂的程序依据一定的规则(规范)封装成几个模块(文件),并进行组合在一起,每个模块内部数据实现私有的, 只是向外部暴露一些接口(方法)与外部其他模块通信
按照模块化思考开发的项目, 也被称为模块化开发.
模块化的进化
// 全局开发模式
// 最早期所有的js代码卸载一个js文件中
function foo(){}
function bar(){}
// 造成的问题,就是代码量过大以后,Global全局被污染,很容易导致命名冲突
后来对代码进行简单的封装
// 简单封装: Namespac 模式, 就是我们俗称的命名空间
var Namespac = {
foo: function(){},
bar: function(){}
}
// 减少Global上的变量数目
// 本质就是对象,不太安全
因为简单封装的不安全,又出现了IIFE模式
// 匿名闭包: IIFE模式
var Module = (function(){
var foo = function(){
}
return {
foo: foo
}
})()
Module.foo()
// 函数是JavaScript 的Local Scope
有的时候,我一个模块可能还需要依赖其他的模块,我们就需要在升级一下,将依赖的模块注入
// 增强封装, 引入依赖
var Module = (function($){
var $body = $(body);
var foo = function(){
}
return {
foo:foo
}
})($)
// 这就是模块模式,也是现代模块实现的基石
1.3 为什么要模块化
- 降低复杂度
- 提高解耦性
- 部署方便
1.4 模块化的好处
- 避免命名冲突(减少命名空间的污染)
- 更好的分类,按需加载
- 更高的复用性
- 高可维护性
1.5 模块的规范
-
CommonJS(NodeJS)
-
AMD
-
CMD
-
ESModule (ES模块化)
1. CommenJS 规范
Commos 是诞生比较早的,NodeJS就采用了CommonJs的规范来定义模块的,但是CommonS采用的是同步加载文件的方式,只适用于服务端
CommonJS API :不是一个语言,也不是一个平台,它只是一个标准,主要是对原有的JavaScript标准API进行了增强.
CommonJs是一种规范,NodeJS是对这个对方的实现
在规范中每一个文件都是一个独立的模块
特点:
- 在服务器端:模块的加载是运行时同步加载
- 在浏览器端,模块需要体检编译打包处理
2. NodeJs 模块化
2.1 Node中模块分类
- 系统模块
NodeJS开发 团队已经开发了很多功能模块,不需要卸载,直接 引入就可以使用 - 第三方模块
第三模块必须先安装在使用
使用报管理工具npm进行安装
第三方模块在npmjs.org中 - 自定义模块
自己写的一个js文件就是一个模块
也有将模块分成两种的
- 核心模块(系统模块)
- 文件模块,(包括第三方模块和自定义模块)
还有按照模块功能按照关系划分为以下两类
- 主模块
主模块就是被node执行的模块,通常喜欢命名app.js main.js index.js
一个项目只能有一个主模块(也叫入口文件,就是整个项目的启动模块) - 依赖模块
2.2 内置模块
NodeJs中的内置了很多模块,可以直接使用require来进行引用.国际惯例,你接受的名字最好和模块的名字一样
const http = reqire('http')
如果特别长可以简写
const qs = require('querystring')
内置模块的引用是无条件的,无路径的
2.3 自定义模块
自定义模块就是自己写的一个模块文件
每一个js都是一个模块,Node.js使用commonjs规范
定义a模块
// a.js
var str = '这个是一个字符串'
exports.str = str; // exports 是导出(也可以叫暴露)对象
定义b模块导入a模块
// b.js
var aa = require('./a.js');
// 此时aa是a模块中暴露对象,如果要使用暴露的字符串str,
console.log(aa.str)
然后通过node执行b.js
node b.js
会发现require()谁,就会执行谁,会让a.js文件执行
注意点:
- 会发现require()谁,就会执行谁,会让a.js文件执行
- require()引入的模块如果有异步语句,不会死等,会将所有的同步执行完毕后在执行异步语句
- 如果是多层引用会先把引入文件里的引入执行干净了
- 如果循环引用.A引用B,B引入A,Node会很智能的组织好第二次引入
3. Commen模块化
3.1. 模块的组成
在模块中打印当前模块的组成信息,
console.log(arguments)
console.log(arguments.callee.toString); // 打印函数体
打印的arguments对象
[Arguments] {
'0': {},
'1':{
[Function: require]
resolve: { [Function: resolve] paths: [Function: paths] },
main:
Module {
id: '.',
exports: {},
parent: null,
filename: 'C:\\Users\\HiWin10\\Desktop\\study\\app.js',
loaded: false,
children: [Array],
paths: [Array] },
extensions:[Object: null prototype] {
'.js': [Function],
'.json': [Function],
'.node': [Function]
},
cache:[Object: null prototype] {
'C:\\Users\\HiWin10\\Desktop\\study\\app.js': [Module],
'C:\\Users\\HiWin10\\Desktop\\study\\wu.js': [Module]
}
},
'2':
Module {
id: '.',
exports: {},
parent: null,
filename: 'C:\\Users\\HiWin10\\Desktop\\study\\app.js',
loaded: false,
children: [ [Module] ],
paths:[
'C:\\Users\\HiWin10\\Desktop\\study\\node_modules',
'C:\\Users\\HiWin10\\Desktop\\node_modules',
'C:\\Users\\HiWin10\\node_modules',
'C:\\Users\\node_modules',
'C:\\node_modules'
]
},
'3': 'C:\\Users\\HiWin10\\Desktop\\study\\app.js',
'4': 'C:\\Users\\HiWin10\\Desktop\\study'
}
打印的函数体
function (exports, require, module, __filename, __dirname) {
console.log(arguments.callee.toString())
}
所有用户编写的代码都自动封装一个函数中,函数有五个参数,我们就可以在函数内部使用五个实参
五个参数
-
exports
暴露对象,可以将模块中的数据暴露给引入的地方 -
require
引入模块的函数,用于在一个模块中引入另外一个模块,并且将子模块暴露的数据 赋值给变量 -
module
模块对象包含了模块的所有信息(当前模块信息) -
__filename
当前模块的文件名 -
__dirname
当前模块所在 的路径
3.2. require函数(重点)
require(moduleId)函数用于在当前模块中加载和使用别的模块,传入一个模块名,返回一个模块导出对象
'1':{
[Function: require]
resolve: { [Function: resolve] paths: [Function: paths] },
main:
Module {
id: '.',
exports: {},
parent: null,
filename: 'C:\\Users\\HiWin10\\Desktop\\study\\app.js',
loaded: false,
children: [Array],
paths: [Array] },
extensions:[Object: null prototype] {
'.js': [Function],
'.json': [Function],
'.node': [Function]
},
cache:[Object: null prototype] {
'C:\\Users\\HiWin10\\Desktop\\study\\app.js': [Module],
'C:\\Users\\HiWin10\\Desktop\\study\\wu.js': [Module]
}
},
}
./
.表示当前目录,模块文件路径的js后缀可以省略
var foo = require('./foo'); // 当前目录下面的foo.js文件
var foo2 = require('./foo.js')
导入如果不加后缀名 ,会先找js文件,如果没有js文件 会找json文件
注意:
- 引入模块文件有语法错误时会报错
- 引入模块不存在时会报错
- 重复引入模块只会执行一次(返回值会被缓存起来)
- 所有的模块如果没有返回值,导入的模块中将返回空对象
- 导入自定义模块必须加'./' 因为在node.js中查找 模块默认是在node_modules目录中查找的
- 如果引入第三方模块,直接写模块的名字就可以了
require("jquery")
1. 主模块main
require有一个属性main来确定当前模块是不是主模块,应该应用程序中, 通过node启动的模块就是应用模块,也叫主模块
console.log(module == require.main) // true表示当前模块是主模块, false表示当前模块不是主模块
2. require.resolve() 方法获取绝对路径
require.resolve()方法 传入一个相对路径参数,返回拼接后的绝对路径
console.log(require.resolve("./module"))
3. require.cache 属性
require.cache属性值是一个对象,缓存了所有已经被加载的模块
console.log(require.cache)
3.3. exports 导出对象
exports 对象是当前模块的导出对象,用于导出模块共有的方法和属性,别的模块在通过require函数导入使用当前模块时就会获得当前模块的exports 对象
ps:例子
exports.hello = function(){
console.log("hello World")
}
注意事项:
- exports 是module.exports对象的引用
- exports 不能改指向,只能添加属性和方法
如果希望更改暴露指向,那么我 们就需要使用modeule.exports进行暴露
var aa = function(){
console.log(11)
}
module.exports = aa;
3.4. module 模块对象(重中之重)
'2':
Module {
id: '.', // 模块的id ,模块的名称, .代表主模块(其他模块,id就是模块的文件路径)
exports: {}, // 当前模块导出的内容(重点,其他了解)
parent: null, // 引入当前模块的父模块,
path: '' , // 当前文件夹的例子
filename: 'C:\\Users\\HiWin10\\Desktop\\study\\app.js', //当前模块文件路径
loaded: false,
children: [ [Module] ], // 子模块
paths:[ // 默认模块查找路径,当前目录找不到,就当上一层文件夹找
'C:\\Users\\HiWin10\\Desktop\\study\\node_modules',
'C:\\Users\\HiWin10\\Desktop\\node_modules',
'C:\\Users\\HiWin10\\node_modules',
'C:\\Users\\node_modules',
'C:\\node_modules'
]
},
module对象可以访问到当前模块的一些相关信息,但最多的用途是替换当前模块导出的对象
module.exports = {}
例如默认的导出对象是一个对象,我们可以改变,让导出对象为普通的函数或其他数据类型的值
module.exports = function(){
console.log('hello world')
}
module.exports 真正的暴露对象,exports只是对module.exports的引用
3.5. 模块初始化
每一个模块中的js代码尽在模块第一次使用时执行一次,并在执行过程中初始化模块的导出对象,之后,会讲导出对象缓存起来,被重复利用
4. NodeJS 作用域
4.1. 作用域
作用域: 规定一个变量和函数可以使用的范围,作用域分为两种,全局作用域和局部作用域
在Node中,每一个js文件都是单独的作用域,因为node的模块化会将每一个文件中的代码封装在一个函数内部,这和浏览器开发不同,浏览器中,var a,此时a会自动成为window的属性,此时js文件和js文件共享作用域,但是Node.js中,每个js文件是独立的作用域不共享,所以Node中每一个文件中的变量都是私有的.
比如我们编写一下代码
var wu = 10
NodeJS会在执行之前,编译这个模块为
function (exports,require,module,__filename,__dirname){
var wu = 10
}
4.2. 暴露(导出)
所以,如果让其他作用域使用本作用域中的值呢.我们采用暴露的方式将值抛出去: 也就是暴露共享的数据
暴露数据
module.exports = {wuwei: 10}
使用exports暴露
// test.js
var a = 100;
exports.a = a;
// exports是一个对象,给这个对象添加了一个属性a,a的值就是本作用域的变量值
此时当我们通过require引入test.js的时候,就可以拿到这个属性啊的值
// b.js
var test = require('./test.js');
// 此时text是引入的对象,就是exports对象,所以如果要获取a的值应该用
// test.a
console.log(test.a)
注意,模块叫test文件,定义接受的变量名也叫test.其他名字不会报错,但是我们不会这么做
使用module.exports 暴露数据
刚才我们使用了exports像外暴露一些值,但是很不方便,这个东西还必须是exports的属性,require()导入的时候,返回的是一个对象我们还得去对象里那属性和方法,如果仅仅只能暴露一个东西,我希望向外暴露一个类,此时就很不方便
// 到处类 Person.js
function Person(name,age,sex){
this.name = name;
this.age = age;
this.sex = sex;
}
exports.Person = Person;
导入
var Person = require(./Person.js);
// 此时的Person是exports对象,如果想使用这个类,得Person.Person()使用
var xiaoming = new Person.Person('小明',12,'男')
此时就会发现使用很不方便
如果js里仅仅只暴露一个东西,我们可以使用module.exports来暴露
这样module.export暴露的是什么,那么require()导入返回的就是什么
module.exports暴露一个东西,exports暴露多个东西
// 到处类 Person.js
function Person(name,age,sex){
this.name = name;
this.age = age;
this.sex = sex;
}
module.exports = Person;
导入
var Person = require(./Person.js);
// 如果采用抵module.exports暴露的,这里Person就是暴露的那个类,所以饿哦们可以直接使用
var xiaoming = new Person('小明',12,'男')
这样在使用的时候就方便很多
4.3. 定义为全局变量
var username = 'wuwei
global.name = username
使用时可以不写global
console.log(name)
5. 模块的共性
所有的模块化都有一个共性,模块的功能都是把代码放到一个独立的函数中
- 模块中使用的var 定义变量都是局部变量
- 模块中定义的函数也是局部的
- 模块有一个模块对象,包含moduleName(模块名),exports(导出对象)
- 如果模块中需要 暴露方法或者属性给外部使用,那么就是像exports对象上添加
- 导入一个模块使用require('moduleName'),改方法返回的是模块对象的exports对象
var aa = require('./a.js')
6. 关于路径与后缀名情况
/ 表示绝对路径
./ 表示当前路径
如果在引入模块是不传入后缀名, 会一次找 后缀名为js, json,都找不到就报错
如果不写路径则认为是内置模块或各级node_modules文件夹中的第三方模块