Node.js到底是什么?

2023-11-09

前言

Node.js是一个基于Chrome V8引擎的JavaScript运行环境。

JavaScript是脚本语言,脚本语言需要一个解析器(运行环境)才能运行,若运行在浏览器中,则浏览器就是JavaScript的解析器(运行环境),而对于独立运行的js,Node.js就是一个解析器(运行环境);

以CHrome浏览器为例,JavaScript运行环境如下:

而在Node.js中,JavaScript运行环境如下:

 Node.js通过Chrome V8将js翻译成c或c++代码,供底层使用;

与浏览器运行环境相比,nodejs缺少了对dom的操作,增加了跨域请求/文件读写等功能;

与传统服务器相比(如java服务器):

    传统服务器每次产生一个请求时,则会生成一个线程(进程);这样就会产生一个问题:

           由于请求的速度由用户决定,响应的速度可以通过提升带宽等方法来提升速度。但是i/o的输入输出速度是比较难提升的。而每一个请求都会产生一个线程(橙色框),数据请求又比较慢就会出现很多线程在缓存中等待,从而造成大量的内存浪费。

  node服务器则采用单线程模式(橙色框),即不管产生多少请求都只有一个线程,这样就可以大大节省内存,降低成本;

传统服务器处理(java服务器),橙色框表示线程(多线程)

node服务器处理  橙色框表示线程(单线程)

node特点如下:

  • 异步非阻塞的I/O(I/O线程池)
  • 特别适用于I/O密集型应用
  • 事件循环机制
  • 单线程(处理不好cpu密集型任务)
  • 跨平台 
  • 回调函数嵌套过多(不足)
  • node中的全局对象是global,使用common.js模块化方法

总而言之,

  • 当JavaScript作为前端开发语言来说,需要在浏览器的环境上进行
  • 当JavaScript作为后端开发语言来说, 需要在node.js的环境上进行

npm

npm全称是Node Package Manager,即Node的包管理器(安装完node后自动安装npm);通过npm可以对Node的包进行搜索/下载/安装/删除/上传等操作;

npm的包服务器是https://registry.npmjs.org

npm init // 初始化项目等package.json文件
npm search 包名// 搜索指定的包
npm install 包名// 安装指定包
npm install 包名 --save 或 npm install 包名 -S// 安装指定包并添加到项目的生产依赖中
npm install 包名 --save-dev 或 npm install 包名 -D// 安装指定包并添加到项目的开发依赖中
npm install 包名 --g // 全局安装指定包
npm install XXX@YYY // 安装XXX包的YYY版本
npm install  // 安装项目package.json中的所有依赖
npm remove 包名  // 删除指定包,同时会移除package.json中的声明

cnpm

因为npm的远程服务器在国外,所以会遇到访问过慢或无法访问的情况,所以淘宝搭建了一个国内的npm服务器,它每隔10分钟将国外npm服务器的所有内容搬运回国内的服务器上,这样我们就可以直接访问淘宝的国内服务器了。镜像地址是:https://registry.npm.taobao.org/

使用时将npm地址改为淘宝镜像,命令依然使用与上述npm讲解的命令;

npm config set registry https://registry.npm.taobao.org // 设置为淘宝镜像
npm config get registry  // 查看npm地址是否设置成功,若为上面地址则设置成功

yarn

yarn的查找算法要比npm更精准,使用的依然是npm仓库,在业界口碑更好;

npm install -g yarn // 全局安装yarn

yarn的全局安装位置与npm不同,要配置yarn的全局安装路径到环境变量中,否则全局安装的包不起作用,具体操作如下:

yarn global dir // 查看全局安装的yarn所在位置
yarn global bin // 查看全局安装的yarn bin所在位置

将上述两个命令的地址添加到环境变量中即可;

yarn操作包的指令如下:

yarn init // 初始化项目
yarn // 安装项目package.json中所有依赖
yarn add xxx@yyy // 安装xxx包的yyy版本
yarn add xxx@yyy -D // 下载指定开发依赖
yarn global add xxx // 全局下载指定包
yarn remove xxx // 删除指定依赖包
yarn global remove xxx // 全局删除指定依赖包

Buffer缓冲器

Buffer是一个和数组类似的对象,不同的是Buffer是专门用来保存二进制数据的;

特点:

  • 大小固定:在创建时就确定了,且无法调整;
  • 性能较好:直接对计算机的内存进行操作,效率高,存储和读取很快;
  • 每个元素大小为1字节(byte)---8bit
  • Buffer是Node中非常核心的模块,无需下载,无需引入,直接即可使用;

创建Buffer

创建Buffer有三种方式:

let buff1 = Buffer.alloc(10); // 直接在堆里开辟一块没人用过的空间
console.log(buff1); // <Buffer 00 00 00 00 00 00 00 00 00 00> Buffer存储的是二进制,但是输出时以16进制展示

let buff2 = Buffer.allocUnsafe(10); // 在堆里开辟空间(该空间可能包含有被弃用的数据),性能好,但是容易造成数据泄露
console.log(buff2); // <Buffer 00 00 00 00 00 00 00 00 00 00>

let buff3 = Buffer.from('hello, Buffer');
// 将数据存入一个Buffer实例
console.log(buff3); // <Buffer 68 65 6c 6c 77 2c 20 42 75 66 66 65 72>

文件系统

所谓文件系统,就是对计算机的文件进行增删改查等操作;Node提供了fs模块专门用于操作文件;fs模块是Node的核心模块,只需引入即可使用;

文件写入

  • writeFile(file, data[, options], callback)异步简单文件写入方式     
// file 为要写入的文件路径+文件名+文件后缀
// data 为要写入的数据
// options 配置项(可选参数), 包括:
      // encoding: 字符串格式,默认是'utf8'
      // mode: 整数格式, 默认是0o666 = 0o222+0o444
           // 0o111: 文件可被执行的权限
           // 0o222: 文件可被写入的权限
           // 0o444: 文件可被读取的权限
      // flag: 标识(打开文件要执行的操作),默认是'w' 
           // w(写入,即覆盖)
           // a(追加) 
// callback 回调函数,传入错误对象err,若写入成功,则err为空
let fs = require('fs') // node使用commonJs模块方式
fs.writeFile(__dirname + '/1.txt', 'hello, node', (err) => {
    if (err) {
        console.log('写入失败');
    } else {
        console.log('写入成功'); // 则在当前文件夹下会生成1.txt文件,内容为hello, node
    }
})
let fs = require('fs') // node使用commonJs模块方式
fs.writeFile(__dirname + '/1.txt', 'hello, Thuesday', {flag: 'a'},  (err) => {
    if (err) {
        console.log('写入失败');
    } else {
        console.log('写入成功'); // 则在当前文件夹下会追加生成1.txt文件,内容为hello, nodehello, Thuesday(因为1.txt中原本内容为hello, node)
    }
})
  • createWriteStream(path[, options]) 异步流式文件写入
// path 为要写入的文件路径+文件名+文件后缀
// options 配置项(可选参数), 包括:
      // encoding: 字符串格式,默认是'utf8'
      // mode: 整数格式, 默认是0o666 = 0o222+0o444
           // 0o111: 文件可被执行的权限
           // 0o222: 文件可被写入的权限
           // 0o444: 文件可被读取的权限
      // flags: 标识(打开文件要执行的操作),默认是'w' 
           // w(写入,即覆盖)
           // a(追加) 
      // fd: 文件统一标识符,linux下文件标识符,默认是null
      // autoClose: 自动关闭文件,默认是true
      // emitClose: 默认是false
      // start: 开始写入的位置
let fs = require('fs') // node使用commonJs模块方式
let ws = fs.createWriteStream(__dirname + '/2.txt')
// 监视写入流打开或关闭状态
ws.on('open', () => {
    console.log('写入流打开了');
})
ws.on('close', () => {
    console.log('写入流关闭了');
})
ws.write('hello, new day') // 写入数据,成功后,在当前文件夹下会生成2.txt文件,内容为hello, new day
ws.write('ok, fine') 
ws.close() // 在node8版本中,使用close关闭会导致数据丢失,一般使用end()关闭流

 读取文件

  • readFile(path[, options], callback) 异步读取简单文件
// path 为要写入的文件路径+文件名+文件后缀
// options 配置项(可选参数), 包括:
      // encoding: 字符串格式,默认是'utf8'
      // mode: 整数格式, 默认是0o666 = 0o222+0o444
           // 0o111: 文件可被执行的权限
           // 0o222: 文件可被写入的权限
           // 0o444: 文件可被读取的权限
      // flag: 标识(打开文件要执行的操作),默认是'w' 
           // w(写入,即覆盖)
           // a(追加) 
// callback 回调函数,传入错误对象err和成功数据data
let fs = require('fs')
fs.readFile(__dirname + '/1.txt', (err, data) => {
    if (err) {
        console.log('读取失败',err);
    } else {
        console.log('读取成功', data); // 读取成功 <Buffer 68 65 6c 6c 6f 2c 20 6 .... more bytes>
    }
})

fs读出来的data数据是Buffer格式(因为不一定全是字符串格式,也有可能是流媒体格式,存成Buffer后面好使用);

let fs = require('fs')
fs.readFile(__dirname + '/1.txt', (err, data) => {
    if (err) {
        console.log('读取失败',err);
    } else {
        console.log('读取成功', data); // 读取成功 <Buffer 68 65 6c 6c 6f 2c 20 6 .... more bytes>
        fs.writeFile(__dirname+'/3.txt', data, (err) => {
            if (err) {
               console.log('写入失败'); 
            } else {
               console.log('写入成功');
            }
        })
    }
})
  • createReadStream(path[, options]) 流式文件读取
// path 为要写入的文件路径+文件名+文件后缀
// options 配置项(可选参数), 包括:
      // encoding: 字符串格式,默认是'utf8'
      // mode: 整数格式, 默认是0o666 = 0o222+0o444
           // 0o111: 文件可被执行的权限
           // 0o222: 文件可被写入的权限
           // 0o444: 文件可被读取的权限
      // flags: 标识(打开文件要执行的操作),默认是'w' 
           // w(写入,即覆盖)
           // a(追加) 
      // fd: 文件统一标识符,linux下文件标识符,默认是null
      // autoClose: 自动关闭文件,默认是true
      // emitClose: 默认是false
      // start: 开始读取的位置
      // end: 停止读取的位置
      // highWaterMark: 每次读取数据的大小,默认是64*1024
let fs = require('fs') // node使用commonJs模块方式
let rs = fs.createReadStream(__dirname + '/1.txt')
let ws = fs.createWriteStream(__dirname + '/4.txt')
rs.on('open', () => {
    console.log('可读流打开了');
})
rs.on('close', () => {
    console.log('可读流关闭了');
    ws.end() // 关闭写入流,在可读流读完的时候,会自动关闭,此时关闭写入流最为合适
})
ws.on('open', () => {
    console.log('写入流打开了');
})
ws.on('close', () => {
    console.log('写入流关闭了');
})
rs.on('data', (data) => {
    // 对data事件进行监控,可以看到每次读取的data是啥
    console.log(data, '每次读取的data是啥');
    ws.write(data) // 写入每次读取的数据
})

简单文件写入和简单文件读取,都是一次性将所有要读取或写入的内容加到内存中去,容易造成内存泄露

http服务

node自带有http模块,可以直接引入使用即可;

分为三个步骤:

  • 引入node内置的http模块
let http = require('http')
  • 创建服务对象
let server = http.createServer(function(request, response) {
  // 其中request是请求对象, response是响应对象
  response.setHeader('content-type', 'text/html;charset=utf8') // 可设置响应头
  response.end('返回的数据在这里传回')
})
  • 指定服务器运行的端口号并绑定监听
server.listen(端口号, function(err) {
    if(!err) console.log('服务器启动成功')
    else console.log(err)
})

Express

node作为一个JavaScript运行环境,提供了很多基础的功能和API,基于node.js也衍生出了很多框架,如Express是一个基于Node.js平台的极简、灵活的web应用开发框架,它提供一系列强大的特性,帮助快速创建各种web和移动设备应用,官网见Express - Node.js web application framework

Express 框架核心特性:

  • 可设置中间件来响应HTTP请求;
  • 定义了路由表用于执行不同的HTTP请求动作;
  • 可通过向模板传递参数来动态渲染HTML页面

基本使用方式如下:

const express = require('express') // 引入express
const app = express() // 创建app服务对象

// 配置理由,发送请求
app.get('xxx路由', function(request, response) {
    response.send('这里是后台要返回的数据')  
})
// 通过app.get发送get请求,通过app.post发送post请求
//  response.send中返回后台数据
// 指定服务器运行的端口号并监听
app.listen(端口号, function(err) {
    if(!err) console.log('服务器启动成功了')
    else console.log(err)
})

请求方法

express支持所有http请求方法,如get、post、delete、put以及all方法(用以支持restful API)等

app.METHOD(path, callback [, callback ...]) // 可传入多个回调

示例:

app.all('/secret', (req, res, next) => {
  console.log('Accessing the secret section ...')
  next() // pass control to the next handler
})

 在路径path中可以包含限定字符? (匹配前面字符零次或一次)、+(匹配前面字符一次或多次) *(任意字符) 以及括号();

app.get('/ab?cd', (req, res) => { // 匹配路径/acd与/abcd
  res.send('ab?cd') 
})

app.get('/ab+cd', (req, res) => { // 匹配路径/abcd、/abbcd、/abbbcd等等
  res.send('ab+cd')
})

app.get('/ab*cd', (req, res) => { // 匹配路径/abcd /abxcd /ab1cd /ab1234cd等等
  res.send('ab*cd')
})

app.get('/ab(cd)?e', (req, res) => { // 匹配路径/abe与/abcde
  res.send('ab(cd)?e')
})

可支持多个回调函数调用,但在每个回调结束记得要指定next(),此方法类似于后面要讲的中间件方法~

app.get('/example/b', (req, res, next) => {
  console.log('the response will be sent by the next function ...')
  next()
}, (req, res) => {
  res.send('Hello from B!')
})

Request对象

Request对象是express中路由回调函数中的第一个参数,代表了用户发送给服务器的请求信息;

属性/方法 描述
request.query

可获取get请求查询字符串的参数,拿到的是一个对象,

如路由为/demo?name="team"&age="12",则request.query是{ name: "'team'", age: "'12'" }

request.params

可以获取get请求参数路由的参数,拿到的是一个对象

如路由为/demo/team/28,则request.params是{ name: 'team', age: '28' }

request.body 可以获取post请求体,拿到的是一个对象(不可以直接用,要借助中间件)
request.get(xxx)

获取请求头中指定key对应的value,

如console.log(request.get('HOST')), 输出为localhost:3000

Response对象

属性/方法 描述
response.send() 给浏览器的响应
response.end() 结束响应进程
response.download() 给浏览器一个文件
response.sendFile() 给浏览器发送文件
response.redirect() 重定向到一个新的地址(url)
response.set(key, value) 自定义响应头key-value
response.get() 获取响应头指定key值
response.status() 设置响应状态码(一般不设置,由http自动设置)
response.json() 返回json格式响应
response.jsonp() 返回jsonp处理的json格式响应

中间件

中间件本质上就是一个函数,包含三个参数:request, response, next,其作用是:

  • 执行任何代码
  • 修改请求和响应对象
  • 终结请求-响应循环(让一次请求得到响应)
  • 调用堆栈中的下一个中间件或路由

分为四种:

  • 应用(全局)级中间件(用于过滤非法的请求,如防盗链)

第一种写法:app.use((request, response, next) => { }) ,使用这种方式的中间件,则每个请求都会经过此中间件进行过滤

// demo.js
const express = require('express')

const app = express()

// 利用中间件防止盗链
app.use((request, response, next) => {
  if (request.get('Referer')) {
    const testReferer = request.get('Referer')
    console.log(testReferer)
    if (testReferer !== '要校验的某网站') {
      next()
    } else {
      response.send('无权使用该网站的图片')
    }
  } else {
    // 若没有网站来源,也放行
    next()
  }
})
app.get('/', function(request, response) {
  response.redirect('https://www.baidu.com')
})

第二种写法: 使用函数定义,该种方法较为灵活,可灵活用在某个需要的地方

// demo.js
const express = require('express')

const app = express()

// 函数式中间件
function guard(request, response, next) {
  request.demo = 123 // 给request设置字段demo为123
  if (request.get('Referer')) {
    const testReferer = request.get('Referer')
    console.log(testReferer)
    if (testReferer !== '要校验的某网站') {
      next()
    } else {
      response.send('无权使用该网站的图片')
    }
  } else {
    next()
  }
}
app.get('/', guard, function(request, response) {
  console.log(request.demo, 'request.demo')  // 123
  response.redirect('https://www.baidu.com')
})
  • 第三方中间件(通过包管理工具下载的中间件,如body-parser)

如app.use(bodyParser.urlencoded({extended: true})),该中间件用于解析post请求中的请求body;

npm install cookie-parser // 安装第三方中间件

const express = require('express')
const app = express()
const cookieParser = require('cookie-parser')

// load the cookie-parsing middleware
app.use(cookieParser()) // 使用第三方
  • 内置中间件(express内置封装好的中间件)

如app.use(express.urlencoded({extended: true})),与body-parser中间件功能一样,用于解析post请求中的请求体参数

app.use(express.static('public')) ,用于暴露静态资源

  • 路由器中间件Router

Router是一个完整的中间件和路由系统,也可以看做是一个小型的app对象;它的存在是为了更好的管理路由对象;

使用express.Router()可以创建模块化路由,然后作为中间件加载,如下示例:

// 子模块路由,SubRouter.js
const express = require('express')
const router = express.Router()

// middleware that is specific to this router
router.use((req, res, next) => {
  console.log('Time: ', Date.now())
  next()
})
// define the home page route
router.get('/', (req, res) => {
  res.send('Birds home page')
})
// define the about route
router.get('/about', (req, res) => {
  res.send('About birds')
})

module.exports = router
//app.js 在app.js引入子路由模块
const birds = require('./birds')

// ...

app.use('/birds', birds)

备注:在express中,定义路由和中间件的时候,根据定义的顺序(代码的顺序),将定义的每一个中间件或路由,放在一个类似于数组的容器中,当请求过来的时候,依次从容器中取出中间件和路由进行匹配,若匹配成功,则交由该路由或中间件处理;

对于服务器来说,一次请求,只有一个请求对象,只有一个响应对象,其它任何的request和response都是对二者的引用;

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Node.js到底是什么? 的相关文章

随机推荐

  • Centos7加入AD域并通过域账号登录

    环境 windows server 2012系统安装AD域服务 安装完毕创建用户 安装过程百度搜索 创建用户 打开服务器管理器 AD DS 右击服务器选择 AD用户和计算机 创建组织单位 选择你安装的域名服务 空白处右击 新建 组织单位 选
  • 【大数据实验1】note1:安装CentOS 7.7(桥接模式)

    note1 安装CentOS 7 7 桥接模式 1 安装VMware Workstation Pro 2 安装CentOS7 7 3 安装VMwareTools 4 注意点 为了实验做准备 冲鸭 实验具体操作步骤 cloudstack安装部
  • python自动化笔记(十)——openpyxl模块,操作excel管理测试数据

    这里利用第三方库openpyxl来操作excel管理测试数据 注意 此模块只支持xlsx读写操作 旧版的xls不支持 安装第三方库 pip install openpyxl 平常操作excel的流程 3个对象 工作簿 Workbook 表单
  • Gym - 102263 B - Road to Arabella

    原题内容 Ayoub and Kilani felt board while they are going to ArabellaCPC in Amman Irbid road so Kilani invented a new game t
  • 基于STM8的TIM定时器操作---STM8-第三章

    1 综述 STM8S提供三种类型的 TIM 定时器 高级控制型 TIM1 通用型 TIM2 TIM3 TIM5 和基本型定时器 TIM4 TIM6 它们虽有不同功能但都基于共同的架构 此共同的架构使得采用各个定时器设计应用变得非常容易与方便
  • 三目运算符

    三目运算符 又称条件运算符 是计算机语言 c c java等 的重要组成部分 它是唯一有3个操作数的运算符 所以有时又称为三元运算符 一般来说 三目运算符的结合性是右结合的 定义 对于条件表达式b x y 先计算条件b 然后进行判断 如果b
  • yolov5训练报错: a view of a leaf Variable that requires grad is being used in an in-place operation

    梯度信息丢失错误 报错详情 报错原因 解决方法 报错详情 RuntimeError a view of a leaf Variable that requires grad is being used in an in place oper
  • python简单爬虫实例,爬取CSDN文章

    查看要爬的网页的源代码 准备爬取所有文章和链接 代码 import requests from bs4 import BeautifulSoup url https blog csdn net LI AINY headers User Ag
  • 基于Spark的分布式数据处理和机器学习技术【上进小菜猪大数据】

    上进小菜猪 沈工大软件工程专业 爱好敲代码 持续输出干货 大数据已经成为当今社会中一个重要的资源和挑战 随着数据规模的不断增长 如何高效地处理和分析这些数据成为了一个关键问题 本文将介绍基于Apache Spark的分布式数据处理和机器学习
  • 【机器学习】通俗易懂决策树(原理篇)

    决策树 引言 决策树是什么 怎样利用决策树来帮助我们分类 怎样构建自己的决策树 决策树是一种类似流程图的结构 其中每个内部节点代表一个属性的 测试 例如硬币翻转出现正面朝上或反面朝上 每个分支代表测试的结果 每个叶节点代表一个类标签 在计算
  • flutter_tools/gradle/app_plugin_loader.gradle‘ as it does not exist

    背景 flutter 1 17 hotfix5 因为使用flutter crate 来创建新项目的 在之前的windows电脑能够正常运行 但是在mac电脑上死活运行不上去 查了很久 github上也查看了相关issuer 发现解决问题的方
  • MPP数据库简介及架构分析

    目录 什么是MPP 特性 并行处理 超大规模 数据仓库真正适合什么 典型的分析工作量 数据集中化 线性可伸缩性 MPP架构技术特性 数据库架构分析 Shared Everything Shared Disk Share Memory Sha
  • elastic weight consolidation

    GitHub kuc2477 pytorch ewc Unofficial PyTorch implementation of DeepMind s PNAS 2017 paper Overcoming Catastrophic Forge
  • Springboot简单实现用户登录操作

    从0开始开发SpringBoot vue前后端分离项目 文章目录 从0开始开发SpringBoot vue前后端分离项目 一 创建Springboot项目 二 引入依赖 三 插件推荐 1 Mybatis Log Free 2 Free My
  • c# 代码实现通过域名获取IPV4地址

    c 代码实现通过域名获取IPV4地址 IPHostEntry iPHostEntry Dns GetHostByName www baidu com IPAddress ip iPHostEntry AddressList 0 label1
  • 每日一题(两数相加)

    每日一题 两数相加 2 两数相加 力扣 LeetCode 思路 思路 由于链表从头开始向后存储的是低权值位的数据 所以只需要两个指针p1和p2 分别从链表的头节点开始遍历 同时创建一个新的指针newhead 用于构造新链表 将创建的新节点进
  • 交换两数(不使用中间变量)

    引出问题 说到交换两数的值 对大家来说应该是一个非常简单的任务 但是 我们最常用的方法就是创建一个临时变量 再通过这个临时变量来改变两值 如下 int temp a a b b temp 但是 难道交换两个变量就只有这一种做法吗 方法1 其
  • IntelliJ IDEA写JSP文件出现“cannot resolve method”解决办法

    最近在使用IDEA写JSP文件的时候 有些内置对象出现了cannot resolve method的警告提示 代码运行没有问题 在编写的时候也不会提示 最后请教了万能的搜索引擎 解决了此问题 解决办法 该错误的导致的原因是因为没有在项目中添
  • 利用 Android Studio 和 Gradle 打包多版本APK( applicationIdSuffix)

    在项目开发过程中 经常会有需要打包不同版本的 APK 的需求 比如 debug版 release版 dev版等等 有时候不同的版本中使用到的不同的服务端api域名也不相同 比如 debug api com release api com d
  • Node.js到底是什么?

    前言 Node js是一个基于Chrome V8引擎的JavaScript运行环境 JavaScript是脚本语言 脚本语言需要一个解析器 运行环境 才能运行 若运行在浏览器中 则浏览器就是JavaScript的解析器 运行环境 而对于独立