Node.js

334 阅读16分钟

1.NodeJS简介

简单的说 Node.js 就是运行在服务端的 JavaScript。

Node.js 是一个基于 Chrome JavaScript 运行时建立的一个平台。

Node.js 是一个事件驱动 I/O 服务端 JavaScript 环境,基于 Google 的 V8 引擎,V8 引擎执行 Javascript 的速度非常快,性能非常好。

Node.js 的工作原理?

  • Node.js 最大的用武之地在于建设高性能、高扩展性的互联网应用---因为它能够处理庞大的并且高吞吐量的并发连接。主要是因为node.js的以下几大特性:

  • event-driven 事件驱动

  • non-blocking 非阻塞的异步I/O调用

  • lightweight 省空间、省设备,轻量意味着更好的移植性

  • Node.js是跨平台的项目,可以运行在Linux、Unix、Windows

  • efficient 高效(node.js是基于单线程)

在PHP或者JSP中,是每个新增一个连接(请求)便生成一个新的线程,这个新的线程会占用系统内存,最终会占掉所有的可用内存。而 Node.js 仅仅只运行在一个单线程中,使用非阻塞的异步 I/O 调用,所有连接都由该线程处理,在 libuv 的加分下,可以允许其支持数万并发连接(全部挂在该线程的事件循环中),

注意:Node.js也有潜在的缺陷,比如:如果所有客户端的请求共享单一线程时也会有问题, 大量的计算可能会使得 Node 的单线程暂时失去反应, 并导致所有的其他客户端的请求一直阻塞, 直到计算结束才恢复正常;

因此,开发时千万不要让一个 Exception 阻塞核心的事件循环,因为这将导致 Node.js 的应用程序崩溃。比如在 PHP 中某个页面挂掉是不会影响网站运行的,但是 Nodejs 是一个线程一个线程来处理所有的链接,因此一旦异常阻塞了都可能会影响到其他所有的链接。

当然,Node.js中有很多工具和策略来帮助我们解决上述的问题,比如:异常回调传递, Forever 进行进程监视等,下面来简单学习一下,初步不入后端的门槛。

运行js代码

  • 用node执行单独的js代码 nodejs运行 JavaScript 脚本文件

1、创建并编写 JavaScript 脚本文件 image.png 2、在js脚本文件找到对应的根目录,点击磁盘路径删除,输入cmd命令,打开 cmd 命令窗口 image.png

image.png

3、直接执行“node js文件名的具体路径”命令,即可执行js脚本文件

image.png

nodejs有哪些核心模块

  1. 内置模块
  2. 第三方模块
  3. 自定义模块

nodejs的核心模块:1、HTTP模块,用于处理客户端的网络请求;2、URL模块,用于处理客户端请求过来的URL;3、Query Strings模块;4、File System模块;5、Path模块;6、Global模块等等。

  • 首先我们了解这几个模块

Node核心模块之HTTP模块

作用:

处理客户端的网络请求

代码步骤:

1.导入 HTTP 核心模块
2.监听客户端的请求
3.处理客户端的请求
4.开启服务器

需要借助 http 模块 搭建服务器:

var http = require('http');
//  req: 客户端向服务器请求
//  res: 服务器向客户端响应
var app=http.createServer((req, res) => {
    // 设置响应头
    res.setHeader(200, {"Content-type":"text/plain; charset=utf-8"})
    //  设置编码格式
    res.end(data);
    //  返回数据
    res.end("") 
    //  终止响应 也可以返回数据
//})
//app.listen(8080)
//服务正确开启,需要监听8080 端口

NodeJS核心模块之File System模块

前提 : 以后的所有静态资源(html,css,js,图片等)都是放在服务端的,如果浏览器需要这些html,css,js,图片等资源,则需要先将其读取到node.exe的内容中,然后再返回给浏览器

作用:

在服务端来操作文件,可能是需要将浏览器上传的图片保存到服务器,也可能是需要将服务器的资源读取之后返回给浏览器

  • 代码步骤:

1.导入 fs 核心模块

2.使用相应的方法来写入文件、读取文件等操作

nodejs 的 fs 文件系统

nodejs 的fs 文件系统 可以操作电脑上的文件, 对文件进行增删改查的操作

 var fs = require('fs')

对文件的基本操作

  1. 读取文件 fs.readFile()
fs.readFile(url, (err, result) => {
    if(err) {
        console.log(err);
        return;
    }
    console.log(result)  // 默认的格式是buffer 类型
    console.log(result.toString())  // 默认的格式是buffer 类型
})

开始

// 引入模块
var fs = require('fs');

// 1. fs.stat() 检测是文件还是目录
/**
* path 文件路径
* callback 回调函数
*/
fs.stat('./package.json', (err, data) => {
	if (err) {
		console.log(err);
	}

	console.log(`是文件: ${data.isFile()}`); // true
	console.log(`是目录: ${data.isDirectory()}`); // false
})

// 2. fs.mkdir() 创建目录
/**
* path    	将要创建的目录路径
* mode 	  	目录权限(读写权限),默认777,一般不用写
* callback 	回调函数,传递异常参数 err
*/

fs.mkdir('./src/util', err => {
	if (err) {
		console.log(err);
		return;
	}

	console.log('创建成功');
})

// 3. fs.writeFile() 写入操作(有则写入,无则创建并写入)
/**
* path    	将要写入文件路径
* content	写入内容
* callback 	回调函数,传递异常参数 err
*/

fs.writeFile('./util/format.js', '这是format 文件', err => {
	if (err) {
		console.log(err);
		return;
	}

	console.log('写入成功');
})

// 4. fs.appendFile() 追加内容操作(有则写入,无则创建并写入)
/**
* path    	将要追加文件路径
* content	写入内容
* callback 	回调函数,传递异常参数 err
*/

fs.appendFile('./util/format.js', '这是追加内容', err => {
	if (err) {
		console.log(err);
		return;
	}

	console.log('追加内容成功');
})

// 5. fs.readFile() 读取文件内容
/**
* path    	文件路径
* callback 	回调函数,传递异常参数 err
*/

fs.readFile('./util/format.js', (err,data) => {
	if (err) {
		console.log(err);
		return;
	}

	console.log(data); // Buffer 类型
	console.log(data.toString()); // 将 Buffer类型 转成 string类型
})

// 6. fs.readdir() 读取目录下的文件及文件夹
/**
* path    	文件路径
* callback 	回调函数,传递异常参数 err
*/

fs.readdir('./util', (err,data) => {
	if (err) {
		console.log(err);
		return;
	}

	console.log(data); // ['index.html', 'index.js', 'js']
})

// 7. fs.rename() 重命名/移动文件
/**
* oldPath   旧文件路径
* newPath	新文件路径
* callback 	回调函数,传递异常参数 err
*/

fs.rename('./html/old.html', './new/new.html', (err) => {
	if (err) {
		console.log(err);
		return;
	}

	console.log('移动文件成功'); 
})

// 8. fs.rmdir() 删除目录(不能删除非空目录)
/**
* oldPath   待删除目录路径
* callback 	回调函数,传递异常参数 err
*/

fs.rmdir('./html', (err) => {
	if (err) {
		console.log(err);
		return;
	}

	console.log('删除目录成功'); 
})

// 9. fs.unlink() 删除文件
/**
* oldPath   待删除文件路径
* callback 	回调函数,传递异常参数 err
*/

fs.unlink('./html/old.html', (err) => {
	if (err) {
		console.log(err);
		return;
	}

	console.log('删除文件成功'); 
})

// 9. fs.copyFile() 删除文件
/**
* oldPath   待拷贝文件路径
*newpath    要拷贝到的文件路径
* callback 	回调函数,传递异常参数 err
*/

/var oldpath=__dirname+"/src/1.txt"

/var newpath=__dirname+"/wangluo/1.txt"

//fs.copyFile(oldpath,newpath,(err)=>{
    console.log(err)
})

//fs.readdir(__dirname+"/src",(err)=>{
    console.log(err)
})

Node核心模块之URL模块

作用:

处理客户端请求过来的URL

代码步骤:

1.导入 URL 核心模块
2.导入 HTTP 核心模块
3.监听客户端的请求
在这中间处理客户端请求过来的URL
4.处理客户端的请求
5.开启服务器

nodejs 的 url 模块

url 模块可以解析 url 地址 ; 通过 url.parse() 方法 解析地址 转化为一个对象

var url = require('url');
var path = 'https:://baike.baidu.com/xiaoshuo?kerywods=hello#3';
var res = url.parse(path);

//  网址:  URL  
//https:://baike.baidu.com/xiaoshuo?kerywods=hello#3

//网址的组成:   协议 域名 pathname querystring hash
//域名 ==>DNS解析  会把域名解析为一个ip port 

/* 
Url {
  protocol: 'https:',   // 协议
  slashes: null,
  auth: null,
  host: null,           // 主机
  port: null,           // 端口号
  hostname: null,       // 主机名
  hash: '#3',           // 哈希  url地址中 #后的内容
  search: '?kerywods=hello',        // 搜索  url 地址中 ? 开始往后的内容, 不包含hash
  query: 'kerywods=hello',          // 查询  url 地址中 ? 后边的内容   传递的数据
  pathname: '://baike.baidu.com/xiaoshuo',      // 访问的地址(请求资源的路径) 
  path: '://baike.baidu.com/xiaoshuo?kerywods=hello',       // 路径
  href: 'https:://baike.baidu.com/xiaoshuo?kerywods=hello#3'       // 详细的url地址
}
 */

如果需要获取url地址中传递的数据, 需要传递第二个参数 url.parse(url, true) 对url 中的 query 字段解析为对象

let res = url.parse(path, true);
Url {
  protocol: 'https:',
  slashes: null,
  auth: null,
  host: null,
  port: null,
  hostname: null,
  hash: '#3',
  search: '?kerywods=hello',
  query: [Object: null prototype] { kerywods: 'hello' },
  pathname: '://baike.baidu.com/xiaoshuo',
  path: '://baike.baidu.com/xiaoshuo?kerywods=hello',
  href: 'https:://baike.baidu.com/xiaoshuo?kerywods=hello#3'
}

通过 url 模块对 url 地址进行解析, 实现路由的操作

const http = requrie('http')
const url = requrie('url')
http.createServer((req, res) => {
    //  对请求的路径进行解析
    let pathname = url.parse(req.url).pathname;
    if(pathname == '/'){
        // 相关的处理 
    }else if(pathname == '/login'){
           // 相关的处理
    }....

}).listen(8000, () => {
    console.log('server runnning ....')
})


// var url=require("url")
// // console.log(url)
// var str="http://www.hqyj.com/20220728/news/page1/index.html?count=20&maxid=123456"
// var obj=url.parse(str)
// console.log(obj)


var http = require("http")
var fs = require("fs")
var url=require("url")
//"http://ip:port/20220728/news/page1/index.html?count=20&maxid=123456"
var app = http.createServer((req, res) => {
	console.log(req.url)//"/20220728/news/page1/index.html?count=20&maxid=123456"
	var path=url.parse(req.url).pathname
	fs.readFile(__dirname + path, (err, data) => {
		res.end(data)
	})
})
app.listen(8080)

NodeJS核心模块之Query Strings模块

作用:

处理客户端通过get/post请求传递过来的参数

使用关键点:

1.需要导入 'querystring' 这个核心模块
2.get请求时 querystring 一般是配合 url 核心模块一起使用的
3.get/post请求最终都需要调用 querystring.parse方法,将请求传递过来的键值对字符串转成js对象,方便操作

注意:

get/post的请求方式是不一样的,客户端传递过来时,参数放在
的地方是不一样的,所以服务器端处理方式也不太一样

nodejs核心模块 querystring模块

用来对url中的查询字符串这部分进行处理。nodejs中提供了querystring这个核心模块来帮助我们处理这个需求。

示例

const qs= require('querystring');
let obj = qs.parse('id=18&name=zs');
console.log(obj) // {id:18, name:"zs"}

querystring模块所有方法:

{
  unescapeBuffer: [Function: unescapeBuffer],
  unescape: [Function: qsUnescape],
  escape: [Function: qsEscape],
  stringify: [Function: stringify],
  encode: [Function: stringify],
  parse: [Function: parse],
  decode: [Function: parse]
}

querystring 模块 字面意思就是 查询字符串,一般是对http请求所带的数据进行解析

引入模块:require('querystring'); querystring模块仅有四个方法

  • querystring.parse(str); parse函数:是将一个字符串反序列化为一个对象。

  • querystring.escape(str); escape函数:可使传入的字符串进行编码。

  • querystring.stringify(str); stringif函数:将一个对象序列化成一个字符串,与querystring.parse相对

  • querystring.unescape(); unescape函数:可将含有%的字符串进行解码

    1 querystring.parse(str)
    
const http = require('http');
const url = require('url');
const qs = require('querystring');
 
//
const strurl = 'http://www.baidu.com?a=1&b=2';
const r = qs.parse(strurl);
console.log(r); //{ 'http://www.baidu.com?a': '1', b: '2' }
 
const server = http.createServer(function(request,response){
   const r2 = qs.parse(url.parse(request.url).query);    
   console.log(r2);//{ newsid: '1', autuor: 'zs' } 相当于parst的true的效果
   response.end(); 
});
server.listen(8000);
2 querystring.stringify()(str)
const http = require('http');
const url = require('url');
const qs = require('querystring');
 
const strbm = 'http://www.baidu.com/s?wd=中国$a=10';
 
const server = http.createServer(function(request,response){
   var params = {
       a:50,
       b:50
    }
   const rsgf = qs.stringify(params);
 
   //也可以指定连接符,同理 可以指定参数名和值的连接符
   const rsgf2 = qs.stringify(params,'@');
   console.log(rsgf);//a=50&b=50
   console.log(rsgf2);//a=50@b=50
 
   response.end();
 
});
server.listen(8000);

还可以用json来解析

// var querystring=require("querystring")
// var obj=querystring.parse("username=jack&count=20&maxid=123456")
// //obj[xxxx]
// console.log(obj)

// var str2=querystring.stringify({name:"jack",age:20})
//返回的是一个网址
// var str3=JSON.stringify({name:"jack",age:20})
//返回的是一个对象
// console.log(str2,str3)

image.png

NodeJS核心模块之Path模块

作用

操作文件的路径,为文件操作服务

常用的属性:

__dirname : 文件所在的文件夹路径
__filename : 文件所在的路径
require() : 导入需要的模块
module : 自定义模块时用到
exports : 自定义模块时用到

path 模块

提供了一些用于处理文件路径的小工具,我们可以通过以下方式引入该模块:

var path = require("path")

在使用 fs 模块操作文件时,如果提供的操作路径是以 ./ 或 ../ 开头的相对路径时,很容易出现路径动态拼接错误的问题。因为代码在运行的时候,会以 node 命令时所处的目录,动态拼接出被操作文件的完整路径

于是我们可以用绝对路径来代替相对路径,但是这样做又会出现移植性差、不利于维护的问题

因此我们可以利用 __dirname 来完美解决

示例:

// 在node.js环境中   有两个全局变量  __dirname  __filename
//1.他们保存的是字符串
//2.
//__dirname 当前js文件所在的目录:绝对路径    文件夹(directory)
//__filename 当前js文件的目录:绝对路径  
console.log(111111,__dirname,__filename,2222)

var fs=require("fs")
fs.readFile(__dirname+"/index.html",(err,data)=>{
	console.log(err,data.toString())
})
fs.readFile(__filename,(err,data)=>{
	console.log(err,data.toString())
})

let fs = require("fs");
let path = require("path");
 
// 拿到需要读取的文件路径  __dirname-表示当前文件所处的目录
let str = path.join(__dirname, "./readMe.txt");
 
//读取文件
// fs.readFile(__dirname + './readMe.txt', "utf8", function(err, data) { 或
fs.readFile(str, "utf8", function (err, data) {
    if (err) {
        throw new Error("读取文件失败");
    }
    console.log(data.toString());
});

NodeJS核心模块之mime模块

作用:

mime是一个互联网标准,通过设定它就可以设定文件在浏览器的打开方式

注意点

可以通过mime.getType()来判断输入网址的文件的类型,从而来根据网址数据设置类型:

也可以用mime.getExtension() 来获取你输入的类型:

但是mime是外部资源模块,需要自行下载,在小黑窗输入 npm i mime 来下载安装此模块

var mime=require("mime")
var re=mime.getExtension("text/css")
console.log(re)
var re2=mime.getType("htpp://123.123.12.3:8080/css/index.html")
console.log(re2)
//引入模块
const mime = require('mime');
//指定文件类型
let type = mime.getType(realPath);
 
res.writeHead(200, {
    'content-type': type
});

mime.getType可以通过路径返回资源类型

var mime=require("mime")
var url=require("url")
var querystring=require("querystring")
var http=require("http")
var fs=require("fs")
 
var app = http.createServer((req,res) => {
    let urlobj = url.parse(req.url)
    let pathname = urlobj.pathname
    let path = __dirname + pathname
    fs.readFile(path,(err,data) => {
        if (!err) {
            // 获取当前访问网址的文件类型
            let type1 = mime.getType(path)
            //设置一个变量来转化,可以实现任何类型,不用去考虑
            res.setHeader("conten-Type",type1)
            res.end(data)
        }else {
            res.end("404")
        }
    })
})
app.listen(8081)

路径

1.文件操作中的./xx相对路径问题

查看以下场景:

# /js/foo/a.txt
hello World
# /js/foo/index.js
const fs = require('fs');

fs.readFile('./a.txt',(err,data) => {
  if (err) {
    console.log('error');
  } else {
    console.log(data.toString());
  }
})

在/js目录下执行node foo/index.js,会出现文件找不到的情况,原因:/js/foo/index.js文件中读文件是写的相对路径也即:./a.txt,而这个相对路径实际上是相对于执行node命令所处的路径,也即以上的执行node时,进行文件操作时查找的路径是:js/a.txt显然/js目录下没有该文件,也就查找失败。

# /js/foo/a.txt
hello World
# /js/foo/index.js
const fs = require('fs');

fs.readFile('./a.txt',(err,data) => {
  if (err) {
    console.log('error');
  } else {
    console.log(data.toString());
  }
})

# /js/other.js
require('./foo/index'); // 引入/foo目录下的index.js文件

/js目录下执行node other.js命令,执行结果为:

找不到该文件

原因和之前的一样,虽然在js下执行该命令,但是在该文件中还是在引入并执行index.js文件,由于node命令执行的目录是:/js目录,所以在文件操作的时候,查找的文件目录是:/js/a.txt,显然又是找不到的结果

所以在文件操作中,相对路径是不可靠的,为了解决这个问题,则需要将相对路径改为绝对路径。但是如果仅仅是将文件操作的路径改为C:\node\js\foo\a.txt路径,则当交付项目的时候,还需要将该路径改为当前项目所处计算机的绝对路径,显然这是不可行的,因此__dirname开始发挥作用。

而什么__dirname是什么呢? 在每个模块中,除了require、exports等模块相关API之外,还有两个特殊的成员

__dirname获取当前文件所处目录(绝对路径)
__filename获取当前文件所处目录,包括当我文件(绝对路径)
__dirname和__filename是不受执行node命令所属路径影响的
以上两种获取路径的方式都是动态获取的
由于__dirname不受node命令所属路径影响,同时又可以动态的获取当前文件的绝对路径,因此可以是个不错的选择,将/foo/index.js修改:

# /js/foo/index.js
const fs = require('fs');
const path = require('path');

// 采用path.join()对于拼接的路径自动进行修复,避免不必要的失误操作造成的文件访问不到的问题
fs.readFile(path.join(__dirname + './a.txt'),(err,data) => {
  if (err) {
    console.log('error');
  } else {
    console.log(data.toString());
  }
})

2.require()中的路径问题

模块中require中所写的路径跟文件操作的路径是没有关系的,其路径是相对于文件模块的,也即相对于当前文件模块(文件)所处目录的相对路径。

# /js/other.js
require('./foo/index.js');

# /js/foo/index.js
console.log('1');

此时查找./foo/index.js就是相对于/js目录

老师笔记详细解答:
/*
本地相对路径
  在这个页面中写路径: file://x1/x2/x2/index.html  
"./src/18.jpg"  写这个路径的文件的页面是在本地打开的==> file://x1/x2/x2/src/18.jpg
"src/18.jpg" 写这个路径的文件的页面是在本地打开的 ==> file://x1/x2/x2/src/18.jpg
"file://c:/"


本地绝对路径
从根盘符开始写路径
"C:/Users/Administrator/Desktop/%E4%BB%A3%E7%A0%81/14-%E5%90%84%E7%A7%8D%E8%B7%AF%E5%BE%84(%E7%9B%B8%E5%AF%B9%E7%BB%9D%E5%AF%B9)/index.html"



相对网络路径
当前页面的网址:  "协议://ip:port /src/news/index.html  querystring hash"
页面内部的路径:
 "./src/18.jpg" ==> "协议://ip:port /src/news/src/18.jpg"
"src/18.jpg" ==> "协议://ip:port /src/news/src/18.jpg"

思考1:
用户输入网址: 
"http://192.168.6.60:8080/user/20220728/newspage.html?n=20" 
打开了一个页面,在这个页面中有一个img的src是 : "./src/18.jpg"
请问192.168.6.60:8080这个服务器会受到req.url是什么?
答: "/user/20220728/src/18.jpg"

思考2:
用户输入网址: 
"http://192.168.6.60:8080/user/20220728/newspage" 
打开了一个页面,在这个页面中有一个img的src是 : "./src/18.jpg"
请问192.168.6.60:8080这个服务器会受到req.url是什么?
答: "/user/20220728/src/18.jpg"
它真正的网址:"http://192.168.6.60:8080/user/20220728/src/18.jpg"



绝对网络路径
"协议://ip:port /src/news/src/18.jpg"

易错思考:
用户输入网址: http://192.168.6.60:8080/user/20220728/newspage
打开了一个页面,在这个页面中有一个img的src是 : "192.168.6.60:8080/src/18.jpg"
请问192.168.6.60:8080这个服务器会受到req.url是什么?
答: "/user/20220728/192.168.6.60:8080/src/18.jpg"
它真正的网址:"http://192.168.6.60:8080/user/20220728/192.168.6.60:8080/src/18.jpg"


本地相对根路径
思考:用户本地打开: "file:///c:/xx/xx2/index.html"
页面中有一个img的src是 : "/src/18.jpg"
它真正的路径:"file:///c:/src/18.jpg"


网络相对根路径
"/src/18.jpg"
思考:
用户输入网址: http://192.168.6.60:8080/user/20220728/newspage
打开了一个页面,在这个页面中有一个img的src是 : "/src/18.jpg"
请问192.168.6.60:8080这个服务器会受到req.url是什么?
答:"/src/18.jpg" 
它真正的网址:"http://192.168.6.60:8080/src/18.jpg"

*/

详细讲解页面加载过程

说一说从输入URL到页面呈现发生了什么?(知识点)

这个题可以说是面试最常见也是一道可以无限难的题了,一般面试官出这道题就是为了考察你的前端知识的深度与广度。

  • 1.浏览器接受URL开启网络请求线程(涉及到:浏览器机制,线程与进程等)

  • 2.开启网络线程到发出一个完整的http请求(涉及到:DNS解析,TCP/IP请求,5层网络协议等)

  • 3.从服务器接收到请求到对应后台接受到请求(涉及到:负载均衡,安全拦截,后台内部处理等)

  • 4.后台与前台的http交互(涉及到:http头,响应码,报文结构,cookie等)

  • 5.缓存问题(涉及到:http强缓存与协商缓存等)(请看上一篇文章这些浏览器面试题,看看你能回答几个?)

  • 6.浏览器接受到http数据包后的解析流程(涉及到html词法分析,解析成DOM树,解析CSS生成CSSOM树,合并生成render渲染树。然后layout布局,painting渲染,复合图层合成,GPU绘制,等)

  • 在浏览器地址栏输入URL: 当我们在浏览器地址栏输入URL地址后,浏览器会开一个线程来对我们输入的URL进行解析处理。

    浏览器中的各个进程及作用:(多进程)

浏览器进程:负责管理标签页的创建销毁以及页面的显示,资源下载等。 第三方插件进程:负责管理第三方插件。 GPU进程:负责3D绘制与硬件加速(最多一个)。 渲染进程:负责页面文档解析(HTML,CSS,JS),执行与渲染。(可以有多个)

这只是一个小小的开头,后面学了再补充总结