Node.js 1.关于 Node.js Node.js是一个让 JavaScript 运行在服务器端的开发平台.
1.1. 介绍 1). Node.js 不是一种独立的语言,与PHP、JSP、Python、Perl、Ruby的”既是语言,也是平台”不同,Node.js的使用JavaScript进行编程,运行在JavaScript引擎上(V8)。
2). 与PHP、JSP等相比,Node.js跳过了Apache、Naginx、IIS等HTTP服务器,它自己不用建设在任何服务器软件之上。Node.js的许多设计理念与经典架构(LAMP)有着很大的不同,可以提供强大的伸缩能力。
官网: https://nodejs.org/en/ 中文网: http://nodejs.cn/
1.2. 特点
1). 单线程 1 2 3 4 所有客户端请求的连接 都使用一个线程来处理. Node.js 不是给每个连接去创建新的 线程,而是仅仅使用一个线程来处理. 单线程带来的好处,减少内存的损耗,提高并发量. 操作系统完全不再有线程创建和销毁的开销.
2). 非阻塞I/O 1 2 3 4 5 6 I/O操作不会阻塞程序的运行 I: Input 输入 O: Output 输出 在阻塞模式下,一个线程只能处理一项任务,想要提高吞吐率,必须通过多线程 而非阻塞模式下,一个线程 永远在执行某种运算操作.这个线程的CPU 核心利用率永远是满载的
3). 事件驱动 1 2 客户端 请求建立连接,提交数据等行为,就会触发 相应的事件. 在Node中,在一个时刻,只能执行 一个事件回调函数,但是在执行 一个事件回调函数的中途,(比如,又有新用户连接了)可以转而处理其他事件,然后返回继续执行原事件的回调函数,这种处理机制,称为“事件环”机制.
Node.js底层是C++(V8引擎也是C++写的).底层代码中,近半数都用于事件队列、回调函数队列的构建.用事件驱动 来完成服务器的 任务调度,是Node.js中 真正底层核心逻辑.
4). 三特点说明 单线程 是为了减少内存的开销,操作系统的内存换页(创建/销毁) 但是,如果某一个请求 有I/O操作,单线程就会被阻塞了! 非阻塞I/O 程序不会傻等I/O语句的执行结束,才继续后续代码的运行,而会直接运行后续代码. 但是,非阻塞就能完美的解决问题吗, 比如 小A的业务执行I/O过程中,有小C要新的请求,此时则么办? 事件驱动(事件环) 不管是新用户的请求,还是老用户的I/O操作,都将以事件的方式加入到事件环之中,等待调度.
Node.js 中所有的I/O都是异步的.回调函数 套 回调函数 泡茶 : 洗茶壶 4 / 洗茶杯 3 / 用茶壶烧开水 8 / 准备茶叶 2 / 泡茶水 5 / 倒茶水 1 4 + 8 + 5 + 1 = 18 3 + 2
1.3 Node.js 的优缺点,适合开发什么? 1). 优点 善于I/O,不善于 大量计算 处理高并发 服务器推送
2). 缺点 单线程的缺点: 单一线程 一旦奔溃则整个服务全奔溃 服务不是绝对可靠的.
3). 适用场景 1 2 3 4 5 6 7 8 9 不能完全替代 传统的后端语言,但在某些方面优于传统. 当应用程序需要处理大量并发的I/O操作,而在发出响应之前,应用程序内部 并不需要进行非常复杂的计算处理的时候,Node.js非常适合。 Node.js也非常适合与web socket配合,开发长连接的实时交互应用程序。 - 聊天室 - 图文直播 - 考试系统 - 收集用户数据的表单 - 提供JSON的API
2.安装使用node 2.1 windows环境安装 node -v 查看版本
node 文件名 运行文件
2.2 搭建Node.js服务器 – 首个nodejs页面页面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 // 搭建nodejs WEB服务器 // 引入HTTP模版 var http = require('http'); var hostname = '127.0.0.1'; var port = 3000; // 创建服务器 // req 表示请求(request) / res 表示响应(response) var server = http.createServer(function(req, res){ // 设置HTTP头信息. 状态码 200; 文件类型是html; 字符集 utf-8 res.writeHead(200, {'Content-type':'text/html;charset=UTF-8'}); // 服务器响应输出完成 res.end('Hi~ o(* ̄▽ ̄*)ブ, 首个nodejs页面,您的积分为: ' + (50 + 10) + '分'); }); // 运行服务器 server.listen(port, hostname, function(){ console.log(`请访问: http://${hostname}:${port}`); });
如果修改了程序代码,必须中断当前的node服务,重新在node一次
node.js是服务器的程序,写好的JS代码,都将运行在服务器上.返回给客户端的, 都是已经处理好的结果
node就是一个JS的运行环境
js文件是不能直接拖入浏览器取运行的,必须靠node去执行
2.3 根目录 与 web容器 1 1). Node.js没有根目录的概念,因为它根本没有任何的web容器!
1 2 3 4 2). 静态页面的呈现 URL和真实的物理文件之间 是没有关系的。 URL是通过 Node 的路由设计之后,才呈现出 某一个静态文件的
由此,我们可以看出,要是使用nodejs 搭建服务器, 是需要写 各种回调函数,定义 各种路由规则,来实现 页面的显示. nodejs中就是 回调函数 套回调函数,每个回调函数 都是一个事件
由此就用到了 nodejs的三个特性: 单线程 | 非阻塞I/O | 事件驱动(事件环) URL路由规则,与实际的物理文件,不一定有直接的联系.
3.HTTP模块 Node.js将各种功能,都划分为了一个个mudule(模块) 需要用什么模块,就可以使用require(‘’)来引入使用
4.URL模块 解析URL
1 2 3 4 5 6 // GET 参数的处理 var getParams = url.parse(req.url, true).query; var name = getParams.name; var age = getParams.age; var sex = getParams.sex; res.end('服务器接收到了以下信息: '+ name + ' | ' + age + ' | ' + sex);
路由设计
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 var http = require('http'); var url = require('url'); var hostname = '127.0.0.1'; var port = 3000; // 创建服务器 var server = http.createServer(function(req, res){ // 跳过了 chrome 的收藏夹图标的请求 if (req.url == '/favicon.ico') return; res.writeHead(200, {'content-type': 'text/html;charset=utf-8'}); // /stu/20190212007 查询学员 // /tch/00128 查询老师 // 获取URL中的path部分 var user = url.parse(req.url).pathname; // substr if (user.substr(0, 5) == '/stu/') { var stuid = user.substr(5); // console.log(stuid); if (/^\d{11}$/.test(stuid)) { res.end('查询到的学号是: ' + stuid); } else { res.end('学员的学号有误!!!'); } } else if (user.substr(0, 5) == '/tch/') { var tchid = user.substr(5); // console.log(tchid); if (/^\d{5}$/.test(tchid)) { res.end('查询到的工号是: ' + tchid); } else { res.end('工号有误!!!'); } } else { res.end('URL有误,请检查重试!!!'); } }); // 运行服务器 server.listen(port, hostname);
5.文件系统 5.1. 新建文件夹 / 删除文件夹 / 文件状态信息 / 读取文件夹
5.2. 读取出 文件夹/文件
6.静态目录 文件加载 (web容器) 7.模块的概念 在Node.js中,不可能用一个js文件去写全部的业务,肯定要有MVC. 它以模块为单位 划分所有功能,并且提供了一个 完整的模块加载机制,我们可以将应用程序 划分为各个不同的部分.
每一个JavaScript文件都是一个模块; 而多个JavaScript文件可以使用require引入,使他们共同实现了一个功能模块.
7.1 输出变量/函数 Node.js中,JS文件中定义的变量、函数,都只在这个文件内部有效. 其他文件中需要引用变量、函数时,必须使用exports对象进行暴露(输出). 使用者要用require()命令,引用执行这个JS文件.
7.2 输出一个类(构造函数) 可以用module.exports = 构造函数名;的方式 向外输出一个类
7.3 模块关联关系
某一个js文件中,有函数或变量: exports.变量 = 变量;
某一个js文件中,有一个类: module.exports = 构造函数名;
7.4 模块封装 8.npm (node package management)
这是一个工具名字.npm的主要职责是 安装开发包和管理依赖项. 安装开发包:安装 npm install命令;更新 npm update命令. 管理依赖项:借助 package.json 文件;最简单生成 package.json 的方法就是 npm init
npm不需要单独安装,只要安装了 Node.js 环境,npm 就已经包含在里面了. 查看 npm 版本: npm -v
为什么要使用npm? 开发时,会使用到各种功能的组件,所有组件都由我们自己来写代码的话,开发效率就会很低.我们不要重复的去造轮子,要学会使用已有的工具,来完善我们的项目,站在巨人的肩膀上去工作. npm是js世界里的一个伟大的社区,能够让开发者更加轻松的共享代码和共用代码片段或模块组件.
https://www.npmjs.com/ # npm官网 https://npm.taobao.org/ # 淘宝npm镜像
不要修改 [node_modules] + package-lock.json 这两个文件,因为它是使用npm去管理的
9.POST请求 相比较GET请求,POST请求比较复杂。 因为Node.js认为,使用POST请求时,数据量会比较多。 为了追求极致的效率,它将数据拆分成为了众多小的数据块(chunk),然后通过特定的事件,将这些小数据块有序传递给回调函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 var http = require('http'); var fs = require('fs'); var querystring = require('querystring'); var hostname = '127.0.0.1'; var port = 3000; var server = http.createServer(function(req,res){ if (req.url == '/form') { // 读取表单加载页面 fs.readFile('./form.html', function(err, data){ res.writeHead(200, {'content-type': 'text/html;charset=UTF-8'}); res.end(data); }); } else if (req.url == '/dopost' && req.method.toLocaleLowerCase() == 'post') { // 如果访问/dopost,且请求类型是post // 进行POST数据处理 var postData = ''; // node为了实现极致的效率,所以把post分成多个小份去传递 req.addListener('data', function (chunk){ postData += chunk; }); // 全部接收完毕 req.addListener('end', function(){ console.log(postData); // 将post字串转换为一个对象 var dataObj = querystring.parse(postData); console.log(dataObj); console.log(dataObj.name); res.end('POST DATA Success!'); }); } else { res.end('404'); } }); server.listen(port, hostname);
10.文件上传处理 原生写POST处理,比较复杂,要写两个监听.
文件上传业务比较麻烦.所以,用第三方模块: `formidable`
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 var http = require('http'); var fs = require('fs'); var querystring = require('querystring'); var formidable = require('formidable'); // 处理表单数据 var timestamp = require('time-stamp'); // 时间 var path = require('path'); // 时间 var hostname = '127.0.0.1'; var port = 3000; var server = http.createServer(function(req,res){ if (req.url == '/form') { // 读取表单加载页面 fs.readFile('./form_file.html', function(err, data){ res.writeHead(200, {'content-type': 'text/html;charset=UTF-8'}); res.end(data); }); } else if (req.url == '/dopost' && req.method.toLocaleLowerCase() == 'post') { // 如果访问/dopost,且请求类型是post // formidable 处理 parse a file upload var form = new formidable.IncomingForm(); // 设置上传目录 form.uploadDir = "./uploads"; // 该模块已经将 POST数据 和 文件数据 分离处理 // 当代码执行到parse()方法的回调函数时,表单中的数据 就都已经处理好了 form.parse(req, function(err, fields, files) { // console.log(fields); // console.log(files); // 处理上传文件的存储 // 新文件名: 时间 + 随机数 + 后缀 var t = timestamp('YYYYMMDDHHmmss'); var ran = parseInt(Math.random()* 100000); var extname = path.extname(files.myfile.name); // 旧文件名 var oldPath = './' + files.myfile.path; // 新文件名 var newPath = './uploads/' + t + ran + extname; console.log(oldPath); console.log(newPath); // 实现改名 fs.rename(oldPath, newPath, function (err){ res.end('文件上传成功!!'); }); }); } else { res.end('404'); } }); server.listen(port, hostname);
11.ejs模版 https://ejs.co/ #官网
https://www.npmjs.com/package/ejs #npm上的ejs包
ejs是Embedded JavaScript templates的简称,意思是嵌入式JavaScript模板.node中的后台模版.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 var http = require('http'); var ejs = require('ejs'); var fs = require('fs'); var hostname = '127.0.0.1'; var port = 3000; var server = http.createServer(function (req, res) { if (req.url == '/') { fs.readFile('./views/index.ejs', function (err, data) { // 模版 // console.log(data); var template = data.toString(); // console.log(template); // 数据 var dict = { title: 'EJS 模版的使用', content: '我是段落内容....', pic: './imgs/1.jpg', songci : { title: '酒调歌头', list : [ '12345', '67890', '09876', '54321' ] } } // 绑定数据 var html = ejs.render(template, dict); // 显示输出页面 res.writeHead(200, {'content-type':'text/html'}); res.end(html); }); } else if (req.url == '/imgs/1.jpg') { fs.readFile('./imgs/1.jpg', function (err, data) { res.writeHead(200, {'content-type':'image/jpg'}); res.end(data); }); } }); server.listen(port, hostname);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 <!--index.ejs--> <!DOCTYPE html> <html lang="cn"> <head> <meta charset="UTF-8"> <title>Document</title> <style type="text/css"></style> </head> <body> <h1><%= title %>></h1> <hr> <p><%= content %>></p> <p><%= content %>></p> <p><%= content %>></p> <p><%= content %>></p> <hr> <br> <img src="<%= pic %>" width="300"> <hr> <h2><%= songci.title %></h2> <ul> <% for (var i = 0; i < songci.list.length; i++) { %> <li><%= songci.list[i] %></li> <% } %> </ul> </body> </html>
PS:V8引擎说明 V8 JavaScript引擎是 Google用于其Chrome浏览器的底层JavaScript引擎。 Google使用V8创建了一个用C++编写的超快解释器,该解释器拥有另一个独特特征: 您可以下载该引擎并将其嵌入任何应用程序。 V8 JavaScript引擎并不仅限于在一个浏览器中运行。 因此,Node.js实际上会使用Google编写的V8 JavaScript引擎,并将其重建为可在服务器上使用。