什么是node.js
Node.js虽然带了一个.js后缀,但是和Javascript没有太大的关系
Node.js = Google(chrome)浏览器+V8引擎+C++语言 编写
本质上是一个“JavaScript运行环境“
提到JavaScript,大家就会想到各种交互组件、异步请求、DOM、BOM等众多内容。它需要依赖浏览器中的JS引擎,来解析JavaScript代码
而Node.js这个运行环境,不仅仅是可以解析JS代码,并且也没有浏览器安全级的各种限制,同时还提供了许多系统级别的API,例如:1.文件的读写、2.进程的管理、3.网络通信。
优势
NodeJs 语法完全是 js 语法,只要你懂 JS 基础就可以学会 Nodejs 后端开发
NodeJs 超强的高并发能力
在 Java、PHP 或者.net 等服务器端语言中,会为每一个客户端连接创建一个新的线程。而每个线程需要耗费大约 2MB 内存。理论上,一个 8GB 内存的服务器可以同时连接的最大用户数为 4000 个左右。要让 Web 应用程序支持更多的用户,就 需要增加服务器的数量,而 Web 应用程序的硬件成本当然就上升了。
Node.js 不为每个客户连接创建一个新的线程,而仅仅使用一个线程。当有用户连接了,就触发一个 内部事件,通过非阻塞 I/O(但同样会消耗CPU资源)、事件驱动机制,让 Node.js 程序宏观上也是并行的。使用 Node.js,一个 8GB 内存的服务器,可以同时处理超过4 万用户的连接。开发周期短、开发成本低、学习成本低
在发起一个事件后,可以继续发起其他事件,当某个事件完成后会以回调函数的方式进行响应的 I/O 就是非阻塞 I/O
其实很多地方都用到了node,比如Vue脚手架,Webpack等等都有node的身影
浏览器环境 Vs node环境
/image-20220815132952860.png)
浏览器环境为了安全,禁止读取磁盘,显卡,网卡等操作,也会限制跨域请求。
而node则不仅可以解析代码,也提供了很多系统级别的API:文件读写(fs库),进程管理,网络通信(http库)
node初体验
var http = require('http');
http.createServer(function(req, res) {
res.writeHead(200, { status : 'OK' });
res.end('Hello, world!');
}).listen(8080, '127.0.0.1');
console.log('server listening on port 8080');
运行
node demo.js
/image-20220815133429235.png)
/image-20220815133440945.png)
成功运行
CommonJS规范
我们可以把公共的功能 抽离成为一个单独的 js 文件 作为一个模块,默认情况下面这个模块里面的方法 或者属性,外面是没法访问的。如果要让外部可以访问模块里面的方法或者属性,就必须在模块里面通 过 exports 或者 module.exports 暴露属性或者方法。
npm使用
npm init
npm install 包名 –g (uninstall,update)
npm install 包名 --save-dev (uninstall,update)
npm list -g (不加-g,列举当前目录下的安装包)
npm info 包名(详细信息) npm info 包名 version(获取最新版本)
npm install md5@1(安装指定版本)
npm outdated( 检查包是否已经过时)
nrm使用
NRM (npm registry manager)是npm的镜像源管理工具,有时候国外资源太慢,使用这个就可以 快速地在 npm 源间切换。
安装 nrm 在命令行执行命令,npm install -g nrm
全局安装nrm。 使用 nrm 执行命令 nrm ls 查看可选的源。 其中,带 * 的是当前使用的源,上面的输出表明当前源是官方源。
切换 nrm 如果要切换到taobao源,执行命令 nrm use taobao
。
yarn使用
npm install -g yarn
对比npm: 速度超快: Yarn 缓存了每个下载过的包,所以再次使用时无需重复下载。
同时利用并行下载以最大化 资源利用率,因此安装速度更快。
超级安全: 在执行代码之前,Yarn 会通过算法校验每个安装包的完整性。
模板字符串
允许嵌入表达式的字符串字面量。你可以使用多行字符串和字符串插值功能
使用反引号来代替普通字符串中的用双引号和单引号。模板字符串可以包含特定语法(${expression}
)的占位符。占位符中的表达式和周围的文本会一起传递给一个默认函数,该函数负责将所有的部分连接起来
例如
console.log('string text line 1\n' +
'string text line 2');
// "string text line 1
// string text line 2"
要获得同样效果的多行字符串,只需使用如下代码:
console.log(`string text line 1
string text line 2`);
// "string text line 1
// string text line 2"
我们也可以插入表达式,例如:
var a = 5;
var b = 10;
console.log('Fifteen is ' + (a + b) + ' and\nnot ' + (2 * a + b) + '.');
// "Fifteen is 15 and
// not 20."
现在通过模板字符串,我们可以使用一种更优雅的方式来表示:
var a = 5;
var b = 10;
console.log(`Fifteen is ${a + b} and
not ${2 * a + b}.`);
// "Fifteen is 15 and
// not 20."
响应中用到常用三种API
res.send()
发送HTTP响应。
该body参数可以是一个Buffer对象,一个String对象或一个Array。
样例:
res.send(new Buffer('whoop'));
res.send({ some: 'json' });
res.send('<p>some html</p>');
res.status(404).send('Sorry, we cannot find that!');
res.status(500).send({ error: 'something blew up' });
三种不同参数 express 响应时不同行为
当参数是buffer对象
该方法将Content-Type 响应头字段设置为“application / octet-stream”
如果想Content-Type设置为“text / html”,需使用下列代码进行设置
res.set('Content-Type', 'text/html');
res.send(new Buffer('<p>some html</p>'));
当参数是是String
该方法设置header 中Content-Type为“text / html”:
res.send('<p>some html</p>');
当参数是Arrayor 或 Object
以JSON表示响应,该方法设置header 中Content-Type为“text / html”
Res.status(500).json({});
res.json([body]) 发送一个json的响应
res.json()
该方法res.send()与将对象或数组作为参数相同。 即res.json()就是 res.send([body])第三种情况。同样 ‘content-type’: ‘application/json’。
res.end()
结束响应,告诉客户端所有消息已经发送。当所有要返回的内容发送完毕时,该函数必须被调用一次。如何不调用该函数,客户端将永远处于等待状态。
内置模块
url模块
parse
const url = require('url')
const urlString = 'https://www.baidu.com:443/ad/index.html?
id=8&name=mouse#tag=110'
const parsedStr = url.parse(urlString) //解析URL
console.log(parsedStr)
解析之后 parsedStr 其实是一个json数据格式
protocol: 'https:',
slashes: true,
auth: null,
host: 'www.baidu.com:443',
port: '443',
hostname: 'www.baidu.com',
hash: '#tag=110',
search: '?id=8&name=mouse',
query: { id: '8', name: 'mouse' },
pathname: '/ad/index.html',
path: '/ad/index.html?id=8&name=mouse'
那么我们可以按需取内容,比如我们要 pathname
就可以
var pathname = url.parse(req.url).pathname
format
将一个解析后的URL对象、转成、一个格式化的URL字符串。
var url = require('url');
var a = url.format({
protocol : 'http' ,
auth : null ,
host : 'example.com:8080' ,
port : '8080' ,
hostname : 'example.com' ,
hash : null ,
search : '?a=index&t=article&m=default',
query : 'a=index&t=article&m=default',
pathname : '/one',
path : '/one?a=index&t=article&m=default',
href : 'http://example.com:8080/one?a=index&t=article&m=default'
});
console.log(a);
//输出结果:http://example.com:8080/one?a=index&t=article&m=default
resolve
主要是用于解决目标URL是基于基本URL的情况,
需要注意有无 /
的区别
const url = require('node:url');
url.resolve('/one/two/three', 'four'); // '/one/two/four'
url.resolve('http://example.com/', '/one'); // 'http://example.com/one'
url.resolve('http://example.com/one', '/two'); // 'http://example.com/two'
querystring模块
从字面上的意思就是查询字符串,一般是对http请求所带的数据进行解析,仅有四个方法
- querystring.parse(str); parse函数:是将一个字符串反序列化为一个对象。到时候像类似于解析URL
- querystring.escape(str); escape函数:可使传入的字符串进行url编码。
- querystring.stringify(str); stringif函数:将一个对象序列化成一个字符串,与querystring.parse相对
- querystring.unescape(); unescape函数:可将含有%的字符串进行解码
jsonp实现
是资料格式 JSON
的一种“使用模式”,可以让网页从别的网域获取资料。第三方产生的响应为json数据的包装(故称之为jsonp,即json padding)。一般用来解决Ajax
跨域的问题
其实返回一个函数调用实现动态生成 script
标签从而进行跨域请求。node 实现此方法,需要后端调用前端的函数,保证前端有这个函数。
其实生活中有很多例子,在360搜索中,我们在搜索框随便打一个 a
字母,会有文本框显示相关搜索,原因就是发送了红箭头所指的 JS 请求
那么我们访问一下,是一个函数调用,调用 suggest_so
函数,返回了一系列的对象,也就是我们在搜索下面看到的文本框。这是一个跨域请求,采用 jsonp 这种方式实现。我猜测他是从其他服务器端口调用数据
那么我们将 callback
调用的函数名改了之后,前端也能发生变化
案例
这里前端使用LiveServer打开,在5500端口,而 node 则是默认的4000端口,构建了跨域请求。前端向后端发送请求,后端直接返回了一个对象,前端接受并打印在控制台上
前端:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!-- jsonp接口调用 -->
<script>
var oscript = document.createElement("script")
oscript.src="http://localhost:4000/api/user?callback=test"
document.body.appendChild(oscript)
function test(obj){
console.log(obj)
}
</script>
</body>
</html>
后端:
const http = require('http')
const url = require('url')
const app = http.createServer((req, res) => {
let urlObj = url.parse(req.url, true)
switch (urlObj.pathname) {
case '/api/user':
res.end(`${urlObj.query.callback}({"name": "gp145"})`)
break
default:
res.end('404.')
break
}
})
app.listen(8080, () => {
console.log('localhost:8080')
})
执行流程
<script src="http://localhost:80/?callback=zl"></script>
发送请求- 服务端收到请求
urlObj.query.callback
此时的值就是test
- 服务端向客户端返回数据,经过字符串拼接,返回给客户端的其实是
test({"name": "gp145"})
- 客户端将
test({"name": "gp145"})
解析为调用了test
函数且入参为({"name": "gp145"})
并打印在控制台
AJax实现jsonp
$.ajax({
type: "GET",
url: "http://localhost:4000/api/user",
dataType: "jsonp",
jsonp: "callback", //此参数的值与服务器端的req.query.callback对应
success: function (data) {
console.log(data) //输出
},
error: function (err) {
console.log(err)
console.log('请求错误')
}
});
跨域CROS
fetch()
英语直译为带来(无方向性的)
全局的 fetch()
方法用于发起获取资源的请求。它返回一个 promise,这个 promise 会在请求响应后被 resolve,并传回 Response
对象。
基本语法:
let promise = fetch(url, [options])
url
—— 要访问的 URL。options
—— 可选参数:method,header 等。
当服务器发送了响应头(response header),fetch
返回的 promise
就使用内建的 Response class 对象来对响应头进行解析。
为了获取 response body,我们需要使用一个其他的方法调用。
Response
提供了多种基于 promise 的方法,来以不同的格式访问 body:
response.text()
—— 读取 response,并以文本形式返回 response,response.json()
—— 将 response 解析为 JSON 格式,- etc.
那么我们可以使用纯promise语法
fetch('http://example.com/movies.json')
.then(response => response.json())
.then(data => console.log(data));
返回一个包含响应结果的 promise(一个 Response
对象)。当然它只是一个 HTTP 响应,而不是真的 JSON。为了获取 JSON 的内容,我们需要使用 json()
方法。
fetch()
接受第二个可选参数,一个可以控制不同配置的 init
对象。
代码案例:
const http = require('http')
const url = require('url')
const querystring = require('querystring')
const app = http.createServer((req, res) => {
let data = ''
let urlObj = url.parse(req.url, true)
res.writeHead(200, {
'content-type': 'application/json;charset=utf-8',
'Access-Control-Allow-Origin': '*' //允许所有请求都是跨域的
})
req.on('data', (chunk) => {
data += chunk
})
req.on('end', () => {
responseResult(querystring.parse(data))
})
function responseResult(data) {
switch (urlObj.pathname) {
case '/api/login':
res.end(JSON.stringify({
message: data
}))
break
default:
res.end('404.')
break
}
}
})
app.listen(8080, () => {
console.log('localhost:8080')
})
前端:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
fetch("http://localhost:4000/api/login")
.then(res=>res.json())
.then(res=>{
console.log(res)
})
</script>
</body>
</html>
get模块
在很多情况下,node是作为中间层,从其他服务器调用数据。那么请求主要分为 post 和 get
那么我们首先需要 request
这个包
代码示例:
很简单就不多说
const http = require('http'); // 引入 HTTP 模块
// 调用 request 方法发送 get 请求
const req = http.request({
hostname: 'localhost',
path: '/php/test/api.php',
port: 80,
method: 'GET'
}, res => {
// 如果状态码不是 200 就在控制台输出状态码
if (res.statusCode !== 200) console.log(res.statusCode);
res.on('data', d => {
// 把接收到的数据转为字符串在控制台输出
console.log(d.toString());
});
});
// 如果出错就在控制台输出错误信息
req.on('error', err => {
console.log(err);
});
req.end(); // 结束
上面给 localhost/php/test/api.php
发送 GET 请求,然后在控制台输出接收到的数据。
下面是 HTTP 模块的简单说明:
http.request(options[, callback])
发送 HTTP 请求,options
参数是选项 需要传入一个对象,callback
参数是一个回调函数。
下面简单写一下上面用到的选项:
hostname
:请求的 IP 或域名path
:请求的页面路径port
:服务器的端口method
:请求方法,默认为GET
请求成功会触发 response
事件,也包括 response
上的 data
事件,也就是上面的 res
。
data
事件接收到的数据是一个 Buffer
,可以使用 toString()
方法转为字符串。
请求结束需要调用 end()
方法,end()
可以传入一个回调函数。
如果需要发送的键值对较多的话,可以使用对象来定义键值对,然后用 querystring
模块转为 URL 键值对。
例如:
const querystring = require('querystring'); // 引入 querystring 模块
// 要发送的键值对
let data = {
id: 2,
title: 'Tutorial',
content: 'This is JavaScript tutorial'
};
// 转为 URL 键值对
data = querystring.stringify(data);
console.log(data);
post模块
没有https.post只有request。
代码示例:
很简单,和 get 模块差不多
const http = require('http'); // 引入 HTTP 模块
const data = 'id=1&name=Louis'; // 要发送的内容
// 使用 request 方法发送 POST 请求
const req = http.request({
hostname: 'localhost',
path: '/php/test/api.php',
port: 80,
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': data.length
}
}, res => {
// 如果状态码不是 200 就输出状态码
if (res.statusCode !== 200) console.log(res.statusCode);
res.on('data', d => {
// 把接收到的内容转为字符串在控制台输出
console.log(d.toString())
});
});
// 设置要发送的内容
req.write(data);
// 如果出错就在控制台输出错误信息
req.on('error', err => {
console.log(err);
});
req.end(); // 结束
路由
为路由提供请求的URL和其他需要的GET及POST参数,随后路由需要根据这些数据来执行相应的代码。
需要的数据从http请求中来,包含在request对象中,为了解析这些数据,需要额外的Node.JS模块,它们分别是url和querystring模块。
代码示例
通过不同路由加载不同的页面
我们需要注意:readFileSync
和 readFile
的区别,一个是同步阻塞方式读取文件,另一个是异步读取。
例如一下代码是readFile实现:
var fs = require('fs');
function readFileAsSync(){
new Promise((resolve, reject)=>{
fs.readFile(filename, "utf8", function(err, data) {
if (err) throw err;
resolve(data);
});
});
}
async function callRead(){
let data = await readFileAsSync();
console.log(data);
}
callRead();
路由示例代码:
var fs = require("fs")
var path = require("path")
function render(res, path) {
res.writeHead(200, { "Content-Type": "text/html;charset=utf8" })
res.write(fs.readFileSync(path, "utf8"))
res.end()
}
const route = {
"/login": (req, res) => {
render(res, "./static/login.html")
},
"/home": (req, res) => {
render(res, "./static/home.html")
},
"/404": (req, res) => {
res.writeHead(404, { "Content-Type": "text/html;charset=utf8" })
res.write(fs.readFileSync("./static/404.html", "utf8"))
}
}
读取静态资源:
function readStaticFile(req, res) {
const myURL = new URL(req.url, 'http://127.0.0.1:3000')
var filePathname = path.join(__dirname, "/static", myURL.pathname);
if (fs.existsSync(filePathname)) {
// console.log(1111)
res.writeHead(200, { "Content-Type": `${mime.getType(myURL.pathname.split(".")[1])};charset=utf8` })
res.write(fs.readFileSync(filePathname, "utf8"))
res.end()
return true
} else {
return false
}
}
这里需要用到几个包:path 和 mime,mime主要是处理文件类型用
split(".")
获取文件扩展名,当然也可以使用path.extname
获取拓展名