博客说明
文章所涉及的资料来自互联网整理和个人总结,意在于个人学习和经验汇总,如有什么地方侵权,请联系本人删除,谢谢!
1、什么是nodejs
Node.js是一个基于V8引擎的JavaScript运行环境。Node. js是一个事件驱动、非阻塞式I/O的模型,轻量而又高效;Node. js的包管理器npm是全球最大的开源库生态系统。
2、为什么出现Node.js?Node.js解决了什么问题?
背景:
node是为了写高性能web服务而生的。一个高性能服务器应该是满足“事件驱动,非阻塞 I/O”模型的。首先第一点,nodejs的开发者Ryan 发现 JS 语言本身的特点就是事件驱动并且是非阻塞 I/O 的,跟他的思路正是绝配。第二点,Chrome 的 JS 引擎,也就是 V8 引擎是开源的,而且性能特别棒。于是 Ryan 就基于 V8 开发了 Node.js 。,Node.js 是 C++ 开发的,一个基于 Chrome V8 引擎的 Javascript 运行环境,或者说是一个 JS 语言解释器。
Web 前端的 JS 代码最终还是运行在浏览器中的,在产品环境下,不依赖于 Node.js 。但是,Node.js 诞生以后,前端大爆发,类似 React/Vuejs 这样的前端框架的开发环境变得非常强大和负责,Node.js 是这些开发环境运行的基础。
- NodeJS可以让Javascript提供服务器端的功能,从而使得客户端和服务器端的开发都统一在Javascript之下。明显的就是,对于一个对象,如Person Profile的验证不需要客户端和服务器端实现两套。
- NodeJS是基于事件的,不像已有的Web服务器,是基于线程的,即每个请求Request都有一个独立的线程为之服务,这样的情况下一旦同时请求很多,则线程很多,每个线程都需要有各种开销,如内存,CPU等。由于每个请求主要消耗的时间都在访问数据库、文件之类的IO操作,这些操作都是同步的,线程都得等。再则线程之间的切换也是需要时间。总之这种线程模型会导致服务器会变慢。而NodeJS则是事件驱动(event driven),只有一个主进程接收各种请求,每个请求只是简单分配,这个很快,当处理耗时的IO操作时,NodeJS采用异步的方式,也就是说主进程不会等IO操作完成,而是直接返回继续进行请求分配,等IO操作完成的时候再通知主进程进行处理。
3、为什么JavaScript是单线程?
- 防止DOM渲染冲突的问题;
- Html5中的Web Worker可以实现多线程,但是子线程必须受主线程控制,且不得操作dom,所以这个标准并没有改变js单线程的本质。
进程是操作系统分配资源的单位,线程是调度的基本单位,线程之间共享进程资源
JavaScript是单线程,但是一个进程有多个线程。当你打开浏览器的一个页面,其实就是新创建了一个进程,这个进程包括很多线程,比如ui渲染线程、js引擎线程、http请求线程。
4、深入理解nodejs
Node.js 主线程是单线程的,Node.js 程序并非「单线程」。
参考:图解 Node.js 中的「单线程」 - 掘金 (juejin.cn)
称Node单线程指的是 JavaScript 的执行是单线程的,但 Javascript 的宿主环境并非单线程。Node 程序中包含的线程实际上是:JavaScript 的宿主环境需要的线程 + JavaScript 的执行线程。
其实当你用 node xxx.js
运行程序的时候,操作系统会启动下面 7 个线程:
-
1 个 Javascript 主线程用于执行用户代码
-
1 个 watchdog 监控线程用于处理调试信息
-
1 个 v8 task scheduler 线程用于调度任务优先级,加速延迟敏感任务执行
-
4 个 v8 线程用来执行代码调优与 GC 等后台任务
libuv 创建了线程池,默认情况下线程池里面有 4 个线程。全部是同步代码,那么只会开启 7 个线程,如果存在异步 I/O 操作,则默认会开启 11 个线程。Node.js 程序并非单线程,只不过主线程是单线程的,所有的异步 I/O操作由 libuv 的线程池中的线程进行处理,然后把运行结果通过回调的方式通知到主线程。
Node.js的运行机制
- V8引擎解析JavaScript脚本。
- 解析后的代码,调用Node API。
- libuv库负责Node API的执行。它将不同的任务分配给不同的worker线程,形成一个Event Loop(事件循环),以异步的方式将任务的执行结果返回给V8引擎。
- V8引擎再将结果返回给用户。
5、浏览器事件循环
1、同步、异步是什么
- 同步就是必须等前一件做完了才能做下一任务。
- 异步:当一个异步任务调用发出后,调用者在没有得到结果之前,就可以继续执行后续操作。当这个异步任务有结果后,一般通过状态、通知和回调等来通知调用者。对于异步调用,调用的返回并不受调用者控制。
2、js有一个主线程和调用栈,所有的任务都会被压入到调用栈中,等待被主线程执行。JS 调用栈是一种后进先出的数据结构。当函数被调用时,会被添加到栈中的顶部,执行完成之后就从栈顶部移出该函数,直到栈内被清空。同步任务会在调用栈中按照顺序排队等待主线程执行。异步任务则会在异步有了结果后将注册的回调函数添加到任务队列(消息队列)中等待主线程空闲的时候,也就是栈内被清空的时候,被读取到栈中等待主线程执行。任务队列是先进先出的数据结构。
执行顺序:先执行同步代码,遇到异步宏任务则将异步宏任务放入宏任务队列中,遇到异步微任务则将异步微任务放入微任务队列中,当所有同步代码执行完毕后,再将异步微任务从队列中调入主线程执行,所有微任务执行完毕后再将异步宏任务从队列中调入主线程执行,一直循环直至所有任务执行完毕。
3、Event Loop
调用栈中的同步任务都执行完毕,栈内被清空了,就代表主线程空闲了,这个时候就会去任务队列中按照顺序读取一个任务放入到栈中执行。每次栈内被清空,都会去读取任务队列有没有任务,有就读取执行,一直循环读取-执行的操作,就形成了事件循环。
在事件队列中任务被分为两种:宏任务,微任务参考:什么是宏任务、微任务?宏任务、微任务有哪些?又是怎么执行的?_F N Nancy的博客-优快云博客
- 宏任务(Node、浏览器发起的)setTimeout setInterval... I/O(Node.js)
- 微任务(js发起的)Promise的回调 process.nextTick(Node.js)
执行顺序:当前执行栈执行完毕时会立刻先处理所有微任务队列中的事件,然后再去宏任务队列中取出一个事件。同一次事件循环中,微任务永远在宏任务之前执行。
6、node命令执行js文件
使用js语言编写代码,然后保存成xxx.js文件,然后终端执行node xxx.js即可执行js程序。
7、node.js使用http.createServer搭建简单服务
参考:node.js使用http.createServer搭建简单服务-优快云博客
Node.js入门笔记整理_node.js基础知识总结笔记-优快云博客
- 该方法属于http模块,使用前需要引入http模块(var http= require(“http”) )
- 使用该函数用来创建一个HTTP服务器,并将 requestListener 作为 request 事件的监听函数。http.createServer([requestListener])
- requestListener 请求处理函数,自动添加到 request 事件,函数传递两个参数:req 请求对象,想知道req有哪些属性,可以查看 “http.request 属性整合”。 res 响应对象 ,收到请求后要做出的响应。想知道res有哪些属性,可以查看 “http.response属性整合”。
解析GET方法参数
var http = require("http");
var querystring = require("querystring");
const url = require('url');
var server = http.createServer(function (req, res) {
var urlObj = url.parse(req.url);
var query = urlObj.query;
var queryObj = querystring.parse(query);
res.write("Hello world!" + " " +JSON.stringify(queryObj));
res.end()
});
server.listen(5678);
解析POST方法参数
var http = require("http");
var server = http.createServer(function (req, res) {
if (req.url == "/") {
req.on(‘data‘, function (chunk) {
body += chunk;
});
req.on(‘end‘, function () {
body = JSON.parse(body);
res.end("Hello world!" + " " +body.user);
});
}
});
server.listen(8000);
8、nodejs的全局变量与内置模块
参考:Node.js学习总结 - 掘金 (juejin.cn)
8.1、常用全局对象
浏览器中的 JavaScript,全局对象window,其有属性
DOM、BOM 、Canvas、xhr、js内置对象
在Node环境中,JavaScript也有唯一一个全局变量:global对象。global对象及其所有属性(即全局变量)都可以在程序的任何地方访问,不需要导入。
process对象
process.nextTick的执行时机类似于promise的执行时机,都是等同步任务执行完毕后再被执行。
Buffer
对象
它提供了操作二进制数据的功能。(???)
8.2、内置模块
Node.js开发的目的就是为了用JavaScript编写Web服务器程序,服务器程序必须能接收网络请求,读写文件,处理二进制内容
- path模块-本地文件目录模块,负责处理操作系统相关的文件路径
- fs模块——文件系统模块,负责读写文件
- http模块——网络请求模块,发送和接收网络请求
- url模块——网络地址模块,解析和生成url对象
- event模块——事件模块,监听和响应事件
- stream模块——流模块,标准输入流和标准输出流
- crypto模块-提供了通用的加密和哈希算法
- 还有很多,可以参考官网API
express模块
直接用内置的 http 模块去开发服务器有以下明显的弊端:
- 需要写很多底层代码——例如手动指定 HTTP 状态码和头部字段,最终返回内容。如果我们需要开发更复杂的功能,涉及到多种状态码和头部信息(例如用户鉴权),这样的手动管理模式非常不方便
- 没有专门的路由机制——路由是服务器最重要的功能之一,通过路由才能根据客户端的不同请求 URL 及 HTTP 方法来返回相应内容。但是上面这段代码只能在
http.createServer
的回调函数中通过判断请求req
的内容才能实现路由功能,搭建大型应用时力不从心
由此就引出了 Express 对内置 http 的两大封装和改进:
- 更强大的请求(Request)和响应(Response)对象,添加了很多实用方法
- 灵活方便的路由的定义与解析,能够很方便地进行代码拆分
- Express内置了路由、静态文件服务、请求数据解析、中间件等功能(还需学习)
Request 请求对象
req.body
:客户端请求体的数据,可能是表单或 JSON 数据req.params
:请求 URI 中的路径参数req.query
:请求 URI 中的查询参数req.cookies
:客户端的 cookies
Response 响应对象
- res.send('HTML String');// 发送一串 HTML 代码
- res.sendFile('file.zip');// 发送一个文件
- res.render('index');// 渲染一个模板引擎并发送
- res.status(404).send('Page Not Found');// 设置状态码为 404,并返回 Page Not Found 字符串
路由机制
在express中,路由指的是客户端的请求与服务器处理函数之间的映射关系。
Express中的路由由3部分组成,分别是 请求的类型,请求的url地址,处理函数。格式如下:
const express = require('express');
const hostname = 'localhost';
const port = 3000;
const app = express();
app.get('/', (req, res) => {
res.send('Hello World');
});
app.listen(port, () => {
console.log(`Server running at http://${hostname}:${port}/`);
});
中间件:按顺序执行(还需要学习)
app.use() 函数的作用就是来注册 全局中间件
实现:客户端->发起请求 -> 中间件1 -> 中间件2 -> 中间件n ->处理完毕响应这次请求 -> 客户端
可以实现路由模块化
const app = express();
function loggingMiddleware(req, res, next) {
const time = new Date();
console.log(`[${time.toLocaleString()}] ${req.method} ${req.url}`);
next();
}
//全局中间件
app.use(loggingMiddleware);
//loggingMiddleware只会在当前路由生效,这种用法属于局部中级件
app.get('/only', loggingMiddleware, (req, res) => {
res.send('aaa' + req.startTime)
})
app.get('/', (req, res) => {
res.send('Hello World');
});
var express=require('express');
//引入模块
var admin =require('./routes/admin.js');
var home =require('./routes/home.js');
var app=new express(); //实例化
app.use('/home',home); //前台(路由) http://localhost:3001/home
app.use('/admin',admin); //后台(路由) http://localhost:3001/admin
app.use('/',home); //默认加载前台(路由)
app.listen(3001,'127.0.0.1');
// http://localhost:3001/admin/user---use.js页面
//模块化
var express = require('express')
var router = express.Router() //创建路由对象
//挂载用户路由
router.get('/user',function(req,res){
res.sedn('xxxx')
})
router.post('/user',function(req,res){
res.sedn('xxxx')
})
//挂载列表路由
router.get('/List',function(req,res){
res.sedn('xxxx')
})
module.exports = router // 向外导出路由对象
const router = require('./22模块化的路由') //导入模块
app.use(router) //使用app.use() 注册路由模块
app.use('/api',userRouter) //为路由模块添加前缀
koa模块
express已经够优秀了,为什么又演进出了koa呢?
Koa 是 Express 的轻量级版本。它是一个对http进行了封装的中间件框架,没有像 Express 那样的额外模块。
Express
- Node的基础框架,基础Connect中间件,自身封装了路由、视图处理等功能;
- 线性逻辑,路由和中间件完美融合,清晰明了;
- 弊端是callback回调方式,不可组合、异常不可捕获;
Koa
- 基于node的一个web开发框架,利用co作为底层运行框架,利用Generator的特性,实现“无回调”的异步处理;
- 利用async函数、Koa丢弃回调函数,增强错误处理;
- 很小的体积,因为没有捆绑任何中间件;没有路由模块。