nodejs学习随笔

时间:2021-11-24 12:57:48

<一> 简述nodejs
(社区:www.npmjs.com)可查找一些第三方模块。node.js的包管理系统已经成为世界上最大的开源库生态系统。JavaScript已经成为github上使用最多的语言。

  
  node提供大量的工具库,使得JavaScript可以调用操作系统级别的API(读取文件、开机、关机等),这些功能都是都是单纯的js不具备的。
  nodejs是可以让js运行在浏览器之外的服务器端的平台,是JavaScript语言的服务器运行环境,实现了文件系统、模块、包、操作系统API、网络通信等JS没有的功能,但是没有DOM和BOM。nodejs使用了来自Google ChromeV8引擎,V8引擎是世界上最快的js引擎(chrome浏览器也是使用的v8引擎,引擎就相当于是个解析器,将我们的代码解析为计算机可以识别的语言)。nodejs摒弃了传统平台依赖多线程来实现高并发的设计思路,采用的单线程、异步式I/O,事件驱动式的程序设计模型。(下面以实际生活中的餐厅举例说明)

  nodejs的主线程是单线程的(后台I/O是多线程的),所有阻塞的部分交给一个线程池处理,主线程通过一个队列跟线程池协作。所以nodejs内部实现是单线程非阻塞I/O异步编程。
先说几个概念
  ·多线程:就像是多个服务员同时工作,一个服务员负责一个顾客(开销大)。
  ·单线程:就像是一个餐厅只有一个服务员。
  ·回调:异步变成基本的方法。
  ·同步:就像一个服务员负责一位顾客,直到该顾客的餐全部上齐才会去负责下一位顾客。
  ·异步:就像一个服务员负责一位顾客点餐,然后菜单给了厨房之后就可以给下一位顾客点餐了,当第一个顾客的餐做好之后,厨师呼叫这个唯一的服务员,该服务员再给第一位顾客送餐。因此可知,nodejs是单线程异步编程。
  ·非阻塞:针对内核来说,向内核发起请求的时候不会阻塞主线程的执行,是实现异步的前置条件。如果厨师比作内核,那么非阻塞就是不让唯一的服务员等待,阻塞就是让唯一的服务员等待。一般来说,阻塞了就是同步了。
  ·I/O:Input从文件系统中读取文件(输入),Output向文件系统输入文件(输出)。输入输出的过程就像是做饭的过程。
  ·事件环:主线程按时间执行事件队列,由普通函数与回调函数组成。
<二> 全局对象
nodejs中的全局对象global,相当于js中的window。其中global也是global的属性,并且可以无限循环下去(global.global.global...)。
  __filename 表示当前模块的文件绝对路径
  __dirname 表示模块所在目录的绝对路径
  setImmediate 把参数函数放在下一个事件环中的顶部执行,相当于setTimeOut(fn,0),但效率更高一些。(见图1)

  Process(global属性)
    1,cwd:process.cwd() //输出当前的工作目录
    2,chdir:process.chdir("..") //切换到上级工作目录
    3,memoryUsage:process.memoryUsage() //内存的使用量,其中rss表示常驻内存(栈),heapTotal表示堆的总内存,heapUsed表示堆使用内存。nodejs的内存限制为1.4G左右,V8最多能用1.4G内存。
    4,nextTick:process.nextTick(fn)放在当前任务列表的尾部(见图1)

  global属性可以直接使用。其他不是global的属性,需要用require进行引用之后才可以使用。

Tips:
console.time("cost");
这段代码执行的时间
console.timeEnd("cost");

nodejs学习随笔

                  图1

<三>发布一个包
模块:在node中,每个js就是一个模块,module代表当前模块即当前js。用require加载同一个模块时,属于“===”的关系,因为require有缓存,即require.cache(存放着所有的模块缓存),用delete require.cache[某个模块],就可以从缓存中删除该模块。require是同步的,模块内部的变量是私有的。模块有三种:node自带模块、第三方模块和自定义模块。
  初始化一个项目,包名不能含大写,例如:
  1,创建并进入目录 mkdir fengmin \\保证名字的唯一
  2,初始化项目 npm init fengmin
  3,编写命令行工具 app.js
  4,在package.json中添加 "bin":{"fengmin":"./app.js"}
  5,npm publish发布项目
  6,npm install fengmin -g \\全局安装自己发布的模块
全局安装:
  1,查看全局安装的根路径 npm root -g
  2,把模块安装到全局node_modules下面
  3,配置可执行文件
package.json相关概念:
  一个包下面如果有package.json,则可在package.json文件中用main指定入口文件,如果没有该文件,则默认是该包下面的index.js为入口文件。

<四>Buffer
全局对象,暂时存放输入输出数据的一段内存。
js没有二进制数据类型,但在处理TCP和文件流(图片、音视频等),必须要处理二进制数据,所以服务器端需要用二进制数据。node提供的Buffer对象对二进制进行操作,是一个全局对象,要提前确定放到缓存区中的字节数。
1Byte = 8bit,一个中文用两个字节保存。
parseInt可以把任意进制转换为十进制,如:parseInt("111111",2)和parseInt("0xff",16)
把十进制转成任意进制用{}.toString,如:(255).toString(16)。
定义Buffer的三种方式:
  1,var buffer = new Buffer(size),size表示指定的Buffer的长度,每次实例的新Buffer,输出可能不一样。buffer像是一个字节数组,可以直接赋值,如buffer[0]=0,也可以buffer.fill(0),把buffer中所有元素置为0。
  2,传个数组,buffer的长度就是数组的长度。如:var buffer = new Buffer([1,2,3,4,5,6]),其中传入的数组一定是数字数组,且数字的范围为0~255,如果不符合传入规则,则默认为0。
  3,字符串创建,var buffer = new Buffer(str,[encoding]),其中可选编码参数encoding的默认值为utf-8。如:var buffer = new Buffer("abc"),可得到其分别对应ASC码对应的buffer值,为[97,98,99]。
buffer的常用方法
  1,合并buffer:Buffer.concat([buf1,buf2],length);
  2,复制Buffer:Buffer.copy(targetBuffer,targetstart,sourcestart,sourceend)
  3,判断一个对象是否Buffer用isBuffer方法,如:Buffer.isBuffer(buffer);

<五>Util模块
node自带的模块,并不是global的属性,所以需要用require引用。
  var util = require("util");
  1,util.inherits //子类可以继承父类原型上的方法
  2,util.inspect //
  3,isArray、isRegExp、isDate、isError

<六>文件和路径
node中用fs实现文件的读写,fs模块中,所有的异步方法的回调函数的第一个参数都是error对象,所以异步回调方法中的err有值,则说明出错了。

    var fs = require("fs")。
  fs.readFile //异步方法,在回调方法处理数据
  fs.readFileSync("./1.txt") //同步方法读取,有返回值,所有的异步方法一定会晚于同步方法的执行
  fs.writeFileSync("./2.txt","fengmin",{encoding:"utf8"}) //同步方法写入
  fs.writeFile //异步方法写入,有回调函数

1,目录操作
创建一个目录,与linux下的创建相似
  fs.mkdirSync("a");
  fs.mkdir("a",function(err){})
读取目录下的文件,结果是一个数组

fs.readdir("./a",function(err,files){
files.forEach(function(file){
//此处如果file是目录会报错,因此需要用isFile判断一下
fs.readFile("./a/"+file,function(err,data){
console.log(data.toString());
})
})
})

判断一个文件是否存在
  fs.exist("./a",function(exist){//node中回调的特殊例子,第一个参数不是err,而是一个布尔值})
2,路径操作
  var path = require("path");
  路径的“/”,在windows和linux下不一致,windows下是“\”,linux下是“/”。
  常用方法:
    1,path.join将多个参数值字符串合并为一个路径字符串 //把多个路径合并为一个路径,如:path.join(__dirname,"a","b","c");
    2,path.resolve以应用程序根目录为起点,根据参数解析出一个绝对路径。普通字符串的参数表示当前目录的下级目录;“..”参数表示回到上一级目录;“/”开头表示一个绝对的根目录
    3,path.basename获取路径中的文件名
      path.basename(__filename) //path.js
      path.basename(__filename,".js") //path
    4,extname获取路径中的扩展名
      path.extname(__filename) //.js
    5,delimiter操作系统的分隔符,如环境变量中用到的,windows中用的是“;”,linux中用的是“:”
    6,sep路径的分隔符,如“\”与"/"
    7,normalize 将非标准的路径字符串转为标准的。如:path.normalize("a/../b////c/d//")可以解析为b\c\d\
    8,relative用于获取两个路径之间的相对关系。

<七>事件

发布/订阅模式
EventEmitter方法
on相当于addListener,可给某一个事件绑定多个回调函数
once 事件只绑定一次,执行一次就不再执行了
removeListener("事件名",callback),移除某事件监听的某一个回调函数
removeAllListener
emit("事件名","回调的参数")

var EventEmitter = require("events")
var events = new EventEmitter;
events.addListener("click",function(){console.log("click")})
events.emit("click");//click就是个名字,可随意换
/*========*/
var util = require("util");
function Girl(){}
util.inherit(Girl,EventEmitter);
var girl = new Girl();
girl.on("hungry",function(){console.log("吃饭")});
girl.emit("hungry");

<八>服务器

根据请求的路径返回相应的结果,IP定了,一般地址也就确定了。可以使用netstat -anto查看网络状态,一个普通网站的访问过程是:
1,浏览器向服务器发出一个http请求
2,先把域名解析为IP地址(先找浏览器缓存:chrome://net-internal/#dns,如果找不到就会搜索操作系统缓存,再找不到就会读取本地的host文件,再找不到就会发起DNS系统调用、运营商DNS缓存、根域、com域.....)
3,客户端通过随机端口向服务器发起TCP三次握手,建立TCP连接
4,连接建立后,浏览器就可以发送http请求了
webpack可以把所有用到的资源打包为一个,减少http请求
5,服务器接收请求后,解析请求的路径和参数,后台处理

<九>nodejs中的流

流:一组有序的、有起点和终点的字节数据传输手段。是nodejs各种对象实现的抽象接口。所有的stream对象都是EventEmitter的实例,可以发射事件。上一个的输出是下一个的输入,就像是人与人之间传递东西。如:request、response、stdout(标准输出流)、console也是一种流。流是EventEmitter的子类,
stream.Readable 可读流
  可读流触发的事件有:data、end、error
  可读流的方法有:setEncoding、pause(停止触发data事件)、resume(恢复触发data事件)、pipe
  创建一个可读流
  fs.createReadStream(path,[options]),其中,path是读取文件的路径;opations中,hightWaterMark,默认为64kb
stream.Writable 可写流
  可写流的方法有:write、end、drain
  创建一个可写流,fs.createWriteStream(path,[options]),其中,options中的hightWaterMark,默认为16kb

<十>模块

每个js文件都是一个模块,模块内部声明的变量都是私有变量,外部无法访问

  ·创建模块就是创建一个js文件 math.js

  ·导出模块使用exports对象,在该js文件中exports.add=function(a,b){return a+b}

  ·加载模块 var math = require("./math.js")

  ·调用模块 var sum = math.add(1,2)

模块可以分为三类

  ·核心模块 http fs path等

  ·文件模块 var math = require('./math.js')

  ·第三方模块  var async = require('async')

  三种类型的模块,都是通过require加载的。

<十一>包和npm

多个模块可以封装成一个包,npm是nodejs默认的包管理器,用来安装和管理node模块,网址为http://npmjs.org,可以以包的方式通过npm安装、卸载、发布包

如何初始化一个项目:

  mkdir studynode 创建目录

  cd studynode 进入目录

  npm init 初始化项目描述文件

  注意项目的名称不能是别人已经注册的名称

安装第三方模块

  ·全局安装 直接下载到node的安装目录中,各个项目都可以调用,适合工具模块,比如gulp。

  npm install -g [package name]

  ·本地安装 将一个模块下载到当前目录的node_modules子目录中,然后只有在当前目录和它的子目录之中,才能调用这个模块

  npm install [package name]

<十二>HTTP

服务器:可以是专业服务器,也可以是个人电脑,与硬件无关,与功能有关。能在特定IP服务器的特定端口上监听客户端的请求,并根据请求的路径返回相应结果都叫服务器。

客户端:只要能向特定IP服务器的特定端口发起请求并接受响应的都叫客户端,比如mac、iPhone、ipad等。

数据在服务器和客户端之间传递,比如文档、图片、音视频等。可以把服务器硬盘上已经有的静态文件发送给客户端,也可以由服务器经过逻辑处理生成的动态内容返回给客户端,比如计算出来的字符串或者json串。

要让这些形形色色的机器能够通过网络进行交互,我们就需要指明一种协议(比如http/https)和一种数据封装格式(比如HTML/JSON)。http指的就是这种协议+数据格式的交流体系。

一个http请求一般包括方法、url、协议版本和请求首部字段几部分

一个http响应一般包括协议版本、状态码头、状态码的原因短语和响应首部字段等

一个普通网站访问的过程:

  ·浏览器(或其他客户端如微信)向服务器发出一个HTTP请求

  ·先把域名解析为IP地址(chrome缓存一分钟(chrome://net-internals/#dns)->搜索操作系统缓存->读取本地host文件->发起DNS系统调用->运营商DNS缓存->找根域->com域)

  ·客户端通过随机端口向服务器发起TCP三次握手,建立了TCP连接

  ·连接建立后浏览器就可以发送HTTP请求了

    请求的url为http://user:pass@服务器地址:端口号/node/index.html?type=1#top,其中user:pass表示用户认证的登录信息

  ·服务器接收HTTP请求,解析请求的路径和参数,经过后台的一些处理之后生成完整的相应页面

  ·服务器将生成的页面作为HTTP响应体,根据不同的处理结果生成响应头,发回给客户端

  ·客户端(浏览器)接收到HTTP响应,从响应中得到的HTTP响应体里的是HTML代码,于是对HTML代码开始解析

  ·解析过程中遇到引用的服务器上的资源(额外的css、js、图片、音视频、附件等),再向服务器发送请求

  ·浏览器解析HTML包含的内容,用得到的css代码进行外观上的进一步渲染,js代码也可能会对外观进行一定的处理

创建一个http.js:

var http = require('http')
var server = http.createServer(serve);//每当有请求来的时候,调用serve函数对客户端进行响应
server.listen(8080,'localhost');//监听本机的8080端口
function serve(request,response){
  //获取请求流里的相关信息:request.method(请求方法),request.url(请求里的url),request.headers(请求头)
  //设置响应信息:response.statusCode=200(状态码),
  //response.setHeader('Content-Type','text/html;charset=utf-8')(设置响应头,设置响应的类型,编码为utf-8)
  //response.setHeader('name','nodejs')
  //response.write(new Date().toString())(设置响应体)
  //response.end()(结束http响应)
}

解析查询字符串

var http = require('http');
var fs = require('fs');
var mime = require('mime');
var url = require('url');//对URL进行处理,把字符串转成对象
/**
*
* @param request 请求
* @param response 响应
*/
function serve(request,response){
//true 表示query转成对象
var urlObj = url.parse(request.url,true);
console.log(request.url,urlObj.query.name,urlObj.query.age);
var pathname = urlObj.pathname; if(pathname == '/'){
//设置响应的类型,编码为utf-8
response.setHeader('Content-Type','text/html;charset=utf-8');
//读取文件的内容并且将读到的内容写入响应体
fs.readFile('index.html',function(err,data){
response.write(data);//写响应体
response.end();
})
}else if(pathname == '/clock'){
var counter =0;
var int = setInterval(function(){
response.write(new Date().toString());
counter++;
if(counter==5){
clearInterval(int);
response.end();
}
},1000);
}else{
static(pathname,response)
} function static(pathname,response){
//设置响应的类型,编码为utf-8
response.setHeader('Content-Type',mime.lookup(pathname)+';charset=utf-8');
//读取文件的内容并且将读到的内容写入响应体
fs.readFile(pathname.slice(1),function(err,data){
response.write(data);//写响应体
response.end();
})
} }
//每当有请求来的时候调用serve函数对客户端进行响应
var server = http.createServer(serve); server.listen(8080,'localhost');