NodeJs 采用模塊化方式,管理和組織代碼,NodeJS的所有功能都存在每個(gè)模塊中的
1. 模塊和模塊化開發(fā)的了解
1.1 什么是模塊
模塊: 一個(gè)具有特定功能的文件就是一個(gè)模塊
模塊的優(yōu)點(diǎn): 有了模塊,我們就可以非常方式使用這些模塊,因?yàn)槟K總是完成了特定的功能,如果要修改模塊中個(gè)功能,那么只需要修改這個(gè)自己的模塊文件即可,模塊獨(dú)立于每一個(gè)文件中,不影響其他模塊的代碼
模塊之間相互獨(dú)立,如果一個(gè)模塊引入另一模塊,要使用里面的值,我們就需要在被引入的模塊中暴露這些值.
1.2 什么是模塊化
模塊化: 將一個(gè)復(fù)雜的程序依據(jù)一定的規(guī)則(規(guī)范)封裝成幾個(gè)模塊(文件),并進(jìn)行組合在一起,每個(gè)模塊內(nèi)部數(shù)據(jù)實(shí)現(xiàn)私有的, 只是向外部暴露一些接口(方法)與外部其他模塊通信
按照模塊化思考開發(fā)的項(xiàng)目, 也被稱為模塊化開發(fā).
模塊化的進(jìn)化
// 全局開發(fā)模式
// 最早期所有的js代碼卸載一個(gè)js文件中
function foo(){}
function bar(){}
// 造成的問題,就是代碼量過大以后,Global全局被污染,很容易導(dǎo)致命名沖突
后來對(duì)代碼進(jìn)行簡(jiǎn)單的封裝
// 簡(jiǎn)單封裝: Namespac 模式, 就是我們俗稱的命名空間
var Namespac = {
foo: function(){},
bar: function(){}
}
// 減少Global上的變量數(shù)目
// 本質(zhì)就是對(duì)象,不太安全
因?yàn)楹?jiǎn)單封裝的不安全,又出現(xiàn)了IIFE模式
// 匿名閉包: IIFE模式
var Module = (function(){
var foo = function(){
}
return {
foo: foo
}
})()
Module.foo()
// 函數(shù)是JavaScript 的Local Scope
有的時(shí)候,我一個(gè)模塊可能還需要依賴其他的模塊,我們就需要在升級(jí)一下,將依賴的模塊注入
// 增強(qiáng)封裝, 引入依賴
var Module = (function($){
var $body = $(body);
var foo = function(){
}
return {
foo:foo
}
})($)
// 這就是模塊模式,也是現(xiàn)代模塊實(shí)現(xiàn)的基石
1.3 為什么要模塊化
- 降低復(fù)雜度
- 提高解耦性
- 部署方便
1.4 模塊化的好處
- 避免命名沖突(減少命名空間的污染)
- 更好的分類,按需加載
- 更高的復(fù)用性
- 高可維護(hù)性
2. CommonJS 規(guī)范
CommonJS規(guī)范為JavaScript制定了一個(gè)美好的愿景-希望JavaScript能夠在任何地方運(yùn)行
2.1模塊規(guī)范分類:
CommonJS(NodeJS)
AMD
CMD
ESModule (ES模塊化)
2.2. CommenJS 規(guī)范了解
CommonJS規(guī)范涵蓋了模塊,二進(jìn)制,Buffer,字符集編碼,I/O流, 進(jìn)制環(huán)境, 文件系統(tǒng),單元測(cè)試,包管理等
NodeJS借鑒CommonJs的規(guī)范來實(shí)現(xiàn)了一套易用的模塊系統(tǒng).NPM對(duì)Package規(guī)范的完好支持是的Node應(yīng)用在開發(fā)過程中事半功倍.
CommonJS 規(guī)范 :不是一個(gè)語言,也不是一個(gè)平臺(tái),它只是一個(gè)標(biāo)準(zhǔn),主要是對(duì)原有的JavaScript標(biāo)準(zhǔn)API進(jìn)行了增強(qiáng).
3. CommonJS 模塊規(guī)范
CommonJS對(duì)模塊的定義十分簡(jiǎn)單,主要分為模塊的引用, 模塊的定義, 模塊的標(biāo)識(shí)3個(gè)部分
3.1 模塊的引用
在CommonJS規(guī)范中, 存在require()方法,這個(gè)方法接受模塊標(biāo)識(shí), 以此引用一個(gè)模塊的API到當(dāng)前上下文中
const math = require('math')
require(moduleId)函數(shù)用于在當(dāng)前模塊中加載和使用別的模塊,傳入一個(gè)模塊標(biāo)識(shí),返回一個(gè)模塊導(dǎo)出對(duì)象
'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]
}
},
}
./
.表示當(dāng)前目錄,模塊文件路徑的js后綴可以省略
var foo = require('./foo'); // 當(dāng)前目錄下面的foo.js文件
var foo2 = require('./foo.js')
導(dǎo)入如果不加后綴名 ,會(huì)先找js文件,如果沒有js文件 會(huì)找json文件
注意:
- 引入模塊文件有語法錯(cuò)誤時(shí)會(huì)報(bào)錯(cuò)
- 引入模塊不存在時(shí)會(huì)報(bào)錯(cuò)
- 重復(fù)引入模塊只會(huì)執(zhí)行一次(返回值會(huì)被緩存起來)
- 所有的模塊如果沒有返回值,導(dǎo)入的模塊中將返回空對(duì)象
- 導(dǎo)入自定義模塊必須加'./' 因?yàn)樵趎ode.js中查找 模塊默認(rèn)是在node_modules目錄中查找的
- 如果引入第三方模塊,直接寫模塊的名字就可以了
require("jquery")
3.1.1 主模塊main
require有一個(gè)屬性main來確定當(dāng)前模塊是不是主模塊,應(yīng)該應(yīng)用程序中, 通過node啟動(dòng)的模塊就是應(yīng)用模塊,也叫主模塊
console.log(module == require.main) // true表示當(dāng)前模塊是主模塊, false表示當(dāng)前模塊不是主模塊
3.1.2 require.resolve() 方法獲取絕對(duì)路徑
require.resolve()方法 傳入一個(gè)相對(duì)路徑參數(shù),返回拼接后的絕對(duì)路徑
console.log(require.resolve("./module"))
3.1.3 require.cache 屬性
require.cache屬性值是一個(gè)對(duì)象,緩存了所有已經(jīng)被加載的模塊
console.log(require.cache)
3.1.4 require.extensions屬性
require.extensions屬性值是一個(gè)對(duì)象,用于引入路徑省略后綴名時(shí),按照extension標(biāo)識(shí)的后綴查找
console.log(require.extensions)
3.2 模塊的定義
在模塊上下文提供了exports對(duì)象用于導(dǎo)出當(dāng)前模塊的方法或者變量, 并且它是唯一導(dǎo)出出口, 在模塊中還存在一個(gè)module對(duì)象, 它代表模塊自身.而exports是module的屬性.
3.2.1 查看導(dǎo)出對(duì)象
在模塊中通過打印當(dāng)前模塊的信息來查看module對(duì)象與exports對(duì)象
console.log(arguments)
console.log(arguments.callee.toString); // 打印函數(shù)體
打印的arguments對(duì)象
[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'
}
打印的函數(shù)體
function (exports, require, module, __filename, __dirname) {
console.log(arguments.callee.toString())
}
所有用戶編寫的代碼都自動(dòng)封裝一個(gè)函數(shù)中,函數(shù)有五個(gè)參數(shù),我們就可以在函數(shù)內(nèi)部使用五個(gè)實(shí)參
模塊中的五個(gè)參數(shù)
-
exports
暴露對(duì)象,可以將模塊中的數(shù)據(jù)暴露給引入的地方 -
require
引入模塊的函數(shù),用于在一個(gè)模塊中引入另外一個(gè)模塊,并且將子模塊暴露的數(shù)據(jù) 賦值給變量 -
module
模塊對(duì)象包含了模塊的所有信息(當(dāng)前模塊信息) -
__filename
當(dāng)前模塊的文件名 -
__dirname
當(dāng)前模塊所在 的路徑
3.2.2 exports 導(dǎo)出對(duì)象
exports 對(duì)象是當(dāng)前模塊的導(dǎo)出對(duì)象,用于導(dǎo)出模塊共有的方法和屬性,別的模塊在通過require函數(shù)導(dǎo)入使用當(dāng)前模塊時(shí)就會(huì)獲得當(dāng)前模塊的exports 對(duì)象
ps:例子
exports.hello = function(){
console.log("hello World")
}
注意事項(xiàng):
- exports 是module.exports對(duì)象的引用
- exports 不能改指向,只能添加屬性和方法
如果希望更改暴露指向,那么我 們就需要使用modeule.exports進(jìn)行暴露
var aa = function(){
console.log(11)
}
module.exports = aa;
3.2.3 module 模塊對(duì)象(重中之重)
'2':
Module {
id: '.', // 模塊的id ,模塊的名稱, .代表主模塊(其他模塊,id就是模塊的文件路徑)
exports: {}, // 當(dāng)前模塊導(dǎo)出的內(nèi)容(重點(diǎn),其他了解)
parent: null, // 引入當(dāng)前模塊的父模塊,
path: '' , // 當(dāng)前文件夾的例子
filename: 'C:\\Users\\HiWin10\\Desktop\\study\\app.js', //當(dāng)前模塊文件路徑
loaded: false,
children: [ [Module] ], // 子模塊
paths:[ // 默認(rèn)模塊查找路徑,當(dāng)前目錄找不到,就當(dāng)上一層文件夾找
'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對(duì)象可以訪問到當(dāng)前模塊的一些相關(guān)信息,但最多的用途是替換當(dāng)前模塊導(dǎo)出的對(duì)象
module.exports = {}
例如默認(rèn)的導(dǎo)出對(duì)象是一個(gè)對(duì)象,我們可以改變,讓導(dǎo)出對(duì)象為普通的函數(shù)或其他數(shù)據(jù)類型的值
module.exports = function(){
console.log('hello world')
}
module.exports 真正的暴露對(duì)象,exports只是對(duì)module.exports的引用
3.3 模塊標(biāo)識(shí)
模塊標(biāo)識(shí)其實(shí)就是傳遞給require()方法的參數(shù).它必須是符合小駝峰命名的字符串, 或者以.,.. 開頭的相對(duì)路徑,或者絕對(duì)路徑
4. CommonJS模塊分類
4.1 模塊的分類
- 系統(tǒng)模塊
NodeJS開發(fā) 團(tuán)隊(duì)已經(jīng)開發(fā)了很多功能模塊,不需要安裝,直接 引入就可以使用 - 第三方模塊
第三模塊必須先安裝在使用
使用包管理工具npm進(jìn)行安裝
第三方模塊在npmjs.org中搜素 - 自定義模塊
自己寫的一個(gè)js文件就是一個(gè)模塊
也有將模塊分成兩種的
- 核心模塊(系統(tǒng)模塊)
- 文件模塊,(包括第三方模塊和自定義模塊)
還有按照模塊功能按照關(guān)系劃分為以下兩類
- 主模塊
主模塊就是被node執(zhí)行的入口模塊,通常喜歡命名app.js main.js index.js
一個(gè)項(xiàng)目只能有一個(gè)主模塊(也叫入口文件,就是整個(gè)項(xiàng)目的啟動(dòng)模塊) - 依賴模塊
4.2 內(nèi)置模塊
NodeJs中的內(nèi)置了很多模塊,可以直接使用require來進(jìn)行引用.國(guó)際慣例,你接受的名字最好和模塊的名字一樣
const http = reqire('http')
如果特別長(zhǎng)可以簡(jiǎn)寫
const qs = require('querystring')
內(nèi)置模塊的引用是無條件的,無路徑的
4.3 自定義模塊
自定義模塊就是自己寫的一個(gè)模塊文件
每一個(gè)js文件都是一個(gè)模塊,Node.js使用commonjs規(guī)范
定義a模塊
// a.js
var str = '這個(gè)是一個(gè)字符串'
exports.str = str; // exports 是導(dǎo)出(也可以叫暴露)對(duì)象
定義b模塊導(dǎo)入a模塊
// b.js
var aa = require('./a.js');
// 此時(shí)aa是a模塊中暴露對(duì)象,如果要使用暴露的字符串str,
console.log(aa.str)
然后通過node執(zhí)行b.js
node b.js
會(huì)發(fā)現(xiàn)require()誰,就會(huì)執(zhí)行誰,會(huì)讓a.js文件執(zhí)行
注意點(diǎn):
- 會(huì)發(fā)現(xiàn)require()誰,就會(huì)執(zhí)行誰,會(huì)讓a.js文件執(zhí)行
- require()引入的模塊如果有異步語句,不會(huì)死等,會(huì)將所有的同步執(zhí)行完畢后在執(zhí)行異步語句
- 如果是多層引用會(huì)先把引入文件里的引入執(zhí)行干凈了
- 如果循環(huán)引用.A引用B,B引入A,Node會(huì)很智能的組織好第二次引入
注意: 每一個(gè)模塊中的js代碼盡在模塊第一次使用時(shí)執(zhí)行一次,并在執(zhí)行過程中初始化模塊的導(dǎo)出對(duì)象,之后,會(huì)將導(dǎo)出對(duì)象緩存起來,被重復(fù)利用
5. NodeJS 作用域
5.1. 作用域
作用域: 規(guī)定一個(gè)變量和函數(shù)可以使用的范圍,作用域分為兩種,全局作用域和局部作用域
在Node中,每一個(gè)js文件都是單獨(dú)的作用域,因?yàn)閚ode的模塊化會(huì)將每一個(gè)文件中的代碼封裝在一個(gè)函數(shù)內(nèi)部,這和瀏覽器開發(fā)不同,瀏覽器中,var a,此時(shí)a會(huì)自動(dòng)成為window的屬性,此時(shí)js文件和js文件共享作用域,但是Node.js中,每個(gè)js文件是獨(dú)立的作用域不共享,所以Node中每一個(gè)文件中的變量都是私有的.
比如我們編寫一下代碼
var num = 10
NodeJS會(huì)在執(zhí)行之前,編譯這個(gè)模塊為
function (exports,require,module,__filename,__dirname){
var num = 10
}
5.2. 暴露(導(dǎo)出)
所以,如果讓其他作用域使用本作用域中的值呢.我們采用暴露的方式將值拋出去: 也就是暴露共享的數(shù)據(jù)
暴露數(shù)據(jù)
module.exports = { num: 10}
使用exports暴露
// test.js
var a = 100;
exports.a = a;
// exports是一個(gè)對(duì)象,給這個(gè)對(duì)象添加了一個(gè)屬性a,a的值就是本作用域的變量值
此時(shí)當(dāng)我們通過require引入test.js的時(shí)候,就可以拿到這個(gè)屬性啊的值
// b.js
var test = require('./test.js');
// 此時(shí)text是引入的對(duì)象,就是exports對(duì)象,所以如果要獲取a的值應(yīng)該用
// test.a
console.log(test.a)
注意,模塊叫test文件,定義接受的變量名也叫test.其他名字不會(huì)報(bào)錯(cuò),但是我們不會(huì)這么做
使用module.exports 暴露數(shù)據(jù)
剛才我們使用了exports像外暴露一些值,但是很不方便,這個(gè)東西還必須是exports的屬性,require()導(dǎo)入的時(shí)候,返回的是一個(gè)對(duì)象我們還得去對(duì)象里那屬性和方法,如果僅僅只能暴露一個(gè)東西,我希望向外暴露一個(gè)類,此時(shí)就很不方便
// 到處類 Person.js
function Person(name,age,sex){
this.name = name;
this.age = age;
this.sex = sex;
}
exports.Person = Person;
導(dǎo)入
var Person = require(./Person.js);
// 此時(shí)的Person是exports對(duì)象,如果想使用這個(gè)類,得Person.Person()使用
var xiaoming = new Person.Person('小明',12,'男')
此時(shí)就會(huì)發(fā)現(xiàn)使用很不方便
如果js里僅僅只暴露一個(gè)東西,我們可以使用module.exports來暴露
這樣module.export暴露的是什么,那么require()導(dǎo)入返回的就是什么
module.exports暴露一個(gè)東西,exports暴露多個(gè)東西
// 到處類 Person.js
function Person(name,age,sex){
this.name = name;
this.age = age;
this.sex = sex;
}
module.exports = Person;
導(dǎo)入
var Person = require(./Person.js);
// 如果采用抵module.exports暴露的,這里Person就是暴露的那個(gè)類,所以餓哦們可以直接使用
var xiaoming = new Person('小明',12,'男')
這樣在使用的時(shí)候就方便很多
5.3. 定義為全局變量
在模塊中通過global全局對(duì)象定義全局?jǐn)?shù)據(jù)
var username = 'wuwei
global.name = username
使用時(shí)可以不寫global
console.log(name)
6. 模塊的共性
所有的模塊化都有一個(gè)共性,模塊的功能都是把代碼放到一個(gè)獨(dú)立的函數(shù)中
- 模塊中使用的var 定義變量都是局部變量
- 模塊中定義的函數(shù)也是局部的
- 模塊有一個(gè)模塊對(duì)象,包含moduleName(模塊名),exports(導(dǎo)出對(duì)象)
- 如果模塊中需要 暴露方法或者屬性給外部使用,那么就是像exports對(duì)象上添加
- 導(dǎo)入一個(gè)模塊使用require('moduleName'),改方法返回的是模塊對(duì)象的exports對(duì)象
var aa = require('./a.js')
7. 關(guān)于路徑與后綴名情況
/ 表示絕對(duì)路徑
./ 表示當(dāng)前路徑
如果在引入模塊是不傳入后綴名, 會(huì)按照.js, .json, .node后綴名順序查找, 都找不到就報(bào)錯(cuò)
如果不寫路徑則認(rèn)為是內(nèi)置模塊或各級(jí)node_modules文件夾中的第三方模塊