# Node基础内容

Node.js是运行在服务端的JavaScript,我们可以用它来创建一个自己的服务。

# 安装

下载对应你系统的Node.js版本 Node.js  (opens new window),下载好后点击安装。

# 查看node版本信息
node -v

# 执行

创建一个helle.js文件,输出Hello World。

console.log('Hello World');

以前是在html中进行引入使用,而Node不用引入,我们可以直接打开对应目录,使用node命令来执行此文件,这个文件就会被运行。

# 打开终端运行命令
node helle.js


# >>> Hello World

# 基础模块

在Node环境中,一个.js文件就称之为一个模块,Node为我们提供了很多基础模块,我们可以直接引入或使用对应的模块。

global

基础模块,唯一的全局对象,和Js的window一样,可直接输入。

global.console;


# >>> Console {
#   log: [Function: bound ],
#   info: [Function: bound ],
#   warn: [Function: bound ],
#   error: [Function: bound ],
#   dir: [Function: bound ],
#   time: [Function: bound ],
#   timeEnd: [Function: bound ],
#   trace: [Function: bound trace],
#   assert: [Function: bound ],
#   Console: [Function: Console] }

process Node的进程模块,也可直接输入。

process.version;


# >>> v5.2.0

# fs文件系统

fs 是 file-system的简写,为文件系统模块,负责读写文件,该模块提供了所有文件操作相关的API。此模块还可以在我们的Vue项目中进行应用,比如初始化页面,可以写一个模板,然后使用该模块进行创建,传说中的写个脚本。

读取文件:

// file.js
// 不论同步还是异步,读取大文件将对内存消耗和程序执行速度产生重大影响。
// 在这种情况下,更好的选择是使用流读取文件内容


// 使用require加载fs核心模块
const fs = require('fs')


/**
 * 读取文件,传入参数
 * 文件路径
 * 编码格式
 * 回调函数
 * */ 
fs.readFile('./file.js', 'utf8', function(err, data) {
  if (err) {
    console.error(err);
    return;
  }
  console.log(data);
})


// 同步版本读取,比上面异步先执行
try {
  const data = fs.readFileSync('./path.js', 'utf8');
  console.log(data);
} catch (err) {
  console.error(err);
}

写入文件:

// file.js
// 使用require加载fs核心模块
var fs = require('fs');


const content = 'hello world'
/**
 * @desc 写入文件内容,会覆盖原来内容
 * @param path 文件路径
 * @param content 写人内容
 * @param callback 回调函数
 * */ 
fs.writeFile('./file.js', content, function(err) {
  if (err) console.log(err)
})


// 同步方法
try {
  fs.writeFileSync('./file.js', content);
} catch (err) {
  console.error(err);
}

文件夹操作

// file.js
const fs = require('fs')


/**
 * @desc 创建文件夹
 * @param path 文件路径
 * @param callback 回调函数
 * */ 
fs.mkdir('../file/test', function(err) {
  if (err) console.log(err)
})


// 同步方法
try {
  fs.mkdirSync('../file/test');
} catch (err) {
  console.error(err);
}


/**
 * @desc 读取文件目录,返回一个文件目录数组
 * @param path 文件路径
 * @param callback 回调函数
 * */ 
fs.readdir('../file', function(err, data) {
  if (err) {
    console.log(err)
  }
  console.log(data)
  // [
  //   'appendFile.js',
  //   'file.js',
  //   'folder.js',
  //   'path.js',
  //   'readFile.js',
  //   'writeFile.js'
  // ]
})


// 同步方法
try {
  const dirList = fs.readdirSync('../file')
  console.log(dirList)
} catch (error) {
  console.log(error)
}


/**
 * @desc 充命名文件夹
 * @param path1 源文件
 * @param path2 新路径
 * @param callback 回调函数
 * */ 
fs.rename('../file', '../new', function(err) {
  if (err) {
    console.log(err)
  }
})


// 同步方法
try {
  fs.renameSync('../file')
} catch (error) {
  console.log(error)
}


/**
 * @desc 删除文件夹
 * @param path 文件路径
 * @param callback 回调函数
 * */ 
fs.rmdir('../file',function(err) {
  if (err) {
    console.log(err)
  }
})


// 同步方法
try {
  fs.rmdirSync('../file')
} catch (error) {
  console.log(error)
}


// 要删除包含内容的文件夹,请使用 fs.rm() 和选项 { recursive: true } 以递归方式删除内容。
fs.rm('../file', { recursive: true, force: true }, err => {
  if (err) {
    throw err;
  }
});

后去文件路径:

const path = require('path')


const notes = 'D:\GitProjects\Node\file\\file.js';


console.log(path.dirname(notes)) // D:GitProjectsNode
console.log(path.basename(notes)) // file.js
console.log(path.extname(notes)) // .js

# http模块

http服务模块,可以为我们创建一个简单服务器,我们就可以去搭建一个自己的后端服务了,也就是名副其实的全栈了。

创建一个服务,我们首先要明白一个道理,在浏览器中我们访问的某个服务网址页面,不论是html、js、css、图片还是接口,其都是像服务器发送的一个个请求,浏览器拿到这个请求返回的数据再组装成页面进行加载的。所以,在创建的服务面前,不论是页面路径还是资源请求,都是像服务器进行请求,只不过服务器根据不同的请求路径进行判断,返回给客户端不同的资源。

简单的创建一个http服务:

// http.js


// 1、引入http模块
var http = require('http');


// 2、使用 http.createServer()方法创建一个web服务器
var server = http.createServer();


// 3、注册request请求事件,监听客户端请求
/**
 * 请求回调函数接受两个参数
 * request  请求对象:
 *          请求对象可以用来获取客户端的一些请求信息,如请求路径
 * response 响应对象
 *          响应对象可以用来给客户端发送响应消息
 */
server.on('request', function (request, response) {
  console.log('收到客户端请求');
  console.log(request);
  console.log(response);
  // 像客户端返回响应内容
  // response.setHeader告诉浏览器是什么编码内容
  response.setHeader('Content-Type', 'text/html; charset=utf-8');
  // response返回的响应数据只能是字符串或者二进制,其他的类型都不行
  response.end('<h1>Hello world!</h1>');
})


// 4、绑定端口号,启动服务
server.listen(3000, function () {
  console.log('服务启动成功');
})

执行命令,启动服务

# 打开终端运行命令
node http.js


# >>> 服务启动成功

然后直接在浏览器中输入http://localhost:3000,即可看到服务器响应的内容为Hello world!。 根据不同地址返回不同的内容:

// http.js


var http = require('http');


var server = http.createServer();


server.on('request', function (req, res) {
  console.log(req.url);
  // 通过req.url判断路径处理响应
  if (req.url === '/') {
    var arr = [{
        name: '小苹果',
        age: 18
      },
      {
        name: '小香蕉',
        age: 19
      }
    ]
    res.end(JSON.stringify(arr))
  } else if (req.url === '/a') {
    res.end('a page');
  } else {
    res.end('error page');
  }
})


server.listen(3000, function () {
  console.log('服务启动成功');
})

# 响应文件内容

http启动服务后,客户端发送请求时,我们可以结合fs文件模块读取文件的内容,返回给客户端进行响应。

1、读取文件内容

var http = require('http')


var fs = require('fs')


var server = http.createServer();


server.on('request', function(req, res) {
  var wwwDir = 'D:/Node/resource' // 文件地址
  var url = req.url
  var filePath = '/index.html'
  if (url !== '/') {
    filePath = url
  }


  // 把读取的html文件内容返回给客户端
  fs.readFile(wwwDir + filePath, function(err, data) {
    if (err) {
      return res.end('404 Not Found')
    }
    res.end(data)
  })


})


server.listen(3000, function() {
  console.log('server is running...')
})

2、返回不同类型文件

var http = require('http');
var fs = require('fs');


var server = http.createServer();
// 不同的类型资源对应的Content-Type类型不一样
// 图片不需要指定类型编码,一般只为字符指定编码
// 结合fs模块读取文件内容,发送响应 
server.on('request', function (req, res) {
  var url = req.url;
  // 根据不同路由响应不同文件
  if (url === '/plain') {
    res.setHeader('Content-Type', 'text/plain; charset=utf-8')
    res.end('hello 世界');
  } else if (url === '/') {
    // 读取html类型文件,发送响应
    fs.readFile('../resource/index.html', function (err, data) {
      if (err) {
        res.setHeader('Content-Type', 'text/plain; charset=utf-8');
        res.end('文件读取失败!!!');
      } else {
        res.setHeader('Content-Type', 'text/html; charset=utf-8');
        res.end(data);
      }
    })
  } else if (url === '/img') {
    // 读取静态资源,发送响应
    fs.readFile('../resource/logo.jpg', function (err, data) {
      if (err) {
        res.setHeader('Content-Type', 'text/plain; charset=utf-8');
        res.end('文件读取失败!!!');
      } else {
        res.setHeader('Content-Type', 'image/jpeg;');
        res.end(data);
      }
    })
  }
})


server.listen(3000, function () {
  console.log('server is running...');
})

3、返回列表内容

var http = require('http');
var server = http.createServer();


server.on('request', function(req, res) {
  var arr = [{
      name: '小苹果',
      age: 18
    },
    {
      name: '小香蕉',
      age: 19
    }
  ]
  var content = ''
  // 拼接列表数据
  arr.forEach(function (ele) {
    content += `<span>${ele.name}-${ele.age}</span> </br>`
  })
  var htmlStr = `
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
    </head>
    <body>
      ${content}
    </body>
    </html>
  `
  res.end(htmlStr)
})


server.listen(3000, function () {
  console.log('服务启动成功');
})

4、使用模板引擎 在返回给客户端数据时,可能会发现这和原生渲染一样,动态数据渲染会很麻烦,我们可以借助模板引擎art-template来帮助我们渲染HTMl,和Vue模板语法类似。

基础使用:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>在浏览器中使用模板引擎</title>
</head>
<body>
  <div class="box"></div>
  
  <!-- art-template 不仅可以在浏览器中使用,也可以在node中使用 -->
  <!-- 
    在官网下载art-template.js 引入 
    强调: 模板引擎不关心字符串的内容,只关心自己能认识的模板语法,生成字符串然后再手动加载到HTML中
    tpl也只能是字符串
  -->
  <script src="../resource/art-template.js"></script>
  <script type="text/template" id="tpl">
    hello {{name}}
  </script>
  <script>
    var ret = template('tpl', {
      name: 'jack'
    })
    console.log(ret) // hello jack
    document.getElementsByClassName('box')[0].innerHTML = ret
  </script>
</body>
</html>

在Node中使用art-template模板引擎:

// 安装art-template包
// npm install art-template
  
//  引入
var http = require('http')
var fs = require('fs')
var template = require('art-template')


// 使用
var server = http.createServer();
server.on('request', function(req, res) {
  var htmlStr = `
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>tpl模板</title>
    </head>
    <body>
      <p>大家好,我叫{{name}}</p>
      <p>我今年{{age}}岁</p>
      <p>我喜欢{{hobby}}</p>
    </body>
    </html>
  `
  var htmlRet = template.render(htmlStr, {
    name: 'jack',
    age: 18,
    hobby: 'dance'
  })
  res.end(htmlRet)
})
server.listen(3000, function() {
  console.log('Server is running...')
})

读取html内容:

var http = require('http')
var template = require('art-template')
var fs = require('fs')


var server = http.createServer();
server.on('request', function(req, res) {
  fs.readFile('./tpl.html', function(err, data) {
    if (err) {
     return  res.end('404 Not Fond')
    }
    var htmlStr = data.toString()
    var htmlRet = template.render(htmlStr, {
      name: 'jack',
      age: 18,
      hobby: 'dance'
    })
    res.end(htmlRet)
  })
})
server.listen(3000, function() {
  console.log('Server is running...')
})

把tpl.html文件抽离出来

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>tpl模板</title>
</head>
<body>
  <p>大家好,我叫{{name}}</p>
  <p>我今年{{age}}岁</p>
  <p>我喜欢{{hobby}}</p>
</body>
</html>

从这也就可以理解常说的服务端渲染(SSR)是怎么回事了,在请求页面路径时,服务端把数据和HTML模板组装后返回出来。

# buffer模块

buffer模块是二进制数据缓冲区,一种储存原始数据的方法,用来处理二进制文件流。

创建 Buffer 类,通过下面API进行创建Buffer实例,创建一个指定大小的Buffer实例(缓存区),然后在往里写入数据或读取数据,进行数据操作。

  • Buffer.alloc(size[, fill[, encoding]]): 返回一个指定大小的 Buffer 实例,如果没有设置 fill,则默认填满 0

  • Buffer.allocUnsafe(size): 返回一个指定大小的 Buffer 实例,但是它不会被初始化,所以它可能包含敏感的数据

  • Buffer.allocUnsafeSlow(size)

  • Buffer.from(array): 返回一个被 array 的值初始化的新的 Buffer 实例(传入的 array 的元素只能是数字,不然就会自动被 0 覆盖)

  • Buffer.from(arrayBuffer[, byteOffset[, length]]): 返回一个新建的与给定的 ArrayBuffer 共享同一内存的 Buffer。

  • Buffer.from(buffer): 复制传入的 Buffer 实例的数据,并返回一个新的 Buffer 实例

  • Buffer.from(string[, encoding]): 返回一个被 string 的值初始化的新的 Buffer 实例

// 创建一个Buffer实例
const buf = Buffer.alloc(256);


// 长度
console.log(buf.length) // 256


// 写入数据
const len = buf.write("www.w3cschool.cn");
console.log("写入字节数 : "+  len); // 写入字节数 : 16


// 读取
const str = buf.toString()
console.log("读取数据 : "+  str); // 读取数据 : www.w3cschool.cn


// 将 Buffer 转换为 JSON 对象
buf.toJSON()


// 缓冲区合并
const buffer1 = Buffer.from(('w3cschool编程狮'));
const buffer2 = Buffer.from(('www.w3cschool.cn'));
const buffer3 = Buffer.concat([buffer1,buffer2]);
console.log("buffer3 内容: " + buffer3.toString()); // buffer3 内容: w3cschool编程狮www.w3cschool.cn


// 缓冲区比较
const result = buffer1.compare(buffer2);
console.log('比较:', result)




// 拷贝缓冲区
const buf1 = Buffer.from('helloworld');
const buf2 = Buffer.from('***');
//将 buf2 插入到 buf1 指定位置上
buf2.copy(buf1, 5);


console.log(buf1.toString()); // hello***ld




// 缓冲区裁剪
var buffer22 = buffer1.slice(0, 2);

# Stream(流)

Stream 是一个抽象接口,流可读可写,可以让数据从一个地方流动到另一个地方。流是一种处理读写文件、网络通信或任何端到端信息交换的有效方式。主要用来控制数据流转。

我们可以用来处理一些大文件的读写操作,它通过一个通道逐步去加载,就像大视频的加载一步一步进行,而不是一下加载完。

像HTTP 服务器中,request是可读流,response是可写流。还有fs 模块,能同时处理可读和可写文件流。

Stream 有四种流类型:

  • Readable - 可写流: 可写入数据的流。例如fs.createWriteStream() 可以使用流将数据写入文件。

  • Writable -  可读流: 可读取数据的流。例如fs.createReadStream() 可以从文件读取内容。

  • Duplex -  双工流: 既可读又可写的流。例如 net.Socket。

  • Transform - 转换流: 可以在数据写入和读取时修改或转换数据的流。例如,在文件压缩操作中,可以向文件写入压缩数据,并从文件中读取解压数据。

创建流:

const Stream = require('stream')
const readableStream = new Stream.Readable()
// 发送数据
readableStream.push('ping!')
readableStream.push('pong!')

从流中读取数据:

// 从流中读取数据
var fs = require('fs')
var data = ''


// 创建可读流,读取某个txt文件
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('程序执行完毕')


// 执行完会答应input.txt的文件内

写入流:

// 写入流
var fs = require('fs')
var data = 'hello world'


// 创建一个可以写入的流,写入到文件 output.txt 中
var writerStream = fs.createWriteStream('output.txt')


// 使用 utf8 编码写入数据
writerStream.write(data, 'UTF8')


// 标记文件末尾
writerStream.end()


// 处理流事件 --> data, end, and error
writerStream.on('finish', function () {
  console.log('写入完成。')
})


writerStream.on('error', function (err) {
  console.log(err.stack)
})


console.log('程序执行完毕')


// 程序执行完毕
// 写入完成。


// 执行完成后会创建一个output.txt文件

管道流: 管道提供了一个输出流到输入流的机制。通常我们用于从一个流中获取数据并将数据传递到另外一个流中。

var fs = require('fs')


// 创建一个可读流
var readerStream = fs.createReadStream('input.txt')


// 创建一个可写流
var writerStream = fs.createWriteStream('output.txt')


// 管道读写操作
// 读取 input.txt 文件内容,并将内容写入到 output.txt 文件中
readerStream.pipe(writerStream)


console.log('程序执行完毕')

链式流: 链式是通过连接输出流到另外一个流并创建多个对个流操作链的机制。链式流一般用于管道操作。链式操作流。

// 压缩文件,进行链式操作
var fs = require('fs')
var zlib = require('zlib')


// 压缩 input.txt 文件为 input.txt.gz
fs.createReadStream('input.txt').pipe(zlib.createGzip()).pipe(fs.createWriteStream('input.txt.gz'))


console.log('文件压缩完成。')

# 事件循环

事件循环Node.js 使用事件驱动模型,当web server接收到请求,就把它关闭然后进行处理,然后去服务下一个web请求。当这个请求完成,它被放回处理队列,当到达队列开头,这个结果被返回给用户。

与vue中央事件监听类似。