Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nodejs 初学 #12

Open
Jsmond2016 opened this issue Nov 9, 2021 · 0 comments
Open

Nodejs 初学 #12

Jsmond2016 opened this issue Nov 9, 2021 · 0 comments
Labels

Comments

@Jsmond2016
Copy link
Owner

Nodejs 是什么

  • 本质上是一个 JavaScript 的解析器
  • 是 JavaScript 的运行环境
  • 是一个服务器程序
  • 本身是使用的 V8 引擎
  • 不是 web 服务器

为什么要使用 Nodejs

  • 为了提供高性能的 web 服务
  • IO 性能强大(重点)
  • 事件处理机制(核心)
  • 天然能够处理 DOM
  • 社区活跃,生态圈日趋完善

Nodejs 的优势

  • 处理大量数据
  • 适合实时交互的应用
  • 完美支持对象数据库(MongoDB)
  • 异步处理大量并发连接

学习 Nodejs 前置知识

  • JavaScript
  • ES6
  • 一些服务器知识
  • 最好在 Linux 系统下进行开发

相关资料

  • Nodejs 官网 学习文档和下载安装包
  • 文档 说明,有 2 个版本
    • v6.11 版本 长期支持,稳定版,适用于生产环境
    • v8.4.0 最新版,适用于开发

安装 Nodejs

  • 官网下载
  • 说明:ARM 版本 为手机或者平板的版本
  • NPM 包管理器
    • 允许用户从 npm 服务器下载别人编写的三方包到本地使用
    • 允许用户从 npm 服务器下载并安装别人编写的命令
    • 允许用户将自己编写的包或者命令行程序上传到 npm 服务器供别人使用
  • npm 淘宝镜像:
    • 地址:npm.taobao.org
    • 或者可以使用 cnpm 进行安装
    • 需要先安装 cnpm:npm install cnpm -g
  • 创建第一个 nodejs 服务
var http = require('http')

http.createServer(function (req, res) {
    res.writeHead(200, {'Content-Type': 'text/plan'}) // 返回状态值为 200,表示相应成功
	res.end('Hello, world! \n')
})
.listen(8000) // 服务运行起来在 8000 端口上
console.log('server is running')

// 本地访问地址

localhost:8000

// Hello, world!

Nodejs REPL 环境

ctrl + c 退出当前终端
ctrl + c 按下2次 退出当前终端

ctrl + d 退出 node repl
向上/向下  查看输入历史命令

tab  列出当前命令
.help 提示相关命令
.clear 清除历史命令
.exit 退出
.save 保存
.load 保存
.break 退出
.editor 编辑
  • npm 更新:npm install npm -g
  • npm 安装和卸载
    • npm/cnpm install
    • npm/cnpm uninstall

什么是 回调,阻塞和非阻塞

  • 函数调用的方式分为三类:
    • 同步调用
    • 异步调用
    • 回调
  • 回调是一种双向调用方式:被调函数反过来或调用它的主调函数
  • 可以使用回调函数来调用回调

  • 阻塞和非阻塞关注的是等待调用的结果(消息,返回值)时的状态
  • 阻塞就是做不完不准回来
  • 非阻塞就是你先做,我看看有其他的事情没有,完了告诉我一声

阻塞式代码:

// bash 窗口
echo 'ABCDEFG' >> data.txt


// node文件
var fs = require('fs')

// 读取到一个本地文件的内容
var data = fs.readFileSync('data.txt')

console.log(data.toString())
// ABCDEFG

非阻塞式的代码:

// bash 窗口
echo 'ABCDEFG' >> data.txt


// node文件
var fs = require('fs')

fs.readFile('data.txt', function (err, data) {
    // 我是一个回调函数
    if (err){
        return console.err(err)
    }
    console.log(data.toString())
})
console.log('程序执行完毕')

// output
// 程序执行完毕
// ABCDEFG

Nodejs 事件驱动模型

观察者模式

event emitters --> events queue -- > (event Loop) --> event Handlers

核心: event Loop

  • 事件处理 代码,步骤
    • 引入 events 对象,创建 eventEmitter 对象
    • 绑定事件处理程序
    • 触发事件
// 引入 event 模块并创建 eventsEmitter 对象
var events = require('events')
var eventEmitter = new events.EventEmitter();

// 绑定事件处理函数
var connectHandler = function connected() {
    console.log('connected 被调用!')
}

eventEmitter.on('connection', connectHandler)
// 触发事件
eventEmitter.emit('connection');

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

// 结果
// connected 被调用!
// 程序执行完毕

Nodejs 模块化和模块加载方式

  • 为了让 nodejs 的文件可以互相调用,nodejs 提供了一个简单的模块系统
  • 模块式 Nodejs 应用程序的基本组成部分
  • 文件和模块是以一一对应的,一个 Nodejs 文件就是一个模块
  • 该文件可能是 js 代码,json 或者编译过的 c/c++ 拓展
  • nodejs 中存在4类模块(原生模块和3种文件模块)

nodejs 模块加载流程

注意:文件缓存区和原生模块缓存不是同一个东西

  • nodejs 模块加载方式有几种
    • 从文件模块缓存中加载
    • 从原生模块加载
    • 从文件加载
  • require 方法加载
    • require 方法接收一下几种参数传递
    • http, fs, path 等原生模块
    • ./mod../mod ,相路径的文件模块(建议使用)
    • /pathtomode/mod 绝对路径的文件模块
    • mod,非原生模块的文件模块
  • 模块代码编写:

hello 模块

// Hello 模块
function Hello () {
    var name
    this.setName = function (argName) {
        name = argName
    }
    this.sayHello = function () {
        console.log('Hello' + name)
    }
}

// 导出模块
module.exports = Hello

main 模块

var Hello = require('./hello')

var hi = new Hello()

hi.setName('Hj')
hello.sayHello()

// node main.js
// output
// Hello Hj

关于 module.exports = Hello, export.start 的区别和使用,推荐阅读资料:module.exports、exports、export、export default之间的关系和区别

Nodejs 中的函数

匿名函数和具名函数

// 同样的功能不同的实现方式
// 匿名函数
var http = require('http')

http.createServer(function (req, res) {
    res.writeHead(200, {'Content-Type': 'text/plan'}) // 返回状态值为 200,表示相应成功
	res.end('Hello, world! \n')
})
.listen(8000) // 服务运行起来在 8000 端口上
console.log('server is running')

// 具名函数-- 先定义后传递
var http = require('http')

function onRequest(req, res) {
    res.writeHead(200, {'Content-Type': 'text/plan'}) // 返回状态值为 200,表示相应成功
	res.end('Hello, world! \n')
}

http.createServer(onRequest).listen(8000) // 服务运行起来在 8000 端口上
console.log('server is running')

Nodejs 路由

参考:nodejs路由-菜鸟教程

了解下路由 模块 和简单实用:

var http = require("http");
var url = require("url");
 
function start() {
  function onRequest(request, response) {
    var pathname = url.parse(request.url).pathname;
    console.log("Request for " + pathname + " received.");
    response.writeHead(200, {"Content-Type": "text/plain"});
    response.write("Hello World");
    response.end();
  }
 
  http.createServer(onRequest).listen(8888);
  console.log("Server has started.");
}
 
exports.start = start;

利用 url 模块,改造成一个具备路由功能的 server

新建一个 http.js 文件

var http = require('http')
var url = require('url')

var start = function(routeFn) {

	http.createServer(function(req, res) {

	const path = url.parse(req.url).pathname
	
	console.log('Request route is:' + path)
	routeFn(path, res)
	
	res.writeHead(200, {'Content-Type': 'text/plan'})
	res.write('hello, world')
	res.end()
})
.listen(8000)

console.log('Server has started---')

}

exports.start = start

新建一个 route.js ,具备简单路由功能

function route (path, response) {
	if (path === '/') {
		response.writeHead(200, {'Content-Type': 'text/plain'})
		response.write('Hello, world')
		response.end()
	} else if (path === '/index/home') {
		response.end('index')
	} else {
		response.end('404')
	}
}

exports.route = route

新建一个 app.js

var http = require("http");
var url = require("url");
 
function start() {
  function onRequest(request, response) {
    var pathname = url.parse(request.url).pathname;
    console.log("Request for " + pathname + " received.");
    response.writeHead(200, {"Content-Type": "text/plain"});
    response.write("Hello World");
    response.end();
  }
 
  http.createServer(onRequest).listen(8888);
  console.log("Server has started.");
}
 
exports.start = start;

运行 node app.js ,浏览器输入对应的路由,即可预览效果

localhost:8000/index/home

localhost:8000/abc

localhost:8000

Nodejs 的 GET 和 POST 请求

获取GET 请求 内容

使用:

  • util.inspect 是一个将任意对象转换 为字符串的方法,通常用于调试和错误输出。它至少接受一个参数 object,即要转换的对象。
  • url.parse 解析 url 的参数为 url 实例对象,但是不能直接返回给浏览器
var http = require('http');
var url = require('url');
var util = require('util');
 
http.createServer(function(req, res){
    res.writeHead(200, {'Content-Type': 'text/plain; charset=utf-8'});
    res.end(util.inspect(url.parse(req.url, true)));
    // res.end(url.parse(req.url, true))
    // 报错:TypeError [ERR_INVALID_ARG_TYPE]: The "chunk" argument must be of type string or an instance of Buffer. Received an instance of Url

}).listen(3000);

访问 http://localhost:3000/user?name=Hj&url=www.test.com 页面结果:

Url {
  protocol: null,
  slashes: null,
  auth: null,
  host: null,
  port: null,
  hostname: null,
  hash: null,
  search: '?name=Hj&url=www.test.com',
  query: [Object: null prototype] { name: 'Hj', url: 'www.test.com' },
  pathname: '/user',
  path: '/user?name=Hj&url=www.test.com',
  href: '/user?name=Hj&url=www.test.com'
}

获取 URL 参数

使用 url.parse 来解析 url 的参数,代码为

var http = require('http');
var url = require('url');
var util = require('util');
 
http.createServer(function(req, res){
    res.writeHead(200, {'Content-Type': 'text/plain;charset=utf-8'});
 
    // 解析 url 参数
    var params = url.parse(req.url, true).query;
    res.write("网站名:" + params.name);
    res.write("\n");
    res.write("网站 URL:" + params.url);
    res.end();
 
}).listen(3000);

同样,输入 http://localhost:3000/user?name=Hj&url=www.test.com ,得到结果为

网站名:Hj
网站 URL:www.test.com

获取 POST 请求内容

说明:POST 请求的内容全部都在请求体中,http.ServerRequest没有一个属性内容为请求体

原因:等待请求体传输是一件耗时的工作,比如上传文件等,避免 恶意的 post 请求占用cpu资源,nodejs 并不会处理post 请求体,需要手动处理。

需要用到知识点:

  • req.on('data') 监听 req 的 data 的事件监听函数,接收到请求体的数据,累加到 data 中
  • req.on('end') 当 post 的请求体结束传输,触发该函数
var http = require('http');
var querystring = require('querystring');
var util = require('util');
 
http.createServer(function(req, res){
    // 定义了一个post变量,用于暂存请求体的信息
    var post = '';     
 
    // 通过req的data事件监听函数,每当接受到请求体的数据,就累加到post变量中
    req.on('data', function(chunk){    
        post += chunk;
    });
 
    // 在end事件触发后,通过querystring.parse将post解析为真正的POST请求格式,然后向客户端返回。
    req.on('end', function(){    
        post = querystring.parse(post);
        res.end(util.inspect(post));
    });
}).listen(3000)

编写一个简单的 表单的例子

var http = require('http');
var querystring = require('querystring');
 
var postHTML = 
  '<html><head><meta charset="utf-8"><title>菜鸟教程 Node.js 实例</title></head>' +
  '<body>' +
  '<form method="post">' +
  '网站名: <input name="name"><br>' +
  '网站 URL: <input name="url"><br>' +
  '<input type="submit">' +
  '</form>' +
  '</body></html>';
 
http.createServer(function (req, res) {
  var body = "";

  req.on('data', function (chunk) {
    body += chunk;
  });
  
  req.on('end', function () {
    // 解析参数
    body = querystring.parse(body);
    // 设置响应头部信息及编码
    res.writeHead(200, {'Content-Type': 'text/html; charset=utf8'});
 
    if(body.name && body.url) { // 输出提交的数据
        res.write("网站名:" + body.name);
        res.write("<br>");
        res.write("网站 URL:" + body.url);
    } else {  // 输出表单
        res.write(postHTML);
    }
    res.end();
  });
}).listen(3000);

访问:localhost:3000 ,输入表单信息后,得到结果

Nodejs 全局对象

Global Object 类似 window

__filename

__dirname

setTimeout

clearTimeout

setInterval

console

process

Nodejs 工具模块

用的比较少,更推荐使用 underscore.js

Nodejs 的文件系统

资料参考:Nodejs 文件系统-菜鸟教程

同步和异步

  • 准备:echo 'hello, world!' >> input.txt

  • 异步数据在 回调中获取读取数据

var fs = require('fs')

// 异步读取
fs.readFile('input.txt', function (err, data) {
if (err) {
	return console.info(err)
}
console.log('异步读取数据成功', data.toString())
})
  • 同步读取数据,直接获取数据
var data = fs.readFileSync('input.txt')
console.log('同步读取数据成功', data.toString())

打开文件

  • 代码为:fs.open(path, flags[, mode], callback)
  • 参数使用说明如下:
    • path - 文件的路径。
    • flags - 文件打开的行为。具体值详见下文。
    • mode - 设置文件模式(权限),文件创建默认权限为 0666(可读,可写)。
    • callback - 回调函数,带有两个参数如:callback(err, fd)。第二个fd为一个整数,表示打开文件返回的文件描述符。
  • demo 代码
var fs = require('fs')

console.log('异步打开文件')
fs.open('input.txt', function(err, fd) {
	if (err) {
		return console.log(err)
	}

	console.log('文件打开成功', fd)
})

// 开始打开文件
// 文件打开成功 19

推荐学习资料:node.js fs.open 和 fs.write 读取文件和改写文件

关闭文件

  • fs.close(fd, callback) 回调只有 err 一个参数
  • 代码:
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("文件关闭成功");
      });
   });
});

获取文件信息

  • fs.stat(path, callback) 异步获取文件信息
  • 参数使用说明如下:
    • path - 文件路径。
    • callback - 回调函数,带有两个参数如:(err, stats), stats 是 fs.Stats 对象
  • demo
var fs = require('fs')

fs.stat('./input.txt', function (err, stats) {
	if (err) {
		return console.log(err)
	}

	console.log(stats.isFile())
})
  • 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。

读写文件

  • fs.writeFile(file, data[, options], callback)
  • fs.read(fd, buffer, offset, length, position, callback)
  • 文件写入,代码
var 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());
   });
});
  • 文件读取,代码:
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());
      }
   });
});

截取文件

  • fs.ftruncate(fd, len, callback) 异步模式截取文件,和异步读取文件方式类似,对比如下:
  • fs.read(fd, buffer, offset, length, position, callback)
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("文件关闭成功!");
         });
      });
   });
});

删除文件

  • fs.unlink(path, callback) 删除文件,callback 没有参数

代码:

var fs = require("fs");

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

创建目录

  • fs.mkdir(path[, options], callback)
  • 参数使用说明如下:
    • path - 文件路径。
    • options 参数可以是:
      • recursive - 是否以递归的方式创建目录,默认为 false。
      • mode - 设置目录权限,默认为 0777。
    • callback - 回调函数,没有参数。

代码为:

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

读取目录

  • fs.readdir(path, callback)
  • 参数使用说明如下:
    • path - 文件路径。
    • callback - 回调函数,回调函数带有两个参数err, files,err 为错误信息,files 为 目录下的文件数组列表。
  • 代码为:
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 );
   });
});

删除目录

  • fs.rmdir(path, callback)
  • 参数使用说明如下:
    • path - 文件路径。
    • callback - 回调函数,没有参数。
  • 代码为:
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 );
      });
   });
});

更多文件模块使用方法,参考:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant