15-Node


typora-copy-images-to: media

初识Node

Node的概念

  • Nodejs是一个js运行时,它可以像浏览器一样解析和运行js文件,构建于Chrome的V8引擎之上
  • 浏览器中的js:ECMAScript、BOM、DOM
  • Node中的js:ECMAScript (没有BOM、DOM)、文件读写、网络服务构建、网络通信、http服务器
  • Node是一个事件驱动的非阻塞IO模型(异步)
  • Node的包生态系统NPM,是世界最大的开源库生态系统
  • Node.js 是一种建立在Google Chrome’s v8 engine上的 non-blocking (非阻塞), event-driven (基于事件的) I/O平台
  • Node.js平台使用的开发语言是JavaScript,平台提供了操作系统低层的API,方便做服务器端编程,具体包括文件操作、进程操作、通信操作等系统模块
浏览器 内核
IE Trident
FireFox Gecko
Chrome WebKit
Safari WebKit
Opera Presto
Edge Chakra

Node中的 JavaScript 运行环境

  • 1、浏览器是 JavaScript 的前端运行环境
  • 2、Node 是 JavaScript 的后端运行环境
  • 3、Node中无法调用 DOM 和 BOM 等浏览器内置 API

Node.js可以做什么

Node的学习路径

  • 1、JavaScript 基础语法
  • 2、Node.js 内置 API 模块(fs、path、http等)
  • 3、第三方 API 模块(express、mysql 等)

学习参考资料

  • 《深入浅出Node.js》 (偏理论,理解原理,没有实战)
  • 《Node.js权威指南》 (API讲解,没有实战)
  • JavaScript标准参考教程(alpha)
  • Node入门
  • 官方API文档
  • 中文文档(版本较旧)http://nodeclass.com/api/node.html
  • CNODE - 社区https://cnodejs.org/
  • CNODE - 新手入门https://cnodejs.org/getstart

终端基本使用

打开应用

  • notepad - 打开记事本
  • mspaint - 打开画图
  • calc - 打开计算机
  • write - 写字板
  • sysdm.cpl - 打开环境变量设置窗口

常用命令

  • md - 创建目录
  • rmdir(rd) - 删除目录 (目录内没有文档)
  • echo on a.txt - 创建空文件
  • del - 删除文件
  • rm 文件名 - 删除文件
  • cat 文件名 - 查看文件内容
  • cat > 文件名 - 向文件中写入内容(覆盖之前的内容)
  • cat >> 文件名 - 向文件中追加写入内容(不会覆盖之前的内容)

Node开发环境安装

安装

区分 LTS 版本和 Current 版本

  • LTS长期稳定版,对于追求稳定性的企业级项目来说,推荐安装 LTS 版本的 Node.js。
  • Current新特性尝鲜版,对热衷于尝试新特性的用户来说,推荐安装 Current 版本的 Node.js。但是,Current 版本中可能存在隐藏的 Bug 或安全性漏洞,因此不推荐在企业级项目中使用 Current 版本的 Node.js。

多版本安装(nvm

  • 1、卸载已有的Node.js

  • 2、下载nvm

  • 3、在C盘创建目录dev

  • 4、在dev目中中创建两个子目录nvm和nodejs

  • 5、并且把nvm包解压进去nvm目录中

  • 6、在install.cmd文件上面右键选择【以管理员身份运行】

  • 7、打开的cmd窗口直接回车会生成一个settings.txt文件,修改文件中配置信息(注意最后2行是淘宝镜像路径)

    root: D:Program Files vm vm
    path: D:Program Files vm odejs
    arch: 64
    proxy: none

    node_mirror: https://npm.taobao.org/mirrors/node/
    npm_mirror: https://npm.taobao.org/mirrors/npm/

  • 8、配置nvm和Node.js环境变量

    • NVM_HOME:C:dev vm
    • NVM_SYMLINK:C:dev odejs
  • 9、把配置好的两个环境变量加到Path中

    %NVM_HOME%

    %NVM_SYMLINK%

nvm常用的命令

  • nvm list - 查看当前安装的Node.js所有版本
  • nvm install 版本号 - 安装指定版本的Node.js
  • nvm uninstall 版本号 - 卸载指定版本的Node.js
  • nvm use 版本号 - 选择指定版本的Node.js
// nvm常用的命令
nvm version		// 查看nvm版本号,顺便可以测试nvm是否已经安装成功
nvm install <version> [arch]	// 安装nvm环境下的nodejs,version表示版本号,可以使用latest代表最新版本
nvm uninstall <version>		// 卸载nvm环境下的nodejs
nvm list || nvm ls	// 列出当前环境下的所有noejs版本号
nvm use <version> [arch]	// 使用某个版本的nodejs

node -v	|| node --version		// 查询node版本号,测试node是否安装成功

Node入门

  1. 创建、编写js脚本文件
  2. 打开定位到文件所在目录,进入cmd窗口
  3. 执行node xxx.js,运行JS文件
  4. 注意:文件名不要使用node.js 命名,最好也不要使用中文名称

1、简单应用 - 读写文件

// 使用require方法加载fs文件核心模块
var fs = require('fs');

// 'data/hello.txt'     文件路径
// function(err,data)   回调函数
//      文件读取成功
//          err     null
//          data    数据对象
//      文件读取失败
//          err     错误对象
//          data    undefined
// 
fs.readFile('data/hello.txt', function(err,data){
    if( err ){
        // 读取文件出错
        console.log('读取文件出错');
    }else{
        console.log('读取文件成功
');
        console.log(data);
        console.log(data.toString()+'
');
    }
});
$ node 02-文件读写.js
读取文件成功

<Buffer 68 65 6c 6c 6f 20 6e 6f 64 65 2e 6a 73>
hello node.js

2、简单应用 - http服务器搭建

// 加载 http 核心模块
var http = require('http');
// 创建服务器
var server = http.createServer();
// 监听request请求事件
server.on('request', function(request,response){
    console.log('已经收到客户端请求!,请求路径:'+request.url);
  
  	- response.write('hello ');
  	- response.write('nodejs');
  	- response.end();
    + response.end('hello nodejs');
});
// 监听端口号
server.listen(3000, function() {
    console.log('服务器启动成功,请通过127.0.0.1:3000/访问');
});

http://localhost:3000/login

$ node 03-http简单服务器.js
服务器启动成功,请通过127.0.0.1:3000/访问
已经收到客户端请求!,请求路径:/
已经收到客户端请求!,请求路径:/favicon.ico
已经收到客户端请求!,请求路径:/login
已经收到客户端请求!,请求路径:/favicon.ico

REPL

  • REPL:read evel print loop(读取代码->执行->打印结果->循环这个过程)
  • 在REPL环境中, _ 表示最后一次执行结果, .exit 可以退出REPL环境
  • 在REPL环境中,可以直接使用url.parse()这种API,而不用require()

全局成员global

在nodejs中没有window对象,但是有一个类似的对象global,访问全局成员的时候可以省略global

  • __filename - 文件的绝对路径(包含文件名)
  • __dirname - 目录的绝对路径(不包含文件名)
  • setTimeout() -
  • setInterval() -
  • clearTimeout() -
  • clearInterval -
  • console -
  • process - 进程对象
    • process argv - 返回一个数组,第一个元素:process.execPath(Nodejs的环境路径),第二个元素:当前js文件的路径,后面的元素:命令行参数
    • process arch - 当前操作系统的CPU架构:32位 / 64位
  • exports - 导出模块中的成员(module.exports的简写)
  • module - 导出模块中的成员(成员多时使用)
  • require() - 用于引入模块、JSON、本地文件

核心模块

Node为JavaScript提供了很多服务器级别的API,这些API绝大多数都被包装到了一个具名的核心模块中

  1. fs - 文件操作
  2. http - HTTP服务构建
  3. path - 路径操作
  4. os - 操作系统信息
// 常用模块引入
var fs = require('fs');	// 文件操作
var http = require('http');	// http服务器
var path = require('path');	
var os = require('os');

核心模块-FS

Node.js 文件系统(fs 模块)模块中的方法均有异步和同步版本,例如读取文件内容的函数有异步的 fs.readFile() 和同步的 fs.readFileSync()。

异步的方法函数最后一个参数为回调函数,回调函数的第一个参数包含了错误信息(error)。

建议大家使用异步方法,比起同步,异步方法性能更高,速度更快,而且没有阻塞。

异步编程概念

  • js的运行是单线程的,所以为了防止程序阻塞,引入了事件队列机制。Node.js和浏览器的事件模型类似,都是:单线程 + 事件队列

  • 浏览器中的异步操作

    1. 定时任务
    2. 事件处理
    3. Ajax回调处理
  • Node.js中的异步操作

    1. 文件I/O
    2. 网络I/O
  • Node.js是基于回调函数的编码风格

案例:初始化目录结构

/* 初始化项目目录 */
let fs = require('fs');
let path = require('path');

let initData = {
    projectName: 'mydemo',
    data: [
        { name: 'css', type: 'dir' },
        { name: 'js', type: 'dir' },
        { name: 'iamges', type: 'dir' },
        { name: 'index.js', type: 'file' },
        { name: 'index.html', type: 'file' }
    ]
}
let tpl = `
<!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>
    <h1>我的项目</h1>
</body>
</html>
`;
// 创建第一层目录
fs.mkdir( path.join(__dirname, initData.projectName),(err)=>{
    if(err) return console.log('创建目录失败');
    
    console.log('目录创建成功:' + initData.projectName);
    // 创建第二层目录
    initData.data.forEach((item, index)=>{
        if( item.type === 'dir' ){
            fs.mkdir( path.join(__dirname, initData.projectName, item.name), (err)=>{
                if(err) return console.log('创建目录失败');
    
                console.log('目录创建成功:' + path.join(initData.projectName, item.name));
            } );
        }else if ( item.type === 'file' ){
            let tmpTpl = '';
            if( item.name === 'index.html' ){
                tmpTpl = tpl;
            }
            fs.writeFile( path.join(__dirname, initData.projectName, item.name), tmpTpl, (err)=>{
                if(err) return console.log('写入文件内容失败');

                console.log('写入文件成功:', path.join(__dirname, initData.projectName, item.name));
            } );
        }
    });
} );

// 结果
D:00web2015-Nodejs>node 27-case-fs-initdir.js
目录创建成功:mydemo
目录创建成功:mydemocss
目录创建成功:mydemojs
目录创建成功:mydemoimages
写入文件成功: D:00web2015-Nodejsmydemoindex.js
写入文件成功: D:00web2015-Nodejsmydemoindex.html

路径动态拼接的问题

在使用 fs 模块操作文件时,如果提供的操作路径是以 ./ 或 ../ 开头的相对路径时,很容易出现路径动态拼接错误的问题。

原因:代码在运行的时候,会以执行 node 命令时所处的目录 动态拼接出被操作文件的完整路径。

解决方案:在使用 fs 模块操作文件时,直接提供完整的路径(绝对路径),不要提供 ./ 或 ../ 开头的相对路径,从而防止路径动态拼接的问题。

API

  • 文件操作

    • fs.stat(path, callback) - 获取文件信息
    • fs.readFile( path, ['utf8',] callback ) - 读取文件
    • fs.writeFile() - 写入文件
    • fs.unlink - 删除文件
  • 目录操作

    • fs.mkdir(path[,mode], callback) - 创建目录
    • fs.readdir(path[, options], callback) - 读取目录
    • fs.rmdir(path, callback) - 删除目录
  • 文件流(大文件)操作

    • fs.createReadStream( path[, option] ) -
    • fs.createWriteStream( path[, option] ) -
    • readStream.pipe(targetStream) -

fs.readFile()

读取文件

语法

以下为异步模式下读取文件的语法格式:

fs.readFile(path[, options], callback)

参数

参数使用说明如下:

  • path - 文件的路径。
  • options - 该参数是一个对象,包含 {encoding, flag, signal}。默认编码为 utf8, flag 为 'w',signal允许中止正在进行的读取文件
  • callback - 回调函数,有2个参数(err, data),文件读取完成后,通过回调函数拿到读取的结果。

注意

  • 1、如果读取成功,则err的值为null
  • 2、如果读取失败,则err的值为 错误对象,data的值为 undefined

实例

const fs = require('fs');

// 读取文件
fs.readFile('./test.txt', function(err, data){
    if( err ) return console.log(err);
    console.log(data.toString());
})

注意

fs.writeFile()

写入文件

语法

以下为异步模式下写入文件的语法格式:

fs.writeFile(file, data[, options], callback)

writeFile 直接打开文件默认是 w 模式,所以如果文件存在,该方法写入的内容会覆盖旧的文件内容。

参数

参数使用说明如下:

  • file - 文件名或文件描述符。
  • data - 要写入文件的数据,可以是 String(字符串) 或 Buffer(缓冲) 对象。
  • options - 该参数是一个对象,包含 {encoding, mode, flag}。默认编码为 utf8, 模式为 0666 , flag 为 'w'
  • callback - 回调函数,回调函数只包含错误信息参数(err),在写入失败时返回。

注意

  • writeFile只能用来创建文件,不能用来创建目录

实例

接下来我们创建 file.js 文件,代码如下所示:

const fs = require("fs");

console.log("准备写入文件");
fs.writeFile('input.txt', '我是通 过fs.writeFile 写入文件的内容',  function(err) {
   if (err) {
       return console.error(err);
   }
   console.log("数据写入成功!");
   console.log("--------我是分割线-------------")
   console.log("读取写入的数据!");
   fs.readFile('input.txt', function (err, data) {
      if (err) {
         return console.error(err);
      }
      console.log("异步读取文件数据: " + data.toString());
   });
});

以上代码执行结果如下:

$ node file.js 
准备写入文件
数据写入成功!
--------我是分割线-------------
读取写入的数据!
异步读取文件数据: 我是通 过fs.writeFile 写入文件的内容

fs.open()

打开文件

语法

以下为在异步模式下打开文件的语法格式:

fs.open(path, flags[, mode], callback)

参数

参数使用说明如下:

  • path - 文件的路径。
  • flags - 文件打开的行为。具体值详见下文。
  • mode - 设置文件模式(权限),文件创建默认权限为 0666(可读,可写)。
  • callback - 回调函数,带有两个参数如:callback(err, fd)。

flags 参数可以是以下值:

Flag 描述
r 以读取模式打开文件。如果文件不存在抛出异常。
r+ 读写模式打开文件。如果文件不存在抛出异常
rs 以同步的方式读取文件。
rs+ 以同步的方式读取和写入文件。
w 写入模式打开文件,如果文件不存在则创建
wx 类似 'w',但是如果文件路径存在,则文件写入失败。
w+ 读写模式打开文件,如果文件不存在则创建
wx+ 类似 'w+', 但是如果文件路径存在,则文件读写失败。
a 以追加模式打开文件,如果文件不存在则创建
ax 类似 'a', 但是如果文件路径存在,则文件追加失败。
a+ 读取追加模式打开文件,如果文件不存在则创建
ax+ 类似 'a+', 但是如果文件路径存在,则文件读取追加失败。

实例

接下来我们创建 file.js 文件,并打开 input.txt 文件进行读写,代码如下所示:

var fs = require("fs");

// 异步打开文件
console.log("准备打开文件!");
fs.open('input.txt', 'r+', function(err, fd) {
   if (err) {
       return console.error(err);
   }
  console.log("文件打开成功!");     
});

以上代码执行结果如下:

$ node file.js 
准备打开文件!
文件打开成功!

fs.stat()

获取文件信息

语法

以下为通过异步模式获取文件信息的语法格式:

fs.stat(path, callback)

参数

参数使用说明如下:

  • path - 文件路径。
  • callback - 回调函数,带有两个参数如:(err, stats), statsfs.Stats 对象。

fs.stat(path)执行后,会将stats类的实例返回给其回调函数。可以通过stats类中的提供方法判断文件的相关属性。例如判断是否为文件:

var fs = require('fs');

fs.stat('/Users/liuht/code/itbilu/demo/fs.js', function (err, stats) {
    console.log(stats.isFile());         //true
})

stats类中的方法有:

方法 描述
stats.isFile() 如果是文件返回 true,否则返回 false。
stats.isDirectory() 如果是目录返回 true,否则返回 false。
stats.isBlockDevice() 如果是块设备返回 true,否则返回 false。
stats.isCharacterDevice() 如果是字符设备返回 true,否则返回 false。
stats.isSymbolicLink() 如果是软链接返回 true,否则返回 false。
stats.isFIFO() 如果是FIFO,返回true,否则返回 false。FIFO是UNIX中的一种特殊类型的命令管道。
stats.isSocket() 如果是 Socket 返回 true,否则返回 false。

实例

接下来我们创建 file.js 文件,代码如下所示:

var fs = require("fs");

console.log("准备打开文件!");
fs.stat('input.txt', function (err, stats) {
   if (err) {
       return console.error(err);
   }
   console.log(stats);
   console.log("读取文件信息成功!");
   
   // 检测文件类型
   console.log("是否为文件(isFile) ? " + stats.isFile());
   console.log("是否为目录(isDirectory) ? " + stats.isDirectory());    
});

以上代码执行结果如下:

$ node file.js 
准备打开文件!
{ dev: 16777220,
  mode: 33188,
  nlink: 1,
  uid: 501,
  gid: 20,
  rdev: 0,
  blksize: 4096,
  ino: 40333161,
  size: 61,
  blocks: 8,
  atime: Mon Sep 07 2015 17:43:55 GMT+0800 (CST),
  mtime: Mon Sep 07 2015 17:22:35 GMT+0800 (CST),
  ctime: Mon Sep 07 2015 17:22:35 GMT+0800 (CST) }
读取文件信息成功!
是否为文件(isFile) ? true
是否为目录(isDirectory) ? false

fs.read()

读取文件

语法

以下为异步模式下读取文件的语法格式:

fs.read(fd, buffer, offset, length, position, callback)

该方法使用了文件描述符来读取文件。

参数

参数使用说明如下:

  • fd - 通过 fs.open() 方法返回的文件描述符。
  • buffer - 数据写入的缓冲区。
  • offset - 缓冲区写入的写入偏移量。
  • length - 要从文件中读取的字节数。
  • position - 文件读取的起始位置,如果 position 的值为 null,则会从当前文件指针的位置读取。
  • callback - 回调函数,有三个参数err, bytesRead, buffer,err 为错误信息, bytesRead 表示读取的字节数,buffer 为缓冲区对象。

实例

input.txt 文件内容为:

菜鸟教程官网地址:www.runoob.com

接下来我们创建 file.js 文件,代码如下所示:

var fs = require("fs");
var buf = new Buffer.alloc(1024);

console.log("准备打开已存在的文件!");
fs.open('input.txt', 'r+', function(err, fd) {
   if (err) {
       return console.error(err);
   }
   console.log("文件打开成功!");
   console.log("准备读取文件:");
   fs.read(fd, buf, 0, buf.length, 0, function(err, bytes){
      if (err){
         console.log(err);
      }
      console.log(bytes + "  字节被读取");
      
      // 仅输出读取的字节
      if(bytes > 0){
         console.log(buf.slice(0, bytes).toString());
      }
   });
});

以上代码执行结果如下:

$ node file.js 
准备打开已存在的文件!
文件打开成功!
准备读取文件:
42  字节被读取
菜鸟教程官网地址:www.runoob.com

fs.close()

关闭文件

语法

以下为异步模式下关闭文件的语法格式:

fs.close(fd, callback)

该方法使用了文件描述符来读取文件。

参数

参数使用说明如下:

  • fd - 通过 fs.open() 方法返回的文件描述符。
  • callback - 回调函数,没有参数。

实例

input.txt 文件内容为:

菜鸟教程官网地址:www.runoob.com

接下来我们创建 file.js 文件,代码如下所示:

var fs = require("fs");
var buf = new Buffer.alloc(1024);

console.log("准备打开文件!");
fs.open('input.txt', 'r+', function(err, fd) {
   if (err) {
       return console.error(err);
   }
   console.log("文件打开成功!");
   console.log("准备读取文件!");
   fs.read(fd, buf, 0, buf.length, 0, function(err, bytes){
      if (err){
         console.log(err);
      }

      // 仅输出读取的字节
      if(bytes > 0){
         console.log(buf.slice(0, bytes).toString());
      }

      // 关闭文件
      fs.close(fd, function(err){
         if (err){
            console.log(err);
         } 
         console.log("文件关闭成功");
      });
   });
});

以上代码执行结果如下:

$ node file.js 
准备打开文件!
文件打开成功!
准备读取文件!
菜鸟教程官网地址:www.runoob.com
文件关闭成功

fs.ftruncate()

截取文件

语法

以下为异步模式下截取文件的语法格式:

fs.ftruncate(fd, len, callback)

该方法使用了文件描述符来读取文件。

参数

参数使用说明如下:

  • fd - 通过 fs.open() 方法返回的文件描述符。
  • len - 文件内容截取的长度。
  • callback - 回调函数,没有参数。

实例

input.txt 文件内容为:

site:www.runoob.com

接下来我们创建 file.js 文件,代码如下所示:

var fs = require("fs");
var buf = new Buffer.alloc(1024);

console.log("准备打开文件!");
fs.open('input.txt', 'r+', function(err, fd) {
   if (err) {
       return console.error(err);
   }
   console.log("文件打开成功!");
   console.log("截取10字节内的文件内容,超出部分将被去除。");
   
   // 截取文件
   fs.ftruncate(fd, 10, function(err){
      if (err){
         console.log(err);
      } 
      console.log("文件截取成功。");
      console.log("读取相同的文件"); 
      fs.read(fd, buf, 0, buf.length, 0, function(err, bytes){
         if (err){
            console.log(err);
         }

         // 仅输出读取的字节
         if(bytes > 0){
            console.log(buf.slice(0, bytes).toString());
         }

         // 关闭文件
         fs.close(fd, function(err){
            if (err){
               console.log(err);
            } 
            console.log("文件关闭成功!");
         });
      });
   });
});

以上代码执行结果如下:

$ node file.js 
准备打开文件!
文件打开成功!
截取10字节内的文件内容,超出部分将被去除。
文件截取成功。
读取相同的文件
site:www.r
文件关闭成功

删除文件

语法

以下为删除文件的语法格式:

fs.unlink(path, callback)

参数

参数使用说明如下:

  • path - 文件路径。
  • callback - 回调函数,没有参数。

实例

input.txt 文件内容为:

site:www.runoob.com

接下来我们创建 file.js 文件,代码如下所示:

var fs = require("fs");

console.log("准备删除文件!");
fs.unlink('input.txt', function(err) {
   if (err) {
       return console.error(err);
   }
   console.log("文件删除成功!");
});

以上代码执行结果如下:

$ node file.js 
准备删除文件!
文件删除成功!

再去查看 input.txt 文件,发现已经不存在了。

fs.mkdir()

创建目录

语法

以下为创建目录的语法格式:

fs.mkdir(path[, options], callback)

参数

参数使用说明如下:

  • path - 文件路径。
  • options 参数可以是:
    • recursive - 是否以递归的方式创建目录,默认为 false。
    • mode - 设置目录权限,默认为 0777。
  • callback - 回调函数,没有参数。

实例

接下来我们创建 file.js 文件,代码如下所示:

var fs = require("fs");
// tmp 目录必须存在
console.log("创建目录 /tmp/test/");
fs.mkdir("/tmp/test/",function(err){
   if (err) {
       return console.error(err);
   }
   console.log("目录创建成功。");
});

以上代码执行结果如下:

$ node file.js 
创建目录 /tmp/test/
目录创建成功。

可以添加 recursive: true 参数,不管创建的目录 /tmp 和 /tmp/a 是否存在:

fs.mkdir('/tmp/a/apple', { recursive: true }, (err) => {
  if (err) throw err;
});

fs.readdir()

读取目录

语法

以下为读取目录的语法格式:

fs.readdir(path, callback)

参数

参数使用说明如下:

  • path - 文件路径。
  • callback - 回调函数,回调函数带有两个参数err, files,err 为错误信息,files 为 目录下的文件数组列表

实例

接下来我们创建 file.js 文件,代码如下所示:

var fs = require("fs");

console.log("查看 /tmp 目录");
fs.readdir("/tmp/",function(err, files){
   if (err) {
       return console.error(err);
   }
   files.forEach( function (file){
       console.log( file );
   });
});

以上代码执行结果如下:

$ node file.js 
查看 /tmp 目录
input.out
output.out
test
test.txt

fs.rmdir()

删除目录

语法

以下为删除目录的语法格式:

fs.rmdir(path, callback)

参数

参数使用说明如下:

  • path - 文件路径。
  • callback - 回调函数,没有参数。

实例

接下来我们创建 file.js 文件,代码如下所示:

var fs = require("fs");
// 执行前创建一个空的 /tmp/test 目录
console.log("准备删除目录 /tmp/test");
fs.rmdir("/tmp/test",function(err){
   if (err) {
       return console.error(err);
   }
   console.log("读取 /tmp 目录");
   fs.readdir("/tmp/",function(err, files){
      if (err) {
          return console.error(err);
      }
      files.forEach( function (file){
          console.log( file );
      });
   });
});

以上代码执行结果如下:

$ node file.js 
准备删除目录 /tmp/test
读取 /tmp 目录
……

fs.createReadStream()

文件流(大文件)操作

语法

fs.createReadStream( path[, option] )

fs.createWriteStream()

文件流(大文件)操作

语法

fs.createWriteStream( path[, option] )

readStream.pipe()

文件流(大文件)操作

语法

readStream.pipe(targetStream)

示例

/* 文件流操作 */
// createReadStream 和 createWriteStream
let fs = require('fs');
let path = require('path');

let sPath = path.join(__dirname, 'data', 'typora-setup-x64.exe');
let ePath = path.join(__dirname, 'www', 'typora-setup-x64.exe');
let readStream = fs.createReadStream(sPath);
let writeSteam = fs.createWriteStream(ePath);

readStream.on('data', (chunk)=>{
    writeSteam.write(chunk);
});
readStream.on('end', ()=>{
    console.log('写入文件成功');
});

// pipe
fs.createReadStream('./data/hello.txt').pipe(fs.createWriteStream('./www/hi.txt'));

核心模块-PATH

path 模块提供了一些用于处理文件路径的小工具

API

  1. path.basename(path, [ext]) - 获取路径的最后一部分(文件名 或 最后一层目录)

  2. path.dirname(path) - 获取路径的目录名

  3. path.extname(path) - 获取路径的扩展名

  4. path.format(objPath) - 把路径对象转化为路径字符串

  5. path.parse(strPath) - 把路径字符串转化为路径对象

  6. path.isAbsulute(path) - 判断是否是绝对路径

  7. path.join([...paths]) - 拼接路径(会识别 ['.', '..']这2个字符 )

  8. path.normalize(path) - 规范化路径写法(会识别 ['.', '..']这2个字符 )

  9. path.relative(from, to) - 计算from相对to的路径

  10. path.resolve([...paths]) - 解析路径(相当于cmd中的cd命令)

  11. path.delimiter - 路径分隔符(windows使用号 linux使用/号)

  12. path.sep - 环境变量分隔符(windows使用;号 linux使用:号)

let path = require('path');
let url = 'D:/IT资料/笔记/Web笔记-传智39期/15-Nodejs/15-Nodejs.md';
let url2 = 'D:/000/web20/15-Nodejs/06-模拟apache';

// path.basename() 获取路径最后一部分
console.log( path.basename(url) );  // 15-Nodejs.md
console.log( path.basename(url2) );  // 06-模拟apache
console.log( path.basename(url, '.md') );   // 15-Nodejs

// path.dirname() 获取路径的目录名
console.log( path.dirname(url) );  // D:/IT资料/笔记/Web笔记-传智39期/15-Nodejs

// path.extname() 获取路径的扩展名
console.log( path.extname(url) );   // .md

// path.parse(strPath)	路径字符串 -> 路径对象
console.log(path.parse( __filename ));
/*
D:\000\web20\15-Nodejs\18-path-parse.js
结果 
{
  root: 'D:\',
  dir: 'D:\000\web20\15-Nodejs',
  base: '18-path-parse.js',
  ext: '.js',
  name: '18-path-parse'
}
*/

// path.format(objPath) 路径对象 ->路径字符串
let path = require('path');
let objPath = {dir: 'D:\000\web20\15-Nodejs', base: '18-path-parse.js'}
console.log( path.format(objPath) );    // D:00web2015-Nodejs18-path-parse.js

// 

path.join()

连接路径

使用特定于平台的分隔符作为定界符将所有给定的 path 片段连接在一起,然后规范化生成的路径

语法

path.join([...paths])

参数

  • paths - 路径片段的序列。

返回 - 连接后的路径字符串

注意

  • 1、零长度的 path 片段被忽略。 如果连接的路径字符串是零长度字符串,则将返回 '.',表示当前工作目录。
  • 2、如果任何路径片段不是字符串,则抛出 TypeError

实例

path.join('/foo', 'bar', 'baz/asdf', 'quux', '..');
// 返回: '/foo/bar/baz/asdf'

path.join('foo', {}, 'bar');
// 抛出 'TypeError: Path must be a string. Received {}'

path.dirname()

返回目录名

语法

path.dirname(path)

参数

  • path - 路径字符串

如果 path 不是字符串,则抛出 TypeError

返回 - 返回 path 的目录名

实例

path.dirname('/foo/bar/baz/asdf/quux');
// 返回: '/foo/bar/baz/asdf'

path.basename()

返回文件名

语法

path.basename(path[, ext])

参数

  • path - 路径字符串。
  • ext - 可选的文件扩展名

如果 path 不是字符串,或者如果给定 ext 并且不是字符串,则抛出 TypeError

返回 - 返回 path 的最后一部分,尾随的目录分隔符被忽略

实例

path.basename('/foo/bar/baz/asdf/quux.html');
// 返回: 'quux.html'

path.basename('/foo/bar/baz/asdf/quux.html', '.html');
// 返回: 'quux'

path.extname()

返回扩展名

语法

path.extname(path)

参数

  • path - <string> ,路径字符串。

如果 path 不是字符串,则抛出 TypeError

返回 - <string>

  • 返回 path 的扩展名。即 path 的最后一部分中从最后一次出现的 .(句点)字符到字符串的结尾。
  • 如果 path 的最后一部分中没有 .,或者除了 path 的基本名称(参见 path.basename())的第一个字符之外没有 . 个字符,则返回空字符串。

实例

path.extname('index.html');
// 返回: '.html'

path.extname('index.coffee.md');
// 返回: '.md'

path.extname('index.');
// 返回: '.'

path.extname('index');
// 返回: ''

path.extname('.index');
// 返回: ''

path.extname('.index.md');
// 返回: '.md'

path.parse()

解析字符串路径为路径对象

语法

path.parse(path)

参数

  • path - 路径字符串。如果 path 不是字符串,则抛出 TypeError

返回 - <object>

  • 返回一个对象,其属性表示 path 的重要元素。 尾随的目录分隔符被忽略
  • 返回的对象将具有以下属性:{ dir, root, base, name, ext }

实例

path.parse('C:\path\dir\file.txt');

// 返回:
// { root: 'C:\',
//   dir: 'C:\path\dir',
//   base: 'file.txt',
//   ext: '.txt',
//   name: 'file' }

path.format()

解析路径对象为为字符串路径

语法

path.format(pathObject)

参数

  • pathObject - 具有{dir, root, base, name, ext}属性的 JavaScript 对象,标红的参数优先级高于其他

返回 - <string>

  • 从对象返回路径字符串

注意

  • 1、当向 pathObject 提供属性时,存在一个属性优先于另一个属性的组合
    • 如果提供 pathObject.dir,则忽略 pathObject.root
    • 如果 pathObject.base 存在,则忽略 pathObject.extpathObject.name

实例

// 提供了`dir`,`root` 将被忽略
path.format({
  root: '/ignored',
  dir: '/home/user/dir',
  base: 'file.txt'
});
// 返回: '/home/user/dir/file.txt'

// 提供了`base`,`ext` 将被忽略
path.format({
  root: '/',
  base: 'file.txt',
  ext: 'ignored'
});
// 返回: '/file.txt'

// Windows系统
path.format({
  dir: 'C:\path\dir',
  base: 'file.txt'
});
// 返回: 'C:\path\dir\file.txt'

path.normalize()

规范化路径

规范化给定的 path,解析 '..''.' 片段。

语法

path.normalize(path)

参数

  • path - 待规范的路径

返回 - string

实例

path.normalize('C:\temp\\foo\bar\..\');
// 返回: 'C:\temp\foo\'

path.resolve()

解析路径片段为绝对路径

语法

path.resolve([...paths])

参数

  • ...paths - <string> ,路径或路径片段的序列

返回 - <string>

  • 将路径或路径片段的序列解析为绝对路径

注意

  • 1、给定的路径序列从右到左处理,每个后续的 path 会被追加到前面,直到构建绝对路径
  • 2、如果在处理完所有给定的 path 片段之后,还没有生成绝对路径,则使用当前工作目录
  • 3、生成的路径被规范化,并删除尾部斜杠(除非路径解析为根目录)
  • 4、零长度的 path 片段被忽略
  • 5、如果没有传入 path 片段,则 path.resolve() 将返回当前工作目录的绝对路径

实例

path.resolve('/foo/bar', './baz');
// 返回: '/foo/bar/baz'

// 给定的路径序列从右到左处理,直到构建绝对路径
path.resolve('/foo/bar', '/tmp/file/');
// 返回: '/tmp/file'

// 如果在处理完所有给定的 path 片段之后,还没有生成绝对路径,则使用当前工作目录
path.resolve('wwwroot', 'static_files/png/', '../gif/image.gif');
// 如果当前工作目录是 /home/myself/node,
// 则返回 '/home/myself/node/wwwroot/static_files/gif/image.gif'

path.relative()

返回从from到to的相对路径

语法

path.relative(from, to)

参数

  • from - <string> ,起始路径
  • to - <string> ,目标路径

返回

  • 根据当前工作目录返回从 fromto 的相对路径

注意

  • 如果 fromto 都解析为相同的路径(在分别调用 path.resolve() 之后),则返回零长度字符串
  • 如果零长度字符串作为 fromto 传入,则将使用当前工作目录而不是零长度字符串
  • 如果 fromto 不是字符串,则抛出 TypeError

实例

path.relative('/data/orandea/test/aaa', '/data/orandea/impl/bbb');
// 返回: '../../impl/bbb'

path.isAbsolute()

路径是否为绝对路径

语法

path.relative(path)

参数

  • path - <string>,路径字符串

返回 - <boolean>

注意

  • 1、如果 path 不是字符串,则抛出 TypeError
  • 2、如果给定的 path 是零长度字符串,则将返回 false

实例

path.isAbsolute('//server');    // true
path.isAbsolute('\\server');  // true
path.isAbsolute('C:/foo/..');   // true
path.isAbsolute('C:\foo\..'); // true

path.isAbsolute('bar\baz');    // false
path.isAbsolute('bar/baz');     // false
path.isAbsolute('.');           // false

核心模块-HTTP

http 模块是 Node.js 官方提供的、用来创建 web 服务器的模块。通过 http 模块提供的 http.createServer() 方法,就能方便的把一台普通的电脑,变成一台 Web 服务器,从而对外提供 Web 资源服务。

IP地址和端口号

所有联网的程序都需要进行网络通信,计算机中只有一个物理网卡,而且同一个局域网中,网卡的地址必须是唯一的,网卡是通过唯一的IP地址来进行定位

  • IP地址用来定位计算机
  • 端口号用来定位具体的应用程序(所有需要联网通信软件的软件都必须有端口号)
  • 端口号的范围:[0 , 65536]
  • 可以同时开启多个服务,但一定要确保不同服务占用的端口号不一致
  • 同一台电脑,同一个端口号不能同时被占用
  • request.socket.remoteAddress - 远程计算机的IP地址
  • request.socket.remotePort - 远程计算机的端口号

// 加载 http 核心模块
var http = require('http');
// 创建服务器
var server = http.createServer();
// 绑定request事件,监听客户端request请求
server.on('request', function(request,response){
	console.log('已经收到客户端请求!,请求路径:'+request.url);
	console.log('已经收到客户端请求!,远程IP地址、端口号:'+request.socket.remoteAddress+':'+request.socket.remotePort);
	response.end('hello nodejs');
});
// 启动服务器
server.listen(3000, function() {
	console.log('服务器启动成功,请通过127.0.0.1:3000/访问');
});

响应内容类型 Content-Type

在服务器端默认发送的数据,是utf8编码,但是浏览器不知道你是utf8编码的内容

浏览器在不知道服务器响应内容的编码格式的情况下,会按照当前操作系统默认的编码格式(GBK)去解析

不同资源对应的Content-Type是不一样的,具体参照:https://tool.oschina.net/commons

常用Content-Type:

  • text/plain - 普通文本

  • text/html - HTML文本

  • text/css - CSS文件

  • application/x-javascript - JS文件

  • image/jpeg - jpg,jpeg图片

  • image/png - png图片

  • image/gif - gif图片

    图片就不需要指定编码charset=utf-8了,因为常说的编码一般指的是:字符编码

// 解决中文乱码
// 		text/plain 普通文本格式(直接输出div等标签字符)
// 		text/html  HTML文本格式(会将div等标签字符渲染)
response.setHeader('Content-Type', 'text/plain; charset=utf-8');
response.end();

// PHP中设置编码
header('Content-Type:text/html;charset=utf-8');

// HTML中设置编码
<meta charset="utf-8">

根据不同URL读取不同文件

通过网络发送文件内容,http + fs

/* 模拟Apache,根据URL的不同读取不同的文件 */
// 1. http结合fs发送文件中的数据
// 2. 使用Content-Type指定编码
// 注意:该脚本不能访问html文件中的link、script加载的css、js文件

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

var server = http.createServer();

server.on('request', function(req, res){
    var url = req.url;
    if( url == '/' ){
        // 读取html文件
        fs.readFile('index.html', function (err, data) {
            if( err ){
                res.setHeader('Content-Type', 'text/html; charset=utf-8');
                res.end('读取文件出错');
            }else{
                res.setHeader('Content-Type', 'text/html; charset=utf-8');
                res.end(data.toString());
            }
        });
    }else if( url == '/img' ){
        // 读取图片文件
        fs.readFile('title2.png',function(err, data){
            if( err ){
                res.setHeader('Content-Type', 'text/plain; charset=utf-8');
                res.end('读取图片出错');
                return false;
            }
            res.setHeader('Content-Type', 'image/png');
            res.end(data);
        });
    }
});

server.listen('3000', function(){
    console.log('服务器已经开启,请通过localhost:3000访问');
});

请求对象 request

只要服务器接收到了客户端的请求,就会调用通过 server.on() 为服务器绑定的 request 事件处理函数。

如果想在事件处理函数中,访问与客户端相关的数据或属性,可以使用如下的方式:

server.on('request', function(req, res){
    // 访问与客户端请求相关的数据或属性
    // 常用
    console.log( req.url );  // /
    console.log( req.method ); // GET
    console.log( req.headers ); // {host,connection, accept}
    console.log( req.headers.host ); // 127.0.0.1:3000
    console.log( req.complete ); // false

    // 不常用
    console.log( req.httpVersion );  // 1.1
    console.log( req.statusCode ); // null
    console.log( req.statusMessage ); // null
    console.log( req.aborted ); // false
});

注意

  • 一个请求对应一个响应,如果在一个请求的过程中,已经结束了响应,则不能重复发送响应。没有请求就没有响应

响应对象 response

在服务器的 request 事件处理函数中,如果想访问与服务器相关的数据或属性,可以使用如下的方式:

server.on('request', function(req, res){
  	// 响应
    res.setHeader('Content-Type', 'text/html;charset=utf-8'); // 设置字符编码,解决中文乱码问题
    res.setHeader('Set-Cookie', ['type=ninja', 'language=javascript']); // 设置cookies
  	res.writeHead(200, {'Content-Length': Buffer.byteLength('body'), 'Content-Type': 'text/plain'}); //
  	
    res.end('结束本次访问请求'); // 结束请求,并输出文本
});

统一处理静态资源

  • 实现请求不同文件时响应对应文件的内容
/* 模拟Apache - 实现请求不同文件时响应对应文件的内容 */
var http = require( 'http' );
var path = require( 'path' );
var fs = require('fs');

var server = http.createServer(function (req, res) {
    console.log('服务器已开启' + req.url);
    var url  = __dirname + req.url;
    
    if( url === '/' ) url = '/index.html';
    fs.readFile(rootDir + url, function (err, data) {
        if( err ) return res.end('readFile ' + url + ' error!');
        res.end( data );
    });
}).listen('3000');

// 浏览器
localhost:3000
localhost:3000/index.html
localhost:3000/admin/login.html

// 返回
D:00web2015-Nodejs8-http-根据url响应不同的文件>node init.js
服务器已开启/
服务器已开启/favicon.ico
服务器已开启/data.txt
服务器已开启/favicon.ico
服务器已开启/admin/login.html
服务器已开启/favicon.ico
服务器已开启/
服务器已开启/favicon.ico
服务器已开启/index.html
服务器已开启/favicon.ico
  • 渲染指定目录的列表文件
/* 模拟Apache - 渲染指定目录的列表文件(技术http,fs,art-template) */
var http = require('http');
var fs = require('fs');
var template = require('art-template');
// 1. 指定需要遍历的文件目录
var rootDir = 'D:/000/web20/15-Nodejs/www';

http.createServer(function(req, res){
    
    // 2. 获取指定目录下的文件列表,files是一个数组:[ 'admin', 'data.txt', 'images', 'index.html', 'index.js' ]
    fs.readdir(rootDir, function(err, files){
        if( err ) return res.end('dir ' + rootDir + ' not found!');

        // 3. 读取模板引擎文件 template10.html
        fs.readFile('template10.html', function(err, data){
            if( err ) return res.end('file template10.html not found!');
            // 4. 使用模板引擎art-template语法对读取到的模板文件进行替换渲染
            var newTemplate = template.render(data.toString(), {
                files: files,
                title: rootDir
            });
            // 5. 输出渲染之后的模板内容到浏览器
            res.end(newTemplate);
        });
    });

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

参数的传递和获取

get参数获取

let url = require('url');
let u = new URL('http://www.baidu.com:3633/index.html?name=jack&age=10#101');
let uSearch = new URLSearchParams(u.searchParams);
console.log( uSearch.get('name') ); // jack
console.log( uSearch.get('age') );  // 10

post参数获取

  • querystring.parse(str[, sep, eq, options]) - 把url参数字符串转为对象
  • querystring.stringify(obj[, sep, eq, options]) - 把对象转化为url字符串
const querystring = require('querystring');

// parse()
let param = 'uname=lisi&age=10&age=13';
let obj = querystring.parse(param);
console.log(obj);	// [Object: null prototype] { uname: 'lisi', age: [ '10', '13' ] }

// stringify()
let obj1 = {
  flag: '123',
  abc: ['hello', 'hi']
}
let str1 = querystring.stringify(obj1);
console.log(str1);		// flag=123&abc=hello&abc=hi

API

  • http.createServer - 创建服务器
  • http.Server类
    • 'request'事件 - 监听客户端的request请求
    • server.socket
      • server.socket.remoteAddress - 获取远程客户端的IP地址
      • server.socket.remotePort - 获取远程客户端的端口号
    • server.listen([port, ip, callback]) - 监听客户端的访问地址和端口
  • http.Incomingmessage类(request)
    • message.url - 获取URL中端口之后的路径
  • http.ServerResponse类
    • response.end([data, encoding, callback]) - 向客户端响应内容,只能写一次
    • response.write(chunk[, encoding, callback]) - 向客户端响应内容,可以写多次
    • response.setHeader(name, value) - 设置响应内容的类型和字符编码
    • response.writeHead(stateCode, {name: value})

http.createServer

创建服务器

语法

http.createServer([options][, requestListener])

参数

  • options - <object> -
  • requestListener - <function> -

返回

  • -<http.Server>-

注意

  • requestListener 是自动添加到request事件的函数

实例

// 创建一个服务器实例对象
const server = http.creatServer();
// 使用服务器实例的.on()方法,为服务器绑定一个request事件
server.on('request', function(req, res){
  ...
});

http.Server类

'request'事件

监听客户端的request请求

server.listen()

监听客户端的访问地址和端口

// 调用服务器实例的.listen(port, callback) 方法,启动web服务器
server.listen(80, function(){
  console.log('server running at http://127.0.0.1');
})

http.Incomingmessage类(request)

req.url

获取URL中端口之后的路径

req.url // / | /index.html | /about.html...
req.method

获取请求的方法

req.method // GET | POST...

http.ServerResponse类

response.end()

向客户端响应内容,只能写一次

语法

response.end([data[, encoding]][, callback])

参数

  • data -<String> | <Buffer>- 如果指定了 data,则其效果类似于调用 response.write(data, encoding) 后跟 response.end(callback)
  • encoding -<String>- 字符编码
  • callback -<Function>- 如果指定了 callback,则将在响应流完成时调用

返回

注意

实例

response.write()

向客户端响应内容,可以写多次

response.setHeader()

为标头对象设置单个标头值

语法

request.setHeader(name, value)

参数

  • name -<String>- 键
  • value -<any>- 值

返回

注意

  • 1、如果该标头已经存在于待发送的标头中,则其值将被替换
  • 2、在此处使用字符串数组发送具有相同名称的多个标头。 非字符串值将不加修改地存储
  • 3、request.getHeader() 可能返回非字符串值。 但是,非字符串值将转换为字符串以进行网络传输

实例

request.setHeader('Content-Type', 'application/json');
request.setHeader('Content-Type', 'text/html;charset=utf-8');
request.setHeader('Cookie', ['type=ninja', 'language=javascript']);
response.writeHead()

向请求发送响应头

语法

response.writeHead(statusCode[, statusMessage][, headers])

参数

  • statusCode -<Number>- 响应状态码。状态码是 3 位的 HTTP 状态码,如 404
  • statusMessage -<String>- 响应状态码描述文本
  • headers -<Object> | <Array>- 响应头。

返回 -<http.ServerResponse>

注意

  • 1、此方法只能在消息上调用一次,并且必须在调用 response.end() 之前调用
  • 2、如果在调用此之前调用了 response.write()response.end(),则将计算隐式 / 可变的标头并调用此函数
  • 3、当标头已使用 response.setHeader() 设置时,则它们将与任何传给 response.writeHead() 的标头合并,其中传给 response.writeHead() 的标头优先

实例

res.writeHead(200, {'Content-Length': Buffer.byteLength('body'), 'Content-Type': 'text/plain'}); //
response.getHeader()

读取已排队但未发送到客户端的标头

语法

response.getHeader(name)

参数

  • name -<String>- 标头名。该名称不区分大小写

返回 -<any>-

  • 返回值的类型取决于提供给 response.setHeader() 的参数

实例

response.setHeader('Content-Type', 'text/html');
response.setHeader('Content-Length', Buffer.byteLength(body));
response.setHeader('Set-Cookie', ['type=ninja', 'language=javascript']);

const contentType = response.getHeader('content-type');	// contentType 是 'text/html'
const contentLength = response.getHeader('Content-Length');	// contentLength 是数字类型
const setCookie = response.getHeader('set-cookie');	// setCookie 是 string[] 类型
response.hasHeader()

是否存在某标头

语法

response.hasHeader(name)

参数

  • name -<String>- 标头名。该名称不区分大小写

返回 -<Boolean>-

  • 如果存在该标头,则返回true
  • 吐过不存在该标头,则返回false

实例

const hasContentType = response.hasHeader('content-type'); // 返回true或者false

核心模块-Buffer

Buffer对象是Node处理二进制数据的一个接口。它是Node原生提供的全局对象,可以直接使用,不需要require(‘buffer’)

Buffer本质上就是字节数组

实例化Buffer对象

  • Buffer.alloc(size) - 分配一个大小为 size 字节的新 Buffer
  • Buffer.from(string | buffer ) - 创建一个包含 string 或 buffer 的新 Buffer
let buf = new Buffer(5);	// 不推荐
let buf = Buffer.alloc(5);	// 推荐  <Buffer 00 00 00 00 00>
let buf = Buffer.from('this','utf8');  // <Buffer 74 68 69 73>
let buf = Buffer.from([0x62, 0x75, 0x66, 0x66, 0x65, 0x72]);

静态方法

  • Buffer.isEncoding(encoding) - 如果支持 encoding 字符编码,则返回 true,否则返回 false
  • Buffer.isBuffer(obj) - 如果 obj 是一个 Buffer,则返回 true,否则返回 false
  • Buffer.byteLength(string[, encoding]) - 当使用 encoding 进行编码时,返回字符串的字节长度
  • Buffer.concat( [buf1, buf2, buf3...] ) - 返回一个合并了 list 中所有 Buffer 实例的新 Buffer

实例方法

  • buf.write(string[, offset, length, encoding]) - 根据 encoding 指定的字符编码将 string 写入到 buf 中的 offset 位置,length 参数是要写入的字节数。 如果 buf 没有足够的空间保存整个字符串,则只会写入 string 的一部分
  • buf.slice([start, end]) - 返回一个新的 Buffer,它引用与原始的 Buffer 相同的内存,但是由 start 和 end 索引进行偏移和裁剪
  • buf.toString([encoding, start, end]) - 根据 encoding 指定的字符编码将 buf 解码成字符串。 传入 start 和 end 可以只解码 buf 的子集
  • buf.toJSON() - toJSON方法不需要显式调用,当JSON.stringify方法调用的时候会自动调用toJSON方法
// buf.write()
let buf = Buffer.alloc(5);
buf.write('hello',2,2);
console.log(buf);	// <Buffer 00 00 h e 00>

// buf.slice()
let buf = Buffer.from('hello');
let buf1 = buf.slice(2,3);
console.log(buf === buf1);		//false
console.log(buf1.toString());	// llo

// JSON.stringify(buf);
const buf = Buffer.from('hello');
const json = JSON.stringify(buf);
console.log(json);

核心模块-URL

API

  • url.parse(strUrl[, queryType]) - 解析url地址为对象格式

    参数

    • queryType - true:query解析为对象格式,false:query解析为字符串格式
  • url.format(objUrl[, options])- 将对象格式的URL转化为字符串格式

  • URL类

    • new URL(input[, base]) - 初始化一个URL类的对象

      // 代码
      let url = require('url');
      let u = new URL('http://www.baidu.com:3633/index.html?name=jack&age=10#101');
      console.log(u);
      console.log(u.searchParams);
      let params = new URLSearchParams(u.search);
      console.log( params.get('name') );  // jack
      
      // 结果
      URL {
        + href: 'http://www.baidu.com:3633/index.html?name=jack&age=10#101',
        + origin: 'http://www.baidu.com:3633',
        protocol: 'http:',
        username: '',
        password: '',
        + host: 'www.baidu.com:3633',
        + hostname: 'www.baidu.com',
        port: '3633',
        + pathname: '/index.html',
        + search: '?name=jack&age=10',
        + searchParams: URLSearchParams { 'name' => 'jack', 'age' => '10' },
        hash: '#101'
      }
      

    • u.href

    • u.origin

    • u.host

    • u.hostname

    • u.pathname

    • u.search

    • u.toString()

  • URLSearchParams类

    • urlSearchParams.get(name) - 通过name获取URL参数值

核心模块-OS

API

  1. os.cpus() - 获取当前机器的CPU信息
  2. os.totalmem() - 获取当前机器的内存大小(单位:byte)

模块化开发

模块化的基本概念

什么是模块化

模块化是指解决一个复杂问题时,自顶向下逐层把系统划分成若干模块的过程。对于整个系统来说,模块是可组合、分解和更换的单元

编程领域中的模块化,就是遵守固定的规则,把一个大文件拆成独立并互相依赖多个小模块

一个js文件就是一个模块,模块内部的成员相互独立,Node中是模块作用域

传统的非模块化开发的缺点

  • 1、命名冲突
  • 2、文件依赖

把代码进行模块化拆分的好处

  • 1、提高了代码的复用性
  • 2、提高了代码的可维护性
  • 3、可以实现按需加载

模块化规范

模块化规范就是对代码进行模块化的拆分与组合时,需要遵守的那些规则。

例如:

  • 使用什么样的语法格式来引用模块

  • 在模块中使用什么样的语法格式向外暴露成员

模块化规范的好处

大家都遵守同样的模块化规范写代码,降低了沟通的成本,极大方便了各个模块之间的相互调用,利人利己。

浏览器端模块化规范

JS天生不支持模块化,浏览器中如果想要像Node一样模块编程,就需要下面的第三方库:

  • AMD - requirejs
  • CMD - seajs

服务器端模块化规范

  • CommonJS - Node.js

Node中模块化

Node中模块的分类

Node.js 中根据模块来源的不同,将模块分为了 3 大类,分别是:

  • 内置模块(内置模块是由 Node.js 官方提供的,例如 fs、path、http 等)

  • 自定义模块(用户创建的每个 .js 文件,都是自定义模块)

  • 第三方模块(由第三方开发出来的模块,并非官方提供的内置模块,也不是用户创建的自定义模块,使用前需要先下载)

加载模块

使用 require() 方法,可以加载需要的内置模块、用户自定义模块、第三方模块进行使用。例如:

注意

  • 1、使用 require() 方法加载其它模块时,会执行被加载模块中的代码
  • 2、在使用 require() 加载用户自定义模块期间,可以省略 .js 的后缀名

Node中的模块作用域

和函数作用域类似,在自定义模块中定义的变量方法等成员,只能在当前模块内被访问,这种模块级别的访问限制,叫做模块作用域

好处 :防止了全局变量污染的问题

向外共享模块作用域中的成员

  • module 对象

    在每个 .js 自定义模块中都有一个 module 对象,它里面存储了和当前模块有关的信息,打印如下:

  • module.exports 对象

    在自定义模块中,可以使用 module.exports 对象,将模块内的成员共享出去,供外界使用。

    外界用 require() 方法导入自定义模块时,得到的就是 module.exports 所指向的对象

    注意:使用 require() 方法导入模块时,导入的结果,永远以 module.exports 指向的对象为准

  • exports 对象

    由于 module.exports 单词写起来比较复杂,为了简化向外共享成员的代码,Node 提供了 exports 对象默认情况下,exports 和 module.exports 指向同一个对象。最终共享的结果,还是以 module.exports 指向的对象为准。

  • exports 和 module.exports 的使用误区

    // exports和module.exports之间的关系
    exports = module.exports = {}
    console.log( exports === module.exports )  // true
    

    时刻谨记,require() 模块时,得到的永远是 module.exports 指向的对象

    注意:为了防止混乱,建议大家不要在同一个模块中同时使用 exports 和 module.exports

分析

  • exports = { gender: '男', age: 22 } ,这种写法是无效的,此时的exports只相当于普通的变量
  • 只有 exports.gender = '男' 这样的写法才是有效的

示例

module.exports = {
    name: 'zs',
    age:33
}
console.log(module);    // exports: { name: 'zs', age: 33 },

console.log('--------------------------------');

module.exports.name = 'Jack';
module.exports.age = 100;
console.log(module);    // exports: { name: 'Jack', age: 100 },

console.log('--------------------------------');

exports = {
    user: 'Tom',
    pass: '123'
}
console.log(module);    // exports: {},
// 注意:此处的exports不能直接等于一个对象,

console.log('--------------------------------');

exports.user = 'ZS';
exports.pass = '123';   
console.log(module);    // exports: { user: 'ZS', pass: '123' },

console.log('--------------------------------');

console.log( exports );     // {}

Node中模块化的规范

Node.js 遵循了 CommonJS 模块化规范,CommonJS 规定了模块的特性各模块之间如何相互依赖

CommonJS 规定:

  • 1、每个模块内部,module 变量代表当前模块
  • 2、module 变量是一个对象,它的 exports 属性(即 module.exports)是对外的接口
  • 3、加载某个模块,其实是加载该模块的 module.exports 属性require() 方法用于加载模块

npm

多个模块可以形成包,不过要满足特定的规则才能形成规范的包

npm概念

npm(node.js package management)

npm网站:全球最大的模块生态系统,里面所有的模块都是开源免费的;网址:https://www.npmjs.com/

npm命令行工具:Node.js的包管理工具

npm安装

本地安装

本地安装的包在当前项目目录下的node_modules目录下,本地安装的包一般用于实际的开发工作

npm install 包名称		// 本地安装

全局安装

全局安装的包位于nodejs环境的node_modules目录下,全局安装的包一般用于命令行工具

npm install -g 包名称	// 全局安装(关键:-g)

解决npm安装包被墙的问题

  • --registry

    # 查看当前的下包镜像源
    npm config get registry
    
    # 把registry选项加到配置文件
    npm config set registry https://registry.npm.taobao.org
    
    ## 修改回来
    npm config set registry https://registry.npmjs.org/
    
    # 如果不设置registry的话,就需要每次安装包的是否在后面加上
    npm install 包名 --registry=https://registry.npm.taobao.org
    
    # 查看npm 配置信息
    npm config list
    
  • cnpm

    # 安装cnpm
    npm install -g cnpm –registry=https//registry.npm.taobao.org
    # 使用cnpm安装包
    cnpm install 包名
    
  • nrm

    # 安装nrm
    npm install -g nrm
    
    # 查看所有可用的镜像源
    nrm ls
    #  npm ---------- https://registry.npmjs.org/
    #  yarn --------- https://registry.yarnpkg.com/
    #  tencent ------ https://mirrors.cloud.tencent.com/npm/
    #  cnpm --------- https://r.cnpmjs.org/
    #  taobao ------- https://registry.npmmirror.com/
    #  npmMirror ---- https://skimdb.npmjs.com/registry/
    
    # 切换镜像源
    nrm use taobao
    

npm常用命令

npm 命令中的选项和简写

// install
-g				// 全局安装
--save			// 向生产环境添加依赖(dependencies)
--save-dev		// 向开发环境添加依赖(devDependencies)
--production	// 安装包时,只安装生产环境中的依赖包,去掉则2个环境的依赖包都安装

// init
-y 				// 一直yes 、 一直回车

// 简写
install == i
uninstall == un
--save == -S
--save-dev == -D
list == ls
  • 安装包(如果没有指定版本号,安装最新版本)

    • npm install 包名称[@版本号] - 本地安装
    • npm install -g 包名称[@版本号] - 全局安装
    • npm install 包名 --save - 下载并保存依赖项到 dependencies 中(npm@5之后可以省略--save)
    • npm install 包名1 包名2... - 一次性安装多个包(包之间有个空格)
    • npm install 包名 --save-dev - 只安装到 devDependencies中(只在开发阶段使用,上线后不需要)(简写 npm i 包名 -D
    • npm install - 一次性安装 dependencies 中的全部依赖项
  • 更新包(更新到最新版本)(有时并不好使)

    • npm update [-g] 包名称
  • 查看版本

    • npm view 包名 version - 查看最新版本
    • npm view 包名 versions - 查看所有版本
  • 查看所有已安装包

    • npm ls - 查看所有本地安装的包(ls是 list 的简写)
    • npm ls -g - 查看所有全局安装的包(好像不好用
  • 卸载包

    • npm uninstall [ -g ] 包名称
    • npm uninstall 包名称 --save - 删除包的同时也从依赖项删除(npm@5之后可以省略--save)
  • 初始化

    • npm init [-y] 初始化项目的package.json。 -y:表示直接生成默认的package.json,不在命令窗口写入
  • 使用帮助

    • npm help - 查看使用帮助
    • npm 命令 --help - 查看具体命令的使用帮助

案例:安装 i5ting_toc

// 安装
// 15ting_toc:将.md文件转化为带有目录的.html文件
npm install -g i5ting_toc

// 使用
// 使用命令进行转化
// 	-f xxx.md 指定要转化的md文件
//  -o 在浏览器中打开
i5ting_toc -f 文件名.md -o

yarn基本使用

  • 类比npm基本使用
  • 解决了npm中出现的一些问题,如:npm update不好使
// 1. 安装
npm install -g yarn
// 2. 初始化包
npm init [-y]		// -y:安静模式安装
yarn init
// 3. 安装包
npm install 包名 [--save]
yarn add 包名			// 自动加上--save
// 4. 移除包
npm uninstall 包名
yarn remove 包名
// 5. 更新包
npm update 包名
yarn upgrade 包名
// 6. 安装开发依赖的包
npm install 包名 --save-dev
yarn add 包名 --dev
// 7. 安装生产依赖的包
npm install 包名 --save
yarn add 包名
// 8. 全局安装
npm install -g 包名
yarn global add 包名
// 9. 设置下载镜像地址
npm config set registry 镜像地址
yarn config set registry 镜像地址
// 10. 执行包
npm run 具体的命令
yarn run 具体的命令

包的概念

Node.js 中的第三方模块又叫做

不同于 Node.js 中的内置模块与自定义模块,包是由第三方个人或团队开发出来的,免费供所有人使用。

注意:Node.js 中的包都是免费开源的,不需要付费即可免费下载使用。

由于 Node.js 的内置模块仅提供了一些底层的 API,导致在基于内置模块进行项目开发的时,效率很低。

包是基于内置模块封装出来的,提供了更高级、更方便的 API,极大的提高了开发效率

内置模块之间的关系,类似于 jQuery浏览器内置 API 之间的关系。

全球最大的包共享平台: https://www.npmjs.com/

包的使用注意

  • 1、必须通过npm来下载:npm install 包名
  • 2、通过require('包名') 的方式来加载下载的包
  • 3、不允许任何一个第三方包的名字与核心模块的名字一样(审核的时候会过滤)
  • 4、一个项目中有且只有一个 node_modules ,放在项目根目录中

规范的包结构

一个规范的包,它的组成结构,必须符合以下 3 点要求:

  • 1、包必须以单独的目录而存在
  • 2、包的顶级目录下要必须包含 package.json 这个包管理配置文件
  • 3、package.json 中必须包含 nameversionmain 这三个属性,分别代表包的名字版本号包的入口

其他规范(不是必要的)

  • 4、二进制文件应该在bin目录下
  • 5、js代码应该在lib目录下
  • 6、文档应该在doc目录下
  • 7、单元测试应该在test目录下

自定义包

文件目录

mypac
	bin/
    lib/
    doc/
    text/
      
	index.js
    package.json

package.json

{
+  "name": "mypac",
+  "version": "1.0.0",
+  "main": "index.js",
  "scripts": {
    "test": "echo "Error: no test specified" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "description": "",
  "dependencies": {
    "art-template": "^4.13.2",
    "es-checker": "^1.4.2"
  },
  "devDependencies": {}
}

package.json

建议每个项目都要有一个 package.json 文件(包描述文件)

如果把 node_modules 删除了,可以通过 npm install 将保存在package.json中dependencies依赖包一次性安装完成

初始化 package.json

npm init [-y]

字段分析

  • name:包的名称,必须是唯一的,由小写英文字母、数字和下划线组成,不能包含空格
  • description:包的简要说明
  • version:符合语义化版本识别规范的版本字符串
  • main:包的入口文件
  • keywords:关键字数组,通常用于搜索
  • maintainers:维护者数组,每个元素要包含name、email(可选)、web(可选)字段
  • contributors:贡献者数组,格式与maintainers相同。包的作者应该是贡献者数组的第一- 个元素
  • bugs:提交bug的地址,可以是网站或者电子邮件地址
  • licenses:许可证数组,每个元素要包含type(许可证名称)和url(链接到许可证文本的- 地址)字段
  • repositories:仓库托管地址数组,每个元素要包含type(仓库类型,如git)、url(仓- 库的地址)和path(相对于仓库的路径,可选)字段
  • dependencies:生产环境包的依赖,一个关联数组,由包的名称和版本号组成
  • devDependencies:开发环境包的依赖,一个关联数组,由包的名称和版本号组成

package-lock.json

  • 1、npm5以前没有package-lock.json这个文件,npm5之后才加入了这个文件
  • 2、npm5之后的版本在安装包的时候不需要再加 --save 参数,会自动保存到 dependencies
  • 3、当安装包的时候,npm都会自动生成或更新package-lock.json这个文件
  • 4、package-lock.json 这个文件保存node_modules所有包的信息版本下载地址依赖包),这样可以加快npm install 的速度
  • 5、package-lock.json 是用来锁定某个版本的,防止自动升级到新版本

模块的加载机制

模块查找机制,加载顺序

  • 优先从缓存加载
  • 核心模块加载
  • 路径形式的文件模块
  • 第三方模块加载

优先从缓存中加载

模块在第一次加载后会被缓存。 这也意味着多次调用 require() 不会导致模块的代码被执行多次。

注意:不论是内置模块、用户自定义模块、还是第三方模块,它们都会优先从缓存中加载,从而提高模块的加载效率

内置模块的加载机制

内置模块是由 Node.js 官方提供的模块,内置模块的加载优先级最高

例如,require('fs') 始终返回内置的 fs 模块,即使在 node_modules 目录下有名字相同的包也叫做 fs

自定义模块的加载机制

使用 require() 加载自定义模块时,必须指定以 ./../ 开头的路径标识符。在加载自定义模块时,如果没有指定 ./ 或 ../这样的路径标识符,则 node 会把它当作内置模块第三方模块进行加载。

同时,在使用 require() 导入自定义模块时,如果省略了文件的扩展名,则 Node.js 会按顺序分别尝试加载以下的文件:

  • 1、按照确切的文件名进行加载
  • 2、补全 .js 扩展名进行加载
  • 3、补全 .json 扩展名进行加载
  • 4、补全 .node 扩展名进行加载
  • 5、加载失败,终端报错

第三方模块的加载机制

如果传递给 require() 的模块标识符不是一个内置模块,也没有以 ‘./’ 或 ‘../’ 开头,则 Node.js 会从当前模块的父目录开始,尝试从 /node_modules 文件夹中加载第三方模块。

如果没有找到对应的第三方模块,则移动到再上一层父目录中,进行加载,直到文件系统的根目录

例如,假设在 'C:Usersitheimaprojectfoo.js' 文件里调用了 require('tools'),则 Node.js 会按以下顺序查找:

  • 1、C:Usersitheimaproject node_modules ools
  • 2、C:Usersitheima node_modules ools
  • 3、C:Users node_modules ools
  • 4、C: node_modules ools

目录作为模块

当把目录作为模块标识符,传递给 require() 进行加载的时候,有三种加载方式:

  • 1、在被加载的目录下查找一个叫做 package.json 的文件,并寻找 main 属性,作为 require() 加载的入口
  • 2、如果目录里没有 package.json 文件,或者 main 入口不存在或无法解析,则 Node.js 将会试图加载目录下的 index.js 文件。
  • 3、如果以上两步都失败了,则 Node.js 会在终端打印错误消息,报告模块的缺失:Error: Cannot find module 'xxx'

模板引擎

  • 模板引擎的底层原理:字符串的替换

  • 在ECMAScript 6的 `` 字符串中,可以使用 ${变量名} 来引用变量

  • art-template 模板引擎,不仅可以在前端使用,也可以在node中使用

  • 模板引擎不关心字符串的内容,只关心它能认识的模板标记语法:{{}} / <%%>

安装

// 该命令在哪执行就会把包下载到哪里,默认位置:node_modules。最好不要改node_modules目录
npm install art-template

// 在web中需要引入的文件
node_modules/lib/tepmlate-web.js

art-template的API

  • template(filename, data) - 基于模板名渲染模板

    参数

    • filename - 模板文件的路径地址
    • data - 替换对象键值
  • template.compile(source, options) - 将模板源代码编译成函数

    参数

    • source - 模板字符串
    • options -
  • template.render(source, data, options) - 将模板源代码编译成函数并立刻执行

    参数

    • source - 模板字符串
    • data - 替换对象键值
    • options -

服务端渲染

  • 在服务端(nodejs)使用模板引擎(art-template)

  • 模板引擎一开始是诞生于服务器领域,后来才发展到前端

  • 服务端渲染和客户端渲染的区别

    • 客户端渲染不利于SEO(搜索引擎优化)

    • 服务端渲染是可以被爬虫抓取到的,客户端异步渲染是很难被爬虫抓取到的

    • 真正的网站是由服务端渲染和客户端渲染结合实现的(商品列表:服务端渲染,用户评论:客户端渲染)

    • 客户端渲染(2次请求)

    • 服务端渲染(1次请求)

    // npm安装
    npm install art-template
    // 加载art-template
    var template = require('art-template');
    // 使用模板引擎
    var result = template.render('原始{{ name }}字符串', {
      name: 'Tom',
      age: 18
    });
    console.log( result );
    
// js文件
var http = require('http');
var fs = require('fs');
// 1. node中引入模板引擎的方法
var template = require('art-template');	

var server = http.createServer();
server.on('request', function(req, res){
    // 2. 读取模板文件template8.html
    fs.readFile('template8.html', function(err, data){
        if( err ) return res.end('file template8.html not found');
		// 3. template.render(模板文件内容-字符串, 模板中需要替换的参数)
        var newTemplate = template.render(data.toString(), {
            name: 'Tom',
            age: 17,
            address: '四川',
            salary: 800
        });
        // 4. 输出解析替换之后的模板内容
        res.end(newTemplate);
    });
});
server.listen('3000', function(){
    console.log('server running...');
});

// 浏览器
我叫 Tom
今年 17
家住 四川
工资 800

案例:留言本

  • 把当前模块所有的依赖项都声明在文件模块的最上面

  • 为了让目录结构保持统一清晰,约定把所有的HTML文件都放在 views 文件夹内

  • 为了方便统一处理网站中的静态资源,约定将所有的静态资源统一存放在 public 目录中

  • 需要通过代码控制哪些资源能被用户访问,哪些资源不能被用户访问

  • 注意:在服务端中,文件中的路径就不要写 相对路径 了,因为这个时候所有的资源都是通过 url 来获取的。

    ​ 我们的服务器开放了 /public/目录,所以这里的请求路径就都要写成:/public/xxx 。/ 就标识url根路径

    ​ 浏览器在真正发送请求的时候会自动把http://127.0.0.1:3000给拼接上

  • 表单提交

    • 表单中需要提交的表单控件元素必须具有 name 属性

    • 表单提交分为:

      ​ 默认的提交行为

      ​ 表单异步提交

  • 通过服务器让客户端重定向

    • 状态码设置为:302(临时重定向)

      response.statusCode = 302

    • 在响应头中通过 Location 告诉客户端往哪重定向

      response.setHeader('Location', '/')

    • 客户端发现收到的服务器响应的状态码是302,就会自动去响应头中找 Location ,然后就能自动跳转了

  • 一次请求对应一次响应,响应结束这次请求也就结束了

app.js

/* 入口js */
let fs = require('fs');
let url = require('url');
let http = require('http');
let template = require('art-template');

let comments = [
    { username: '张三', message: '今天星期二!', dateTime: '2021-01-26 16:41:47' },
    { username: '李四', message: '你吃了吗?', dateTime: '2021-01-26 16:42:47' },
    { username: '王五', message: '还没有吃啊!', dateTime: '2021-01-26 16:43:47' },
    { username: '刘七', message: '是么', dateTime: '2021-01-26 16:44:47' }
];

http.createServer((req, res)=>{
    let oUrl = url.parse(req.url, true);
    let urlPathName = oUrl.pathname;
    let urlQuery = oUrl.query;
    
    // 以下是显示页面
    if( urlPathName === '/' ){
        fs.readFile('./views/index.html', (err, data)=>{
            if(err) return console.log( 'ERROR: index.html' );
            // 动态渲染
            let newTemplate = template.render(data.toString(), {
                comments: comments
            });
            res.end(newTemplate);
        });
    }else if ( urlPathName === '/post' ){
        fs.readFile('./views/post.html', (err, data)=>{
            if(err) return console.log( 'ERROR: post.html' );
            res.end(data);
        });
    }else if ( urlPathName.indexOf('/public/') === 0 ){
        fs.readFile('.' + urlPathName, (err, data)=>{
            if(err) return console.log( 'ERROR: ' + urlPathName );
            res.end(data);
        });
    // 以下是处理前端传递的数据
    }else if( urlPathName === '/post_submit' ){
        let comment = {};
        comment = urlQuery;
        comment.dateTime = dateFormat(Date.now());
        console.log(comment);
        comments.unshift( comment );

        // 页面跳转到首页
        res.statusCode = 302;
        res.setHeader('Location', '/');
        res.end();
    }else {
        fs.readFile('./views/404.html', (err, data)=>{
            if (err ) return console.log( '404 Not Found' );
            res.end(data);
        });
    }
}).listen('3000',()=>{
    console.log('server running...
');
});
// 转化时间格式为:2021-1-26 17:12:56
dateFormat = (timestamp) => {
    let d = new Date(timestamp);
    return d.getFullYear() + "-" + d.getMonth()+1 + "-" + d.getDate() + " " + d.getHours() + ":" + d.getMinutes() + ":" + d.getSeconds();
}

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>留言本</title>
    <link rel="stylesheet" href="/public/lib/bootstrap/dist/css/bootstrap.min.css">
</head>
<body>
    <div class="container">
        <h3>留言本</h3>
        <a class="btn btn-primary" href="/post">发表评论</a>
        <div class="list-group">
            {{ each comments }}
                <li class="list-group-item"><span>{{ $value.username }}:</span> {{ $value.message }} <em class="float-right">{{ $value.dateTime }}</em></li>
            {{ /each }}
        </div>
    </div>
</body>
</html>

post.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>发表评论</title>
    <link rel="stylesheet" href="/public/lib/bootstrap/dist/css/bootstrap.min.css">
</head>
<body>
    <div class="container">
        <h3>发表评论</h3>
        <form action="/post_submit" method="get">
            <div>
                <label for="username">用户名:</label>
                <input class="form-control" type="text" id="username" name="username" placeholder="请输入用户名" minlength="2" maxlength="20"><br>
            </div>
            <div>
                <label for="message">评论内容:</label><br>
                <textarea name="message" id="message" cols="155" rows="10" minlength="2"></textarea>
            </div>
            <div>
                <input class="btn btn-danger" type="submit" value="发表评论">
            </div>
        </form>
    </div>
</body>
</html>

404.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>404</title>
</head>
<body>
    <h3>您要找的页面已经被狗子吃了!</h3>
</body>
</html>

Node.js操作数据库

Mysql环境准备

mysql第三方包基本使用

  • 操作数据库基本步骤
  • 实现增删改成基本操作

基于数据库实现登录功能

Express整合数据库实现增删改查业务

后台API开发

  • 基于数据库的json接口开发
  • 基于数据库的jsonp接口开发

图书管理系统整合数据库

后台接口开发

  • json接口
  • jsonp接口
  • restful接口

基于Ajax与后台接口的前端渲染案例

  • 前端渲染与后端渲染分析

从服务器主动发送请求

  • 基于核心模块主动发送请求

调用第三方接口

  • 针对第三方接口进行二次封装
  • 扩展天气查询功能

Node学习路径图

原文地址:https://www.cnblogs.com/pikachu/p/14668411.html