🚘🚘🚘Node模块和CommonJS

737 阅读9分钟

一、前言

为什么讲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(网络请求)

HTTPHTTPS都是内置模块。 使用这些模块,可以轻松地发出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();