Node.js学习(2)

143 阅读6分钟

​ 持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第2天,点击查看活动详情

今天继续卷node!

Node.js中模块加载机制

1.模块查找规则-当模块拥有路径但没后缀时

require('./xxx.js')

require('./xxx')

require方法根据模块路径查找模块,如果是完整路径,直接引入模块

如果模块后缀省略,先找同名JS文件再找同名JS文件夹

如果找到了同名文件夹,找文件夹中的index.js文件

如果文件夹中没有index.js就会去当前文件夹中的package.js文件中查找main选项中的入口文件

2.模块查找规则-当模块没有路径且没有后缀时

require('xxx')

Node.js会假设它是系统模块

Node.js会去node_modules文件夹中

首先看是否有该名字的文件夹

再看是否有该名字的文件夹

如果是文件夹看里面是否有index.js

如果没有index,js查看该文件夹中的package.json中的mian选项确定模块入口文件

否则找不到会报错

Node.js全局对象global

在浏览器中全局对象是window,在Node中全局对象是global

Node中全局对象下有以下方法,可以在任何地方使用,global可以省略

console.log() 在控制台输出

setTimeout() 设置超时定时器

clearTimeout() 清除超时定时器

setInterval() 设置间歇定时器

clearInterval() 清除间歇定时器

例子

global.console.log('global对象下的console.log方法')

global.setTimeout(function(){
  console.log('123');
},2000)

//控制台输出结果

global对象下的console.log方法
123

当去掉global时依旧可以输出,说明global就是全局变量

​编辑

 ​编辑

服务器端基础概念

网站的组成

网站应用程序主要分为两大部分:客户端和服务器端

客户端:在浏览器中运行的部分,就是用户看到并与之交互的界面程序。使用HTML、CSS、JS构建。

服务器端:在服务器中运行的部分,负责存储数据和处理应用逻辑

Node网站服务器

能够提供网站访问服务的机器就是网站服务器,它能够接受客户端的请求,能够对请求做出响应

IP地址

互联网中设备的唯一标识

IP是Internet Protocol Address的简写,代表互联网协议地址

域名

由于IP地址难以记忆,所以产生了域名的概念,所谓域名就是平时上网所使用的网址

www.xxxx.com => xxx.xxx.xxx.xxx/

虽然在地址栏输入的是网址,但是最终还是会将域名转换为ip才能访问到网站服务器地址

端口

端口是计算机与外界通讯交流的出口,用来区分服务器电脑中提供不同的服务

URL

统一资源定位符,又叫URL(Uniform Resource Locator),是专为标识Internet网上资源位置而设的一种编址方式,我们平时所说的网页地址指的即是URL

URL的组成

传输协议://服务器IP或域名:端口/资源所在位置标识

http:超文本传输协议,提供了一种发布和接收HTML页面的方法

开发过程中客户端和服务器说明

在开发阶段,客户端和服务器端使用同一台电脑,即开发人员电脑

本机的域名:localhost

本地IP:127.0.0.1

创建web服务器

//用于创建网站服务器的模块
const http = require('http');
//app对象就是网站服务器对象
const app =http.createServer();
//当客户端有请求来的时候
app.on('request',(req,res)=>{
  res.end('<h2>hello user</h2>');
});

app.listen(3000);
console.log('服务器启动成功!');

​编辑

HTTP协议

HTTP协议的概念

超文本传输协议(HyperText Transfer Protocol)规定了如何从网站服务器传输超文本到本地浏览器,它基于客户端服务器架构工作,是客户端(用户)和服务器端(网站)请求和应答的标准

报文

在HTTP请求和响应的过程中传递的数据块就叫报文,包括要传送的数据和一些附加信息,并且要遵守规定好的格式。

请求报文

请求方式(RequestMethod)

GET 请求数据

POST 发送数据

请求地址(Request URL)

app.on('request',(req,res) =>{

        req.headers //获取请求报文

        req.url         //获取请求地址

        req.method  //获取请求方法

});

例子

//用于创建网站服务器的模块
const http = require('http');
//app对象就是网站服务器对象
const app =http.createServer();
//当客户端有请求来的时候
app.on('request',(req,res)=>{
//获取请求方式
//req.method
  // console.log(req.method);
  //获取请求地址 
  //req.url
  // console.log(req.url);
  //获取请求报文信息
  //req.headers
  console.log(req.headers['accept']);
  if(req.url =='/index' ||req.url == '/'){
    res.end('Weclome to 主页')
  }else if(req.url == '/list'){  
    res.end('欢迎来到 list页面')
  }else{
    res.end('not found');
  }
  // if(req.method == 'POST'){
  //   res.end('post')
  // }else if(req.method=="GET"){
  //   res.end('get')
  // }
});
app.listen(3000);
console.log('服务器启动成功!');

响应报文

1.HTTP状态码

200 请求成功

404 请求的资源没被找到

500 服务器端错误

400 客户端请求语法错误

2.内容类型

text/html

text/css

application/javascript

image/jpeg

application/json

例子

//用于创建网站服务器的模块
const http = require('http');
//app对象就是网站服务器对象
const app =http.createServer();
//当客户端有请求来的时候
app.on('request',(req,res)=>{
  //res.writeHead()可以设置状态码和响应头信息
  res.writeHead(200,{
    'content-type':'text/html;charset=utf8'
  });
});
app.listen(3000);
console.log('服务器启动成功!');

HTTP请求与响应处理

请求参数

客户端向服务器端发送请求时,有时需要携带一些客户信息,客户信息需要通过请求参数的形式传递到服务器端,比如登录操作。

GET请求参数

参数被放置在浏览器地址栏中,例如:http://localhost:3000/?name=zhangsan&age=20

const url =require('url')
console.log(req.url);
  //第一个参数是要解析的url地址,第二个参数将查询参数解析成对象形式
  let {query,pathname}=url.parse(req.url,true);
  console.log(query.name);
  console.log(query.age);
  if(pathname =='/index' || pathname == '/'){
    res.end('<h2>Weclome to 主页</h2>')
  }else if(pathname == '/list'){  
    res.end('欢迎来到list页面')
  }else{
    res.end('not found');
  }

POST请求参数

参数被放置在请求体中进行传输

获取POST参数需要使用data事件和end事件

使用querystring系统模块将参数转换为对象格式

例子

//用于创建网站服务器的模块
const http = require('http');
//用于处理url地址
const url = require('url')
//app对象就是网站服务器对象
const app =http.createServer();
//当客户端有请求来的时候
//处理请求参数模块
const querystring = require('querystring');

app.on('request',(req,res)=>{
    //post参数是通过事件的方式接受的
    //data 当请求参数传递的时候触发data事件
    //end 当参数传递完成的时候触发end事件
    let postParams ='';
    req.on('data',params =>{
      postParams += params;
    }); 
    req.on('end',() =>{
    console.log(querystring.parse(postParams));
    });
    res.end('ok')
  });
app.listen(3000);
console.log('服务器启动成功!')

静态资源

服务端不需要处理,可以直接响应给客户端的资源就是静态资源,例如CSS、JavaScript、image文件

例子:

这里展示的是后端代码,前端自行创建,前端统一存放在public

//用于创建网站服务器的模块
const http = require('http');
const url =require('url');
const fs =require('fs');
const path =require('path');
const mime = require('mime');
const app =http.createServer();
app.on('request',(req,res)=>{
  //获取用户的请求路径
  let pathname=url.parse(req.url).pathname;
  pathname=pathname == '/'?'/default.html' :pathname;
  //拼接路径的时候最好使用上系统模块path下面的join方法
  let realPath=path.join(__dirname,'public'+pathname)
  //mime的getType可以根据路径返回资源的类型
  let type=mime.getType(realPath)
  //读取文件
  fs.readFile(realPath,(error,result)=>{
    //如果文件读取失败
    if(error!=null){
      res.writeHead(404,{
        'content-type':'text/html;charset=utf8'
      })
      res.end('文件读取失败');
      return;//终止
    }
    res.writeHead(200,{
      'content-type':type
    })
    res.end(result)
  })
});
app.listen(3000);
console.log('服务器启动成功!');

动态资源

相同的请求地址不同的响应资源

路由

路由是指根据客户端请求地址与服务器程序代码的对应关系。简单的说,就是请求什么就响应什么。

​编辑

 例子

//1.引入系统模块http
//2.创建网站服务器
//3.为网站服务器对象添加请求事件
//4.实现路由
// 1.获取客户端的请求方式
// 2.获取客户端的请求地址
const http = require('http')
const url = require('url')
const app =http.createServer();

app.on('request',(req,res)=>{
  //获取请求方式
  const method = req.method.toLowerCase();//toLowerCase把方法转换成小写
  //获取请求地址
  const pathname=url.parse(req.url).pathname;
  res.writeHead(200,{
    'content-type':'text/html;charset=utf8'
  })
  if(method == 'get'){
    if(pathname =='/'||pathname=='/index'){
      res.end('欢迎铁汁!!!')
    }else if(pathname == '/list'){
      res.end('欢迎来到列表页')
    }else {
      res.end('不存在呀!')
    }
  }else if(method == 'post'){

  }
})
app.listen(3000);
console.log('服务器启动了...');

同步与异步

同步API:只有当前API执行完成后,才能继续执行下一个API

异步API:当前API的执行不会阻塞后续代码的执行

异步的例子:

console.log('前');
setTimeout(()=>{
  console.log('最后');
},2000)
console.log('后');

同步和异步的区别1(获取返回值)

同步API可以从返回值中拿到API执行的结果,但是异步API是不可以的

异步API不会阻塞后续代码执行,这里的例子虽然没有写return,但是默认会返回undefined

function getMsg(){
  setTimeout(()=>{
    return {
      msg:'hello node.js'
    }
  },2000)
}
const msg =getMsg();
console.log(msg);
//结果是undefined

异步API的返回值要怎么拿?

答案就是回调函数

回调函数

自己定义函数让别人去调用

回调函数的简单例子

function getData(call){
  call('123') //这里的123是实参
}
getData((n)=>{//n表示形参
  console.log(n);
  console.log('回调函数被调用了');
})

同步和异步的区别2(代码执行顺序)

同步API从上到下依次执行,前面代码会阻塞后面代码的执行

例子:

for (var i=0;i<100000;i++){
  console.log(i);
}
console.log('for后面的代码');

异步API不会等待API执行完成后再向下执行代码

例子

console.log('代码开始执行');
setTimeout(()=>{
  console.log('2s');
},2000)
setTimeout(()=>{
  console.log('0');
},0)
console.log('代码执行结束');
/*
  代码开始执行
  代码执行结束
  0
  2s
*/

node.js中首先会执行同步代码,再执行异步代码,最后执行回调函数

​编辑

Node.js中的异步API

如果异步API后面代码执行依赖当前异步API的执行结果,但实际上后续代码在执行的时候异步API还没有返回结果,怎么解决呢?

最直接的方式就是将后续代码放进当前异步代码的回调里,但这样会造成回调地狱,怎么解决呢?

const fs= require('fs');
//回调嵌套回调,这就是回调地狱
fs.readFile('./1.txt','utf8',(err,result1)=>{
  console.log(result1);
  fs.readFile('./2.txt','utf8',(err,result2)=>{
    console.log(result2);
    fs.readFile('./3.txt','utf8',(err,result3)=>{
      console.log(result3);
    })
  })
});
//结果依次输出1,2,3

Promise

Promise出现是为了解决Node.js异步编程中回调地狱的问题

Promise是个构造函数

例子:

const fs = require('fs');

let promise =new Promise((resolve,reject)=>{
  fs.readFile('./11.txt','utf8',(err,result)=>{
    if(err != null){
      reject(err)
    }else {
      resolve(result)
    }
  });
});
//promise提供了链式编程的写法
promise.then((result)=>{
  console.log(result);
    //结果输出1
}).catch((err)=>{
  console.log(err);
    //输出错误信息
})

Promise解决回调地狱

const fs = require('fs');
function p1(){
  return new Promise((res,rej)=>{
    fs.readFile('./1.txt','utf8',(err,result)=>{
      res(result);
    })
  })
}
function p2(){
  return new Promise((res,rej)=>{
    fs.readFile('./2.txt','utf8',(err,result)=>{
      res(result);
    })
  })
}
function p3(){
  return new Promise((res,rej)=>{
    fs.readFile('./3.txt','utf8',(err,result)=>{
      res(result);
    })
  })
}
p1().then((r1)=>{
  console.log(r1);
  return p2();
}).then((r2)=>{
  console.log(r2);
  return p3();
}).then((r3)=>{
  console.log(r3);
})

异步函数

异步函数是异步编程语法的终极解决方案,它可以让我们将异步代码写成同步的形式,让代码不再有回调函数嵌套,使代码变得清晰明了

async关键字

1.普通函数定义前加async关键字 普通函数变成异步函数

2.异步函数默认返回promise对象

3.在异步函数内部使用return关键字进行结果返回 结果会被包裹的promise对象中 return关键字代替了resolve方法

4.在异步函数内部使用throw关键字抛出程序异常

5.调用异步函数链式调用then方法获取异步函数执行结果

6.调用异步函数链式调用catch方法获取异步函数执行的错误信息

await关键字

1.await关键字只能出现在异步函数中

2.await promise await后面只能写promise对象,写其他类型的API是不可以的

3.await关键字可以暂停异步函数向下执行,直到promise返回结果

例子:

//1.在普通函数定义的前面加入async关键字 普通函数就变成异步函数
//2.异步函数默认的返回值是promise对象
//3.在异步函数内部使用throw关键字进行错误抛出
//await关键字 
//1.它只能出现在异步函数中
//2.await promise 可以暂停异步函数的执行 等待promise对象返回结果后再向下执行
// async function fn(){
//   //throw代替reject了输出错误信息
//   throw '搞错咯!';//throw执行后后面的代码就不执行
//   return 123;
// }
// //console.log(fn());//异步函数默认返回值Promise { undefined }
// fn().then((data)=>{
//     console.log(data);
// }).catch((err)=>{
//     console.log(err);
// })

async function p1(){
  return 'p1';
}
async function p2(){
  return 'p2';
}
async function p3(){
  return 'p3';
}
async function run(){
  //await 可以让下面的函数依次执行
  let r1=await p1()
  let r2=await p2()
  let r3=await p3()
  console.log(r1);
  console.log(r2);
  console.log(r3);
}
run()

用异步函数来解决之前的回调地狱

const fs =require('fs');
const promisify=require('util').promisify;//可以对现有的node.js当中的异步API进行包装,让它返回promise对象,然后才能支持异步函数的写法
const readFile=promisify(fs.readFile);

async function run () {
  let r1 = await readFile('./1.txt','utf8')
  let r2 = await readFile('./2.txt','utf8')
  let r3 = await readFile('./3.txt','utf8')
  console.log(r1);
  console.log(r2);
  console.log(r3);
}
run()

\