一、前言
为什么讲node模块会突然带上commonJS,node已经把commonJS纳入到自身的规范里面了,node里面的模块思想就是用了commonJS,所以这里还会讲到一些commonJS的内容。
文章可能会有点长。如果只是想查看最常用的情况的小伙伴直接查看常用点
二、Node模块分类
首先我们先来给Node简单解释下分类有哪些? 三种分类:内置模块(核心模块)、第三方模块、自定义模块
内置模块
Node.js内置模块(核心模块)就是Node自带的模块,只要安装了Node.js就可以直接使用。
常用内置模块
- url
- querystring
- http/https
- Events
- fs
- Stream
- Zlib
- ReadLine
1. url
url模块主要是用来处理请求地址(url)的方法集合
url提供了三个方法,分别是url.parse(); url.format(); url.resolve();
想知道url更多的方法和属性,可以直接把url打印出来看
const url = require('url')
console.dir({url})
/*
{
url: {
Url: [Function: Url],
parse: [Function: urlParse],
resolve: [Function: urlResolve],
resolveObject: [Function: urlResolveObject],
format: [Function: urlFormat],
URL: [class URL],
URLSearchParams: [class URLSearchParams],
domainToASCII: [Function: domainToASCII],
domainToUnicode: [Function: domainToUnicode],
pathToFileURL: [Function: pathToFileURL],
fileURLToPath: [Function: fileURLToPath]
}
}
*/
常用点 - 1.1 parse
url.parse(urlString,boolean,boolean)
parse这个方法可以将一个url的字符串解析并返回一个url的对象
参数介绍:
urlString 指传入一个url地址的字符串
第二个参数(可选)传入一个布尔值,默认为false,为true时,返回的url对象中,query的属性为一个对象。
第三个参数(可省)为 true,则 // 之后至下一个 / 之前的字符串会被解析作为 host。 例如,//foo/bar 会被解析为 {host: 'foo', pathname: '/bar'} 而不是 {pathname: '//foo/bar'}。 默认为 false
const url = require('url')
url.parse("http://www.baidu.com:8080/a/b/c?query=string#hash");
/*
返回值:
Url {
protocol: 'http:',
slashes: true,
auth: null,
host: 'www.baidu.com:8080',
port: '8080',
hostname: 'www.baidu.com',
hash: '#hash',
search: '?query=string',
query: 'query=string',
pathname: '/a/b/c',
path: '/a/b/c?query=string',
href: 'http://www.baidu.com:8080/a/b/c?query=string#hash'
}
没有设置第二个参数为true时,query属性为一个字符串类型
*/
// 处理get请求最常用方式
url.parse("http://www.baidu.com:8080/a/b/c?query=string#hash",true);
/*
Url {
protocol: 'http:',
slashes: true,
auth: null,
host: 'www.baidu.com:8080',
port: '8080',
hostname: 'www.baidu.com',
hash: '#hash',
search: '?query=string',
query: [Object: null prototype] { query: 'string' }, //差别在这里
pathname: '/a/b/c',
path: '/a/b/c?query=string',
href: 'http://www.baidu.com:8080/a/b/c?query=string#hash'
}
*/
1.2. format
url.format(urlObj)
format这个方法是将传入的url对象编程一个url字符串并返回
参数介绍:
urlObj是url对象,例如:
url.format({
protocol:"http:",
host:"www.baidu.com:60",
port:"60"
});
/*
返回值:
'http://www.baidu.com:60'
*/
上面的urlObj只是一个简单的url对象,完整的url对象其实还有其他参数的,例如:
const url = require('url')
const urlObject = {
protocol: 'https:',
slashes: true,
auth: null,
host: 'www.baidu.com:443',
port: '443',
hostname: 'www.baidu.com',
hash: '#tag=110',
search: '?id=8&name=mouse',
query: { id: '8', name: 'mouse' },
pathname: '/ad/index.html',
path: '/ad/index.html?id=8&name=mouse',
href: 'https://www.baidu.com:443/ad/index.html?id=8&name=mouse#tag=110'
}
const parsedObj = url.format(urlObject)
console.log(parsedObj)
/*
输出
https://www.baidu.com:443/ad/index.html?id=8&name=mouse#tag=110
*/
1.3. resolve
resolve把url和path拼接起来
resolve(url,path)
const url = require('url')
url.resolve('http://example.com/', '/one')
// http://example.com/one
url.resolve('http://example.com/one', '/two')
// http://example.com/two
2. querystring
querystring从字面上的意思就是查询字符串,一般是对http请求所带的数据进行解析(格式化处理等)
常用点 - 2.1 parse
querystring.parse(str[, sep[, eq[, options]]])
const querystring = require('querystring')
var qs = 'x=3&y=4'
var parsed = querystring.parse(qs)
console.log(parsed)
/*
输出
[Object: null prototype] { x: '3', y: '4' }
*/
常用点 - 2.2 stringify
querystring.stringify(obj[, sep[, eq[, options]]])
const querystring = require('querystring')
var qo = {
x: 3,
y: 4
}
var parsed = querystring.stringify(qo)
console.log(parsed)
/*
输出
x=3&y=4
*/
2.3 escape/unescape
函数可对字符串进行编码/解码,这样就可以在所有的计算机上读取该字符串
querystring.escape(str) 对字符串进行编码
/*
使用场景:当你的参数包含各种特殊字符的时候,传值有可能被截断,
所以我们可以通过转义编码成一个长字符串进行存储和传输
*/
const querystring = require('querystring')
var str = 'id=3&city=北京&url=https://www.baidu.com'
var escaped = querystring.escape(str)
console.log(escaped)
/*
输出
id%3D3%26city%3D%E5%8C%97%E4%BA%AC%26url%3Dhttps%3A%2F%2Fwww.baidu.com
*/
querystring.unescape(str)
对字符串进行解码
const querystring = require('querystring')
var str = 'id%3D3%26city%3D%E5%8C%97%E4%BA%AC%26url%3Dhttps%3A%2F%2Fwww.baidu.com'
var unescaped = querystring.unescape(str)
console.log(unescaped)
/*
输出
id=3&city=北京&url=https://www.baidu.com
*/
3. http/https(网络请求)
HTTP和HTTPS都是内置模块。 使用这些模块,可以轻松地发出HTTP请求,而无需安装外部软件包。 但是,这些是低级模块,与其他解决方案相比,它们不是很友好。后面会详细讲有哪些友好的请求三方模块(Request)。
老规矩,我们还是先把http打印出来(http和https用法一毛一样,这里就只拿http来做讲解了)
const http = require('http')
console.dir({http})
/*
输出
{
http: {
_connectionListener: [Function: connectionListener],
METHODS: ['DELETE', 'GET','POST','PUT',...],
STATUS_CODES: {各类状态码},
Agent: [Function: Agent] { defaultMaxSockets: Infinity },
ClientRequest: [Function: ClientRequest],
IncomingMessage: [Function: IncomingMessage],
OutgoingMessage: [Function: OutgoingMessage],
Server: [Function: Server],
ServerResponse: [Function: ServerResponse],
createServer: [Function: createServer],
validateHeaderName: [Function: hidden],
validateHeaderValue: [Function: hidden],
get: [Function: get],
request: [Function: request],
maxHeaderSize: [Getter],
globalAgent: [Getter/Setter]
}
}
*/
打印出来的内容有点多,接下来我们来讲解常用的方法和属性,官方文档
3.1 http.createServer
创建一个http服务,返回 http.Server 的新实例。
http.createServer([options][, requestListener])
使用该函数创建一个http服务器,并将requestListener作为rrequest事件的监听函数传入。 具体使用我们连着下面的请求方法一起演示
3.2 http.get
发起get请求
var http = require('http')
var https = require('https')
// 将http.createServer返回来的http.serve新实例赋值给server,方便后面执行实例方法
const server = http.createServer((request, response) => {
var url = request.url.substr(1)
var data = ''
response.writeHeader(200, {
'content-type': 'application/json;charset=utf-8',
'Access-Control-Allow-Origin': '*'
})
https.get(`https://m.lagou.com/listmore.json${url}`, (res) => {
res.on('data', (chunk) => {
data += chunk
})
res.on('end', () => {
response.end(JSON.stringify({
ret: true,
data
}))
})
})
})
// 新实例调用listen监听函数
server.listen(8080, () => {
console.log('localhost:8080')
})
3.3 http.post
const https = require('https')
const querystring = require('querystring')
const postData = querystring.stringify({
province: '上海',
city: '上海',
district: '宝山区',
address: '同济支路199号智慧七立方3号楼2-4层',
latitude: 43.0,
longitude: 160.0,
message: '求购一条小鱼',
contact: '13666666',
type: 'sell',
time: 1571217561
})
const options = {
protocol: 'https:',
hostname: 'ik9hkddr.qcloud.la',
method: 'POST',
port: 443,
path: '/index.php/trade/add_item',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': Buffer.byteLength(postData)
}
}
function doPost() {
let data
let req = https.request(options, (res) => {
res.on('data', chunk => data += chunk)
res.on('end', () => {
console.log(data)
})
})
req.write(postData)
req.end()
}
// setInterval(() => {
// doPost()
// }, 1000)
3.4 处理跨域请求: jsonp
我们先来介绍下什么是jsonp,首先我们要来了解一个点,jsonp它不是一个ajax请求,它是利用浏览器端请求js不跨域的特性,什么是请求js不跨域特性,举例:a.com 请求了 b.com的一个js文件,资源之间的请求,不受同源策略限制。
//前端请求,假设前端本地服务是localhost:3000, 服务端是localhost:8080
<script>
function getData() {
console.log(data)
}
</script>
//这里通过script去请求,那么请求回来浏览器就会当成一个js去执行,后端返回什么js代码,浏览器就执行什么代码
<script src="localhost:8080/alert"></script>
<script src="localhost:8080/api/user?cb=getData"></script>
//服务端
const http = require('http')
const url = require('url')
const app = http.createServer((req, res) => {
let urlObj = url.parse(req.url, true)
switch (urlObj.pathname) {
case '/api/user':
//res.end('getData("hello")')
res.end(`${urlObj.query.cb}({"name": "gp145"})`)
break
case '/alert'
res.write('alert(“执行一个alert”)')
default:
res.end('404.')
break
}
})
app.listen(8080, () => {
console.log('localhost:8080')
})
3.5 CORS跨域
const http = require('http')
const url = require('url')
const querystring = require('querystring')
const app = http.createServer((req, res) => {
let data = ''
let urlObj = url.parse(req.url, true)
res.writeHead(200, {
'content-type': 'application/json;charset=utf-8',
'Access-Control-Allow-Origin': '*'
})
req.on('data', (chunk) => {
data += chunk
})
req.on('end', () => {
responseResult(querystring.parse(data))
})
function responseResult(data) {
switch (urlObj.pathname) {
case '/api/login':
res.end(JSON.stringify({
message: data
}))
break
default:
res.end('404.')
break
}
}
})
app.listen(8080, () => {
console.log('localhost:8080')
})
3.6 middleware 跨域 (http-proxy-middware)
通过middleware去处理跨域请求,其实类似前端通过nginx去开启代理一样,不过这个代理呢是通过node去开启了
const http = require('http')
const proxy = require('http-proxy-middleware')
http.createServer((req, res) => {
let url = req.url
res.writeHead(200, {
'Access-Control-Allow-Origin': '*'
})
if (/^/api/.test(url)) {
let apiProxy = proxy('/api', {
target: 'https://m.lagou.com',
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
})
// http-proy-middleware 在Node.js中使用的方法
apiProxy(req, res)
} else {
switch (url) {
case '/index.html':
res.end('index.html')
break
case '/search.html':
res.end('search.html')
break
default:
res.end('[404]page not found.')
}
}
}).listen(8080)
4. Events(事件)
- Events模块是Node对“发布/订阅”模式(publish/subscribe)的实现。一个对象通过这个模块,向另一个对象传递消息。
- 该模块通过EventEmitter属性,提供了一个构造函数。该构造函数的实例具有on方法,可以用来监听指定事件,并触发回调函数。
- 任意对象都可以发布指定事件,被EventEmitter实例的on方法监听到。
接下来我们通过一个实例来讲解,先建立一个消息中心,然后通过on方法,为各种事件指定回调函数,从而将程序转为事件驱动型,各个模块之间通过事件联系。
// 写法一:
const EventEmitter = require('events').EventEmitter;
const event = new EventEmitter();
event.on('someEvent', function () {
console.log('event has occured');
});
function f() {
console.log('start');
event.emit('someEvent');
console.log('end');
}
f()
// 写法二(建议):
const EventEmitter = require('events')
class MyEventEmitter extends EventEmitter {}
const event = new MyEventEmitter()
event.on('play', (val) => {
console.log(val)
})
event.emit('play', '123')
event.emit('play', '456')
常用小知识点: 更多其他时间点我,点我,点我,就可以看到了
Node.js允许同一个事件最多可以绑定10个回调函数,超过10个回调函数,会发出一个警告。这个门槛值可以通过setMaxListeners方法改变
event.setMaxListeners(20);
- Events模块默认支持两个事件:
newListener事件:添加新的回调函数时触发
removeListener事件:移除回调时触发
event.on("newListener", function (evtName){
console.log("New Listener: " + evtName);
});
event.on("removeListener", function (evtName){
console.log("Removed Listener: " + evtName);
});
function foo (){}
event.on("save-user", foo);
event.removeListener("save-user", foo);
- 除了可以通过
on绑定事件以外,还可以通过once绑定,但是回调函数只会触发一次
const EventEmitter = require('events').EventEmitter;
const myEmitter = new EventEmitter;
myEmitter.once('message', function(msg){
console.log('message: ' + msg);
});
myEmitter.emit('message', 'this is the first message');
myEmitter.emit('message', 'this is the second message');
myEmitter.emit('message', 'welcome to nodejs');
/*
上面代码触发了三次`message`事件,但是回调函数只会在第一次调用时运行。\
下面代码指定,一旦服务器连通,只调用一次的回调函数。
*/
5. 常用点 - fs(File System,文件操作)
文件操作一系列方法
const fs = require('fs')
const fsP = require('fs').promises
// 创建文件夹
fs.mkdir('./logs', (err) => {
console.log('done.')
})
// 文件夹改名
fs.rename('./logs', './log', () => {
console.log('done')
})
// 删除文件夹
fs.rmdir('./log', () => {
console.log('done.')
})
// 写内容到文件里
fs.writeFile(
'./logs/log1.txt',
'hello',
// 错误优先的回调函数
(err) => {
if (err) {
console.log(err.message)
} else {
console.log('文件创建成功')
}
}
)
// 给文件追加内容
fs.appendFile('./logs/log1.txt', '\nworld', () => {
console.log('done.')
})
// 读取文件内容
fs.readFile('./logs/log1.txt', 'utf-8', (err, data) => {
console.log(data)
})
// 删除文件
fs.unlink('./logs/log1.txt', (err) => {
console.log('done.')
})
// 批量写文件
for (var i = 0; i < 10; i++) {
fs.writeFile(`./logs/log-${i}.txt`, `log-${i}`, (err) => {
console.log('done.')
})
}
// 读取文件/目录信息
fs.readdir('./', (err, data) => {
data.forEach((value, index) => {
fs.stat(`./${value}`, (err, stats) => {
// console.log(value + ':' + stats.size)
console.log(value + ' is ' + (stats.isDirectory() ? 'directory' : 'file'))
})
})
})
// 同步读取文件
try {
const content = fs.readFileSync('./logs/log-1.txt', 'utf-8')
console.log(content)
console.log(0)
} catch (e) {
console.log(e.message)
}
console.log(1)
// 异步读取文件:方法一
fs.readFile('./logs/log-0.txt', 'utf-8', (err, content) => {
console.log(content)
console.log(0)
})
console.log(1)
// 异步读取文件:方法二
fs.readFile('./logs/log-0.txt', 'utf-8').then(result => {
console.log(result)
})
// 异步读取文件:方法三
function getFile() {
return new Promise((resolve) => {
fs.readFile('./logs/log-0.txt', 'utf-8', (err, data) => {
resolve(data)
})
})
}
;(async () => {
console.log(await getFile())
})()
// 异步读取文件:方法四
const fsp = fsP.readFile('./logs/log-1.txt', 'utf-8').then((result) => {
console.log(result)
})
console.log(fsP)
// watch 监测文件变化
fs.watch('./logs/log-0.txt', () => {
console.log(0)
})
6. Stream(流)
Stream 是一个抽象接口,Node 中有很多对象实现了这个接口。例如,对http 服务器发起请求的request 对象就是一个 Stream,还有stdout(标准输出)。
const fs = require('fs')
const readstream = fs.createReadStream('./note.txt')
const writestream = fs.createWriteStream('./note2.txt')
writestream.write(readstream)
Node.js,Stream 有四种流类型:
- Readable - 可读操作。
- Writable - 可写操作。
- Duplex - 可读可写操作.
- Transform - 操作被写入数据,然后读出结果。
所有的 Stream 对象都是 EventEmitter 的实例。常用的事件有:
- data - 当有数据可读时触发。
- end - 没有更多的数据可读时触发。
- error - 在接收和写入过程中发生错误时触发。
- finish - 所有数据已被写入到底层系统时触发。
const fs = require("fs");
const data = '';
// 创建可读流
var readerStream = fs.createReadStream('input.txt');
// 设置编码为 utf8。
readerStream.setEncoding('UTF8');
// 处理流事件 --> data, end, and error
readerStream.on('data', function(chunk) {
data += chunk;
});
readerStream.on('end',function(){
console.log(data);
});
readerStream.on('error', function(err){
console.log(err.stack);
});
console.log("程序执行完毕");
7. zlib(压缩解压操作)
- 通过创建转换流,对文件进行压缩和解压
const fs = require('fs');
const zlib = require('zlib');
const path = require('path');
function gzip($src) {
fs.stat($src, function (err, stats) {
if (stats.isFile()) {
let rs = fs.createReadStream($src);
//zlib.createGzip()创建一个gzip转换流,是一个可读可写流。
//通过管道将数据读取出来写入gzip流,然后又通过管道写入一个文件流中
$dst = path.join(__dirname, path.basename($src) + '.gz');
rs.pipe(zlib.createGzip()).pipe(fs.createWriteStream($dst));
}
});
}
function ungzip($src) {
fs.stat($src, function (err, stats) {
if (stats.isFile()) {
let rs = fs.createReadStream($src);
//zlib.createGunzip()创建一个gunzip转换流
$dst = path.join(__dirname, path.basename($src, '.gz'));
rs.pipe(zlib.createGunzip()).pipe(fs.createWriteStream($dst));
}
});
}
//压缩文件
gzip('./1.txt');
//解压文件
ungzip('./1.txt.gz');
- 调用 zlib 方法对数据进行压缩与解压
const zlib = require('zlib');
let data = 'hello,world';
//参数一表示要压缩的数据,可以是string或buffer
zlib.gzip(data, function (err, buffer) {
if (err) {
console.log(err);
}
//buffer就是压缩后的数据
console.log(buffer.toString());
//对buffer数据进行解压
zlib.unzip(buffer, function (err, buffer) {
console.log(buffer.toString());
});
});
8. readline(逐行读取)
require('readline') 模块提供了一个接口,用于从可读流(如 process.stdin)读取数据,每次读取一行。 它可以通过以下方式使用:
const readline = require('readline')
//createInterface 里是一个固定的写法
const rl = readline.createInterface({
input:process.stdin,
output:process.stdout
})
rl.question('你输入一句话试试?',(answer) => {
console.log('你说的话是:'+ anwser)
rl.close()
})
常用第三方模块
什么是第三方模块?第三方的Node.js模块指的是为了实现某些功能,发布的npmjs.org上的模块,按照一定的开源协议供社群使用。
常见的第三方模块
- router(路由)
- request
- Mysql
- 等待补充。。。 在日常开发中,我们使用了框架,koa2、Express、egg.js等已经帮我们继承了一部分第三方模块的功能了
三、commomJS规范
在前端开发中目前用的最多的是es6中的模块化 export 与 import
但是在Node环境中,采用 CommonJS 模块规范。一个.js文件就称之为一个模块(module),有自己的作用域。在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见。Node 应用由模块组成
1.模块化的使用
commomJS模块化,是通过exports或者module.exports暴露属性或者方法,通过require引入使用。 假设我们开发一个"a"的模块,a.js
const name = 'aaaaa'
const sayName = ()=> {
console.log(name)
}
console.log('module a')
// 接口暴露方法一:
module.exports = {
say: sayName
}
// 接口暴露方法二:
exports.say = sayName;
//错误演示
exports = {
say: sayName
}
在main.js里面使用模块
main.js
const a = require('./a');
a.say();