目录
模块化Module
NodeJs 采用模块化方式,管理和组织代码,NodeJS的所有功能都存在每个模块中.
1.模块的了解
1.1什么是模块
模块:一个具有特定功能的文件就是一个模块
模块的优点: 有了模块,我们就可以非常方式使用这些模块,因为模块总是完成了特定的功能,如果要修改模块中个功能,那么需要修改这个自己的模块文件即可,模块独立于每一个文件中,不影响模块的代码.
模块之间是相互独立的,如果一个模块中引入另一个模块,要用到里面的值,那么必须在被引入的模块中暴露这些值.
1.2 什么是模块化
模块化: 将一个复杂的程序依据一定的规则(规范)封装成几个模块(文件),并进行组合在一起,每个模块内部数据实现私有的, 只是向外部暴露一些接口(方法)与外部其他模块通信
模块的进化
1.全局开发模式
// 全局开发模式
// 最早期所有的js代码卸载一个js文件中
function foo(){}
function add(){}
// 造成的问题,就是代码量过大以后,Global全局被污染,很容易导致命名冲突
所有js代码均在一个js文件中,代码量会很大,很容易导致命名冲突.
2.对代码进行简单的封装
// 简单封装: Namespac 模式, 就是我们俗称的命名空间
var Namespac = {
foo: function(){},
bar: function(){}
}
// 减少Global上的变量数目
// 本质就是对象,不太安全
3.IIFE模式
// 匿名闭包: IIFE模式
var Module = (function(){
var foo = function(){
}
return {
foo: foo
}
})()
Module.foo()
// 函数是JavaScript 的Local Scope
4.模块模式
有时候在一个模块中需要引入其他模块,这就需要我们注入模块.
// 增强封装, 引入依赖
var Module = (function($){
var $body = $(body);
var foo = function(){
}
return {
foo:foo
}
})($)
1.3 模块化的规范
-
CommonJS(NodeJS)
-
AMD
-
CMD
-
ESModule (ES模块化)
在这里我们主要了解commonJS规范,
1.commonJS规范
Node 应用由模块组成,采用 CommonJS 模块规范。
每个文件就是一个模块,有自己的作用域。在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见。
CommonJs是一种规范,NodeJS是对这个规范的实现.
在这个规范中每一个模块都是一个独立的模块.
特点:
在服务器端:模块的加载是运行时同步加载
在浏览器端,模块需要提前编译打包处理
2.模块的分类
2.1 模块的分类
- 系统模块:NodeJS开发 团队已经开发了很多功能模块,不需要卸载,直接 引入就可以使用
- 第三方模块:第三方模仿必须要先安装再使用,要使用包管理工具npm进行安装
- 自定义模块:自己写的一个js文件,在使用了模块化语法后就是一个自定义模块
2.2 内置模块
NodeJs中的内置了很多模块,可以直接使用require来进行引用.国际惯例,你接受的名字最好和模块的名字一样 .
const http = require("http");
如果引入的模块名字特别长也可以简写:
const qs = require('querystring')
内置模块的引用是无条件的,无路径的 .
2.3 自定义模块
自定义模块:自己所写的js文件就是一个模块,Node.js使用commonjs规范 .
定义a模块
let str = "hello World";
console.log(str);
exports.str = str; //exports用来暴露数据,后面会详细介绍到.
定义b模块,并在其中引入a模块
//此时的a是在a.js中暴露的对象
let a = require("./a");
//如果要使用,则必须通过a.str才能访问到
console.log(a.str);
在文件夹中打开Powershell 通过命令 node b.js来执行
会发现require()谁,就会执行谁,会让a.js文件执行
tips:
会发现require()谁,就会执行谁,会让a.js文件执行
require()引入的模块如果有异步语句,不会死等,会将所有的同步执行完毕后在执行异步语句
如果是多层引用会先把引入文件里的引入执行干净了
如果循环引用.A引用B,B引入A,Node会很智能的组织好第二次引入
3. commen 模块化
3.1 commen模块的组成
在模块中打印当前的模块信息
console.log(arguments)
console.log(arguments.callee.toString); // 打印函数体
打印的arguments如下:
[Arguments] {
'0': {},
'1': [Function: require] {
resolve: [Function: resolve] { paths: [Function: paths] },
main: Module {
id: '.',
path: 'C:\\Users\\wuhon\\Desktop\\favorite\\框架\\nodeCode\\day01',
exports: {},
parent: null,
filename: 'C:\\Users\\wuhon\\Desktop\\favorite\\框架\\nodeCode\\day01\\b.js',
loaded: false,
children: [Array],
paths: [Array]
},
extensions: [Object: null prototype] {
'.js': [Function],
'.json': [Function],
'.node': [Function]
},
cache: [Object: null prototype] {
'C:\\Users\\wuhon\\Desktop\\favorite\\框架\\nodeCode\\day01\\b.js': [Module],
'C:\\Users\\wuhon\\Desktop\\favorite\\框架\\nodeCode\\day01\\a.js': [Module]
}
},
'2': Module {
id: '.',
path: 'C:\\Users\\wuhon\\Desktop\\favorite\\框架\\nodeCode\\day01',
exports: {},
parent: null,
filename: 'C:\\Users\\wuhon\\Desktop\\favorite\\框架\\nodeCode\\day01\\b.js',
loaded: false,
children: [ [Module] ],
paths: [
'C:\\Users\\wuhon\\Desktop\\favorite\\框架\\nodeCode\\day01\\node_modules',
'C:\\Users\\wuhon\\Desktop\\favorite\\框架\\nodeCode\\node_modules',
'C:\\Users\\wuhon\\Desktop\\favorite\\框架\\node_modules',
'C:\\Users\\wuhon\\Desktop\\favorite\\node_modules',
'C:\\Users\\wuhon\\Desktop\\node_modules',
'C:\\Users\\wuhon\\node_modules',
'C:\\Users\\node_modules',
'C:\\node_modules'
]
},
'3': 'C:\\Users\\wuhon\\Desktop\\favorite\\框架\\nodeCode\\day01\\b.js',
'4': 'C:\\Users\\wuhon\\Desktop\\favorite\\框架\\nodeCode\\day01'
}
打印出的函数体:
function (exports, require, module, __filename, __dirname) {
console.log(arguments.callee.toString())
}
所有用户编写的代码都自动封装一个函数中,函数有五个参数,我们就可以在函数内部使用五个实参.
五个参数:
- exports: 是一个对象,暴露对象,可将模块中的数据暴露给需要引入的该模块的其他模块;
- require :是一个函数,引入模块的函数,用于在一个模块中引入另外一个模块,并将子模块暴露的数据赋值给变量;
- module : 是一个对象,模块对象包含了模块的所有信息(当前模块信息);
- __filename : 当前文件所在路径;
- __dir :当前文件夹的路径.
3.2 require函数(重要)
require(moduleId)函数用于在当前模块中加载和使用别的模块,传入一个模块名,返回一个模块导出对象
'1': [Function: require] {
resolve: [Function: resolve] { paths: [Function: paths] },
main: Module {
id: '.',
path: 'C:\\Users\\wuhon\\Desktop\\favorite\\框架\\nodeCode\\day01',
exports: {},
parent: null,
filename: 'C:\\Users\\wuhon\\Desktop\\favorite\\框架\\nodeCode\\day01\\b.js',
loaded: false,
children: [Array],
paths: [Array]
},
extensions: [Object: null prototype] {
'.js': [Function],
'.json': [Function],
'.node': [Function]
},
cache: [Object: null prototype] {
'C:\\Users\\wuhon\\Desktop\\favorite\\框架\\nodeCode\\day01\\b.js': [Module],
'C:\\Users\\wuhon\\Desktop\\favorite\\框架\\nodeCode\\day01\\a.js': [Module]
}
},
./表示当前路径,模块文件的后缀名.js课省略.
let foo = require('./foo');
let foo2 = require('./foo.js')
如果省略了后缀名,则会优先找js文件,没有js文件会找json文件,没有json文件则会找node文件.
tips:
引入模块文件有语法错误时会报错
引入模块不存在时会报错
重复引入模块只会执行一次(返回值会被缓存起来)
所有的模块如果没有返回值,导入的模块中将返回空对象
导入自定义模块必须加'./' 因为在node.js中查找 模块默认是在node_modules目录中查找的
如果引入第三方模块,直接写模块的名字就可以了
require("jquery")
第三点第四点详解:
定义模块a:
console.log("a.js");
let num = 5;
//并没有使用exports向外面暴露数据
在模块b中引入模块a,在这里我引入了3次模块a;
let a = require("./a");
let b = require('./a');
let c = require('./a');
console.log(a);
console.log(b);
console.log(c);
执行代码观察执行的结果:
可以看到在没有暴露数据的情况默认会返回空对象,在这里我引入了3次a.js,所以打印出来3个a.js.
而且我们可以看到a.js只被打印了一次,说明虽然我们多次的引入了a.js,但是a.js只执行了一次.后面会直接在缓存区找数据,不会再次执行a.js.
3.3 exports 导出数据
exports 对象是当前模块的导出对象,用于导出模块共有的方法和属性,别的模块在通过require函数导入使用当前模块时就会获得当前模块的exports 对象 .
exports.hello = function(){
console.log("hello World")
}
注意事项:
exports 是module.exports对象的引用
exports 不能改指向,只能添加属性和方法
如果希望更改暴露指向,那么我 们就需要使用modeule.exports进行暴露 .
定义a模块
console.log("a.js");
let num = 5;
exports.num = 5;
定义b模块引入a模块
let a = require("./a");
let b = require('./a');
let c = require('./a');
console.log(a);
console.log(b);
console.log(c);
运行b.js,结果如下:
如果我们使用moudle.exports导出数据,结果则会发生改变:
console.log("a.js");
let num = 5;
exports.num = 5;
module.exports = {};
//使用moudule.exports导出一个空对象
则打印的结果如下:
可以按照下面图所示的理解,实际上导出的数据是module.exports,erports实际上是module.exports对象的引用.当改变module.exports的指向时,exports就失效了.但是可以给exports添加属性和方法.
3.4 模块对象module
'2': Module {
id: '.',
path: 'C:\\Users\\wuhon\\Desktop\\favorite\\框架\\nodeCode\\day01',
exports: {},
parent: null,
filename: 'C:\\Users\\wuhon\\Desktop\\favorite\\框架\\nodeCode\\day01\\b.js',
loaded: false,
children: [ [Module] ],
paths: [
'C:\\Users\\wuhon\\Desktop\\favorite\\框架\\nodeCode\\day01\\node_modules',
'C:\\Users\\wuhon\\Desktop\\favorite\\框架\\nodeCode\\node_modules',
'C:\\Users\\wuhon\\Desktop\\favorite\\框架\\node_modules',
'C:\\Users\\wuhon\\Desktop\\favorite\\node_modules',
'C:\\Users\\wuhon\\Desktop\\node_modules',
'C:\\Users\\wuhon\\node_modules',
'C:\\Users\\node_modules',
'C:\\node_modules'
]
},
module.id
模块的识别符,通常是带有绝对路径的模块文件名。module.filename
模块的文件名,带有绝对路径。module.loaded
返回一个布尔值,表示模块是否已经完成加载。module.parent
返回一个对象,表示调用该模块的模块。module.children
返回一个数组,表示该模块要用到的其他模块。module.exports
表示模块对外输出的值。(重要)
module对象可以访问到当前模块的一些相关信息,但最多的用途是替换当前模块导出的对象
module.exports = {}
例如默认的导出对象是一个对象,我们可以改变,让导出对象为普通的函数或其他数据类型的值
module.exports = function(){
console.log('hello world')
}
module.exports 真正的暴露对象,exports只是对module.exports的引用
4. NodeJS作用域
4.1 作用域
作用域:规定一个变量和函数可以使用的范围,作用域分为两种,全局作用域和局部作用域.
在node中,每一个js文件都是单独的作用域,因为node的模块化会将每一个文件中的代码封装在一个函数内部,这和和DOM浏览器开发不同.在浏览器中,var a,此时a会自动成为window的属性,此时js文件和js文件共享作用域,但是Node.js中,每个js文件是独立的作用域不共享,所以node中每一个文件中的变量都是私有的.
比如我们写了下面的代码:
let a = 10;
Node在执行代码之前,会将我们的代码编译为:
function (exports,require,module,__filename,__dirname){
let a = 10;
}
4.2 暴露数据
如果其他的作用域要使用本作用域中的值,就需要我们将本作用域中的值暴露(导出).
暴露数据
module.exports = {num: 10}
使用exports暴露数据(定义a模块)
// a.js
var a = 100;
exports.a = a;
// exports是一个对象,给这个对象添加了一个属性a,a的值就是本作用域的变量值
定义b模块,在里面引入a.js
// b.js
var aa = require('./test.js');
// 此时text是引入的对象,就是exports对象,所以如果要获取a的值应该用
// aa.a
console.log(aa.a)
使用module.exports暴露数据:
在上面我们使用的是exports来暴露数据,但是你会发现很不方便,通过exports暴露数据的时候,暴露的数据会成为exports的属性,通过require()引用数据的时候,必须通过访问对象的属性才能访问到暴露的值,如果仅仅只能暴露一个东西,我希望向外暴露一个类,此时就很不方便.
如果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;
导入
let Person = require(./Person.js);
// 如果采用抵module.exports暴露的,这里Person就是暴露的那个类,所以饿哦们可以直接使用
var xiaoming = new Person('小明',12,'男')
4.3 定义全局变量
let username = 'lhrlp'
global.name = username
使用时可以不写global
console.log(name)
5.模块的共性
所有的模块化都有一个共性,模块的功能都是把代码放到一个独立的函数中
模块中使用的var 定义变量都是局部变量
模块中定义的函数也是局部的
模块有一个模块对象,包含moduleName(模块名),exports(导出对象)
如果模块中需要 暴露方法或者属性给外部使用,那么就是像exports对象上添加
导入一个模块使用require('moduleName'),改方法返回的是模块对象的exports对象