Node.js必知必会学习笔记(第一章)

476 阅读11分钟

区分服务端与客户端

  • 通常,我们把发送请求(需求)的一方称为:客户端。接收请求(提供服务)的一方成为:服务端
  • 语言是不分前后端的,取决于使用的场景和工作场景。
  • node.js与JavaScript在于你将文件放在哪里(客户端还是服务端)执行。
  • 同一局域网之下,如果两台电脑想要构建出客户端和服务器端的通信可以先通过ipconfig可以查看ipv4地址,在通过此局域网的ip地址给另一台电脑访问即可通信。本地访问可以通过localhost和127.0.0.1和ip地址访问,在服务器端改动了文件内容需要重启服务页面才能更新。
  • 从浏览器输入 URL 到页面展示过程的过程中发生了什么?回答是:敲入回车后会分析协议,file协议加载资源的方式会去加载本地文件资源并通过浏览器解析资源并渲染页面。http协议会去加载网络资源,会先通过域名解析协议DNS转为对应的IP,定位出执行的目标资源所在的主机,等待主机处理和响应,主机接收到请求后,通过请求携带的端口等信息转发给对应的处理程序进行资源的交换等处理,处理完成之后返回数据给客户端,客户端接收数据之后解析内容,例如浏览器便会开始解析资源并渲染页面。

node中的模块化

  • 每一个文件是一个模块,每一个模块都有着自己的命名空间,模块之间是互不干扰的。
  • exports 是 module.exports 的引用,在使用exports.xx=value是实际上导出的还是module.exports,module.exports本质是一个空对象,同一模块下多次使用module.exports=value时会发生对象覆盖的问题, 同一模块下多次使用exports.xx=value是没有问题的。require()还可以引用一整个文件夹目录,下面的home是一整个文件夹目录,即require('./home')

URL模块

  • 常用功能:url.parse(path,boolen)
  • 场景需求:获取url中的search


解决方法:

//引入模块
const url =require('url');
//确定url
var api='http://www.baidu.com?name=zhangsan&age=18'
//在服务端(基于node.js)输出
console.log(url.parse(api,true))

var temp=url.parse(api,true).query
var {name,age}=temp
console.log(name,age)

HTTP模块

var http = require('http');

/**
 *  request 获取url传过来的信息
 *  response 给浏览器响应信息 
 */

http.createServer(function(request, response){
    //设置响应头
    response.writeHead(200,
    {'Content-Type': 'text/plain'}
    );
    
    //表示给我们页面上面输出一句话并且结束响应
    response.end('Hello World');
}).listen(8081); //端口号

console.log('Server running at http://127.0.0.1:8081/');

HTTP模块和URL模块结合使用的第一个案例

场景需求:通过传递的url得到query数据并显示

假设url是http://127.0.0.1?name=zhangsan&age=20

那么需求就是想获取传过来的name和age的值

突破点:该url的req.url是 /?name=zhangsan&age=20

//引入http和url模块
const http=require('http');
const url=require('url');

http.createServer(function(req,res){
    // console.log(req.url)  
    //默认的情况 一个是/ 一个是/favicon.ico

  
    
    //设置响应头
    //状态码是200 文件类型html 字符集utf-8
    res.writeHead(200,{'Content-Type':"text/html;charset:'utf-8'"});
    //解决文字乱码的问题
    res.write("<head><meta charset='UTF-8'></head>")
    
    //res.write('this is node.js')
    //res.write('this is 自学node.js')
    
    //默认的情况其中一个是/favicon.ico需要排除
    if(req.url!='/favicon.ico'){
        console.log('我是',req.url)
        var userInfo=url.parse(req.url,true).query;
        console.log(userInfo)
        //成功得到
        console.log(userInfo.name,userInfo.age)
    }
    //结束响应---必须得写
    res.end()
}).listen(8082)  //在这里写

console.log('Server running at http://127.0.0.1:8082/');

工具:supervisor

/**
 * supervisor会不停的watch你应用下面的所有文件,发现有文件被修改,
 * 就重新载入程序文件这样就实现了部署,修改了程序文件后马上就能看到变更后的结果,不需要再重启nodejs
 *  
 *  cnpm install -g supervisor
 * 
 * 使用supervisor代替node命令启动应用
 * supervisor supervisor.js
 */

console.log('Always monitor')

//工具nodemon不做详解详情可参考--->
//https://www.npmjs.com/package/nodemon

场景需求一:抽离一个单独处理url的js文件模块

需求分析: 传入参数api/plist时可以得到完整的http://www.baidu.com/api/plist,并将其显示在页面上

该构建目录为:

这里是app.js的页面代码

const http=require('http');
const url=require('url');



/**
这是一个公共的功能
---如何抽离为一个单独的js文件作为一个模块

function formatApi(api){
    return "http://www.baidu.com/"+api
}

 */

//引入common02.js中暴露的模块
const formatApi= require('./common02')

http.createServer(function(req,res){

    res.writeHead(200,{'Content-Type':"text/html;charset:'utf-8'"});
    //解决中文文字乱码的问题
    res.write("<head><meta charset='UTF-8'></head>")
    
    var api=formatApi.formatApi('api/plist');
    res.write(api)

    //结束响应---必须得写
    res.end()
}).listen(8082)  //在这里写

console.log('Server running at http://127.0.0.1:8082/');

这里是common02.js的页面代码
---抽离为一个单独的js文件作为一个模块

function formatApi(api){
    return "http://www.baidu.com/"+api
}

module.exports={
    formatApi
}

场景需求二:

  • 需求:使用node.js的http模块搭建一个webserver项目,端口为9999,访问 http://localhost:9999/public/index.html 返回 public 目录下的 index.html 内容,访问http://localhost:9999/quote随机返回一句话,访问 http://localhost:9999//users 返回一段文本。
  • 该需求的文件目录结构为:
// 首先通过 require 引入 http 模块
const http = require('http');
const fs = require('fs');


//静态资源
//相对不变的内容(除非你修改了它的内容)
let users = ['admin1', 'admin2'];


//动态资源(同一个 URL 返回的内容并不固定)
//比如访问 /now 这个 URL,即使不做任何修改,你就有可能得到不一样的结果,这就是动态资源。
//quotes[Math.round((Math.random() * (quotes.length-1)))]
const quotes = [
'加油',
'努力',
'奋斗',
'崛起',
'牛逼'
];



const server = http.createServer(function(req, res) {
      // req: http.IncomingMessage => 对客户端请求数据的封装
      // res: http.ServerResponse => 对服务端输出相关的数据和方法进行封装
      // 这个是请求回调函数,当有客户端请求到该服务的时候,则会执行
      console.log('有人发送了一个请求');
    
    
    //当前客户端请求的方法和请求的url是什么
    // console.log(req.method, req.url);
    let url = req.url;
    
    //自定一种规则,凡是以这种规则访问的url,我都给定位到 public 下的对应资源文件中。
    //静态资源代理:我们把静态资源文件存放在服务器的某个位置
    //'/public/index.html'.startsWith('/public') //true
    
    if (url.startsWith('/public')) {
      let content = fs.readFileSync('.' + url);
      res.write(content);
      return res.end();
    }

    if (url.startsWith('/quote')) {
      res.write("<head><meta charset='UTF-8'></head>")
      res.write(JSON.stringify(quotes[Math.round((Math.random() * (quotes.length-1)))]));
      return res.end();
    }

    if (url.startsWith('/users')) {
      res.write( JSON.stringify(users) );
      return res.end();
    }
    
    
    if (url.startsWith('/tupianba')) {
      // MIME : image/png
      res.setHeader('content-type', 'image/png');
      let content = fs.readFileSync('./public/logo.png');
      res.write(content);
      return res.end();
    }

	
    
    if (url.startsWith('/index.css')) {
        res.setHeader('content-type', 'text/html');
        let content = fs.readFileSync('./public/index.css');
        res.write(content);
        return res.end();
    }
}


server.listen(9999, function() {
    console.log('服务启动成功');
});

常见的MIME类型

常见的MIME类型(通用型):
超文本标记语言文本 .html text/html
xml文档 .xml text/xml
XHTML文档 .xhtml application/xhtml+xml
普通文本 .txt text/plain
RTF文本 .rtf application/rtf
PDF文档 .pdf application/pdf
Microsoft Word文件 .word application/msword
PNG图像 .png image/png
GIF图形 .gif image/gif
JPEG图形 .jpeg,.jpg image/jpeg
au声音文件 .au audio/basic
MIDI音乐文件 mid,.midi audio/midi,audio/x-midi
RealAudio音乐文件 .ra, .ram audio/x-pn-realaudio
MPEG文件 .mpg,.mpeg video/mpeg
AVI文件 .avi video/x-msvideo
GZIP文件 .gz application/x-gzip
TAR文件 .tar application/x-tar
任意的二进制数据 application/octet-stream

端口的意义

  • 一台主机的网卡数量是有限的,不可能为主机上的每一个程序去安装一个网卡。为了解决不同的应用程序共享网卡而不至于数据混乱的问题,系统会准备一批端口(类似银行窗口),由需要使用端口进行数据通信的程序去申请(监听),申请成功以后,就可以进行网络数据交互了。通常,监听端口数据需要程序主动指定。发送请求由系统随机分配。
  • 端口取值范围为:1-65535,即 216 。通常约定1-1023之间的端口为系统常见程序预留端口,1024-5000为通信临时随机端口,5000以后为用户自定端口。
const http = require('http');
const server = http.createServer();
server.listen(8888, '127.0.0.1', () => {
  console.log('服务已经启动了,http://127.0.0.1:8888');
});

几个常用的npm指令和几个常用的热门包

cnpm uninstall ModuleName ---卸载某个模块
cnpm list 查看当前目录下已安装的node包
cnpm info jquery 查看jquery的版本(以jquery举例)
指定包版本安装 copm install jquery @1.8.1
silly-datetime ---格式化日期的包
node-media-server ---视频流媒体的包
mkdirp ---创建目录的包
vscode热门插件
--->node snippets
--->node debug

package.json配置文件解析

package.json配置文件解析
----可通过指令`cnpm init -y`生成


{
  "name": "demo04", 
  "version": "1.0.0",
  "description": "",
  //入口文件
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  //开发依赖   
  "dependencies": {
    "md5": "^2.3.0",
    "express":"^4.13.3"
  },
  //生产依赖    
  "devDependencies":{
  	
  }
}

---> "md5": "^2.3.0"
^表示第一位版本号不变,后面两位取最新的
~表示前两位不变,最后一个取最新的
*表示全部取最新

//删除node_modules后可以用npm install下载回来
//前提是要有package.json记录着信息
//找node_modules的时候如果找不到会一直往上一层文件夹查找
//最终找到全局目录下的node_modules
//(可以通过npm root -g查看全局目录下的node_modules)
//你这个工具是很多项目都需要用到的还是就那几个或一个需要用到的
//决定了是否需要安装在全局下 cnpm i axios -g

fs模块

fs.stat 检测是文件还是目录
fs.mkdir 创建目录
fs.writeFile 创建写入文件
fs.appendFile 追加文件
fs.readFile 读取文件
fs.readdir 读取目录
fs.rename 重命名
fs.rmdir 删除目录
fs.unlink 删除文件

文件操作
fs.writeFile 创建文件并写入内容
fs.readFile 读取文件
fs.rename 重命名文件 和 移动文件
fs.unlink 删除文件
fs.copyFile 复制文件 

目录操作
fs.mkdir 创建目录
fs.rename 重命名 修改目录名称
fs.readdir 读取目录
fs.rmdir 删除目录(一定要是空文件夹或者空目录)

通用方法
fs.exists 判断文件或者目录是否存在

该构建目录为:

fs.stat检查是文件还是目录的运用一:

这里是文件index.js的页面代码---视角是index.js

fs.stat('./html',(err,data)=>{
    if(err){
        console.log(err)
        return;
    }
    console.log(`是文件:${data.isFile()}`)
    console.log(`是目录:${data.isDirectory()}`)
})

//是文件:false
//是目录:true

fs.stat检查是文件还是目录的运用二:

这里是文件index.js的页面代码---视角是index.js

fs.stat('./package.json',(err,data)=>{
    if(err){
        console.log(err)
        return;
    }
    console.log(`是文件:${data.isFile()}`)
    console.log(`是目录:${data.isDirectory()}`)
})

//是文件:true
//是目录:false

fs.mkdir 创建目录的运用一:

/**
 * path  将创建的目录路径
 * mode  目录权限(读写权限),默认777
 * callback 回调,传递异常参数err
 */
这里是文件index.js的页面代码---视角是index.js

fs.mkdir('./css',(err)=>{
    if(err){
        console.log(err);
        return 
    }
    console.log('创建成功')
})

fs.writeFile 创建写入文件

这里是文件index.js的页面代码---视角是index.js

fs.writeFile('./html/index.html','再次写入内容',(err)=>{
    if(err){
        console.log(err)
        return;
    }
    console.log('创建写入文件成功')
})

//如果已经创建了该文件又用此方法调用
//会覆盖之前的内容

fs.writeFile('./html/index.html','再次的再次写入内容',(err)=>{
    if(err){
        console.log(err)
        return;
    }
    console.log('创建写入文件成功')
})

//fs.writeFile还可以配置第二个参数{flag:'w'}(默认)

//a:追加写入  w:写入(默认,会覆盖之前的)   r:读取(在这个场景里不应该写r)  


fs.writeFile('./html/index.html',{flag:'w'},'再次的再次写入内容',(err)=>{
    if(err){
        console.log(err)
        return;
    }
    console.log('创建写入文件成功')
})

fs.appendFile 追加文件

/*
文件不存在时会创建再去追加内容
文件存在时只会起到追加内容的作用
*/
这里是文件index.js的页面代码---视角是index.js

fs.appendFile('./css/base.css','body{color:pink}',(err)=>{
    if(err){
        console.log(err)
        return;
    }
    console.log('追加文件成功')
})

fs.readFile 读取文件

这里是文件index.js的页面代码---视角是index.js

fs.readFile('./html/index.html',(err,data)=>{
    if(err){
        console.log(err);
        return;
    }
    console.log(data)  //得到的数据是Buffer
    console.log(data.toString());//把Buffer转换为string类型
})

//fs.readFile可以指定第二个参数编码格式,
//不指定也可以读取数据但是数据读取到的是buffer数据(可以使用toString转换)


fs.readFile('./html/index.html',‘utf-8’,(err,data)=>{
    if(err){
        console.log(err);
        return;
    }
    console.log(data)  //得到的数据是utf8的字符
})

fs.readdir 读取目录

这里是文件index.js的页面代码---视角是index.js

fs.readdir('./html',(err,data)=>{
    if(err){
        console.log(err);
        return;
    }
    console.log(data) 
//[ 'index.css', 'index.html', 'js', 'news.html' ]
})

fs.rename 重命名

功能 ---重命名文件 和 移动文件
这里是文件index.js的页面代码---视角是index.js
功能一:重命名文件

fs.rename('./css/aaa.css','./css/index.css',(err)=>{
    if(err){
        console.log(err);
        return;
    }
    console.log('重命名成功')
})
这里是文件index.js的页面代码---视角是index.js
功能二:重命名并移动文件

fs.rename('./css/index.css','./html/index.css',(err)=>{
    if(err){
        console.log(err);
        return;
    }
    console.log('重命名—移动成功')
})

fs.rmdir 删除目录

如果目录里面有文件是不能直接删除的
这里是文件index.js的页面代码---视角是index.js

fs.rmdir('./aa',(err)=>{
    if(err){
        console.log(err);
        return;
    }
    console.log('删除目录成功')
})

fs.unlink 删除文件

这里是文件index.js的页面代码---视角是index.js

fs.unlink('./aa/index.html',(err)=>{
    if(err){
        console.log(err);
        return;
    }
    console.log('删除文件成功')
})

  • fs.copyFile复制文件
fs.copyFile('index.html','myjindex.html',(err)=>{
	if(err){
		return console.log(err)
	}
	
	console.log('复制成功!')
})


//自己动手写一个轮子
//先去读取文件在把结果数据写入新的文件
function mycopy(stc,dest){
	fs.writeFileSync(dest,fs.readFileSync(src))
}

mycopy('index.html','test.html')
  • Sync同步操作
//fs.writeFile写入文件和fs.readFile读取文件都是异步的
//所有文件操作 没有加Sync都是异步操作 其他都是同步操作
//(拓展思维)

//同步操作不存在回调函数直接拿返还值即可
let data=fs.readFileSync('1.txt');
console.log(data)

  • fs.exists判断文件或者目录是否存在
//判断文件或者目录是否存在
//如果想要创建一个目录或文件可以用此方法先判断一下是否已经被创建

fs.exists('index.html',exists=>{
	console.log(exists)
})

//存在返回true
  • 案例一:删除非空文件夹
removeDir('./html')
function  removeDir(path){
	//data中是该目录中所有的文件或文件夹组成的数组
	let data=fs.readdirSync(path);
	for(let i=0;i<data.length;i++){
		//判断是文件直接删除判断是目录继续查找删除
		//根据文件所在位置去确定路径(文件路径不同这个的路径拼接方法不同)
		let url=path+'/'+data[i];
		let stat=fs.statSync(url);
		if(stat.isDirectory()){
			//继续查找
			removeDir(url)
		}else{
			//文件直接删除
			fs.unlinkSync(url)
		}
	}
	//跳出循环后删除空目录
	fs.rmdirSync(path)
}

Buffer

//10字节的Buffer
// let buffer = Buffer.alloc(10);
// console.log(buffer);
//<Buffer 00 00 00 00 00 00 00 00 00 00>


//一个中文占据三个字节
// let buffer = Buffer.from("大家好");
// console.log(buffer);
//<Buffer e5 a4 a7 e5 ae b6 e5 a5 bd>


// let buffer = Buffer.from([0xe5,0xa4,0xa7,0xe5,0xae,0xb6,0xe5,0xa5,0xbd]);
// console.log(buffer.toString());
//‘大家好’



let buffer1 = Buffer.from([0xe5,0xa4,0xa7,0xe5]);
let buffer2 = Buffer.from([0xae,0xb6,0xe5,0xa5,0xbd]);
// console.log(buffer1.toString());
let newbuffer = Buffer.concat([buffer1,buffer2]);
console.log(newbuffer.toString());
//不全的字节想转为中文正常情况下是不行的


//不全的字节想转为中文可以使用StringDecoder
// let { StringDecoder } = require("string_decoder");
// let decoder =  new StringDecoder();
// let res1 = decoder.write(buffer1);
// let res2 = decoder.write(buffer2);
// console.log(res1+res2);
// console.log(res2);