看官网学习Node.js

169 阅读13分钟

Node.js事件循环

​ 1.Node.js JavaScript代码运行在单个线程上,每次只处理一件事。

​ 2.阻塞事件循环 任何花费太长时间才能将控制权返回给事件循环的JavaScript代码,都会阻塞页面中任何JavaScript代码的执行,甚至阻塞UI线程,并且用户无法单机浏览,滚动页面等。而JavaScript中机会所有的I/O基元都是非阻塞的。网络请求、文件系统操作等。被阻塞是一个异常,所以JavaScript中有如此多基于回调和基于promise

​ 3.调用堆栈 调用堆栈是一个LIFO队列(后进先出)。事件循环不断地调用堆栈,来查看是否需要运行任何函数。当执行时,它会将找到的所有函数调用添加到调用堆栈中,并按顺序执行每个函数

​ 4.一个事件循环的例子

const bar=()=>{
    console.log("bar")
}
const baz=()=>{
    console.log("baz")
}
const foo=()=>{
    console.log("foo")
    bar()
    baz()
}
foo()
//foo
//bar
//baz

每次迭代中的事件循环都会查看调用堆栈中是否有东西并执行它直到调用堆栈为空

​ 5.入队函数的执行 让我们看看如何将函数推迟直到堆栈被清空

const bar=()=>{
    console.log("bar")
}
const baz=()=>{
    console.log("baz")
}
const foo=()=>{
    console.log("foo")
    setTimeout(bar,0)
    baz()
}
//foo
//baz
//bar

​ 运行该代码时,会首先调用foo()。在foo()内部,会首先调用setTimeout,将bar当作参数传入,并传入0作为定时器指示它尽快运行。然后调用baz()

​ 6.消息队列 当调用 setTimeout() 时,浏览器或 Node.js 会启动定时器。 当定时器到期时(在此示例中会立即到期,因为将超时值设为 0),则回调函数会被放入“消息队列”中。在消息队列中,用户触发的事件(如单击或键盘事件、或获取响应)也会在此排队,然后代码才有机会对其作出反应。 类似 onLoad 这样的 DOM 事件也如此。事件循环会赋予调用堆栈优先级,它首先处理在调用堆栈中找到的所有东西,一旦其中没有任何东西,便开始处理消息队列中的东西。我们不必等待诸如 setTimeout、fetch、或其他的函数来完成它们自身的工作,因为它们是由浏览器提供的,并且位于它们自身的线程中。 例如,如果将 setTimeout 的超时设置为 2 秒,但不必等待 2 秒,等待发生在其他地方。

​ 7.ES6作业队列 ECMAScript 2015 引入了作业队列的概念。这种方式会尽快地执行异步函数的结果,而不是放在调用堆栈的末尾。在当前结束之前resolve的promise会在当前函数之后被立即执行

const bar=()=>{
    console.log("bar")
}
const baz=()=>{
    console.log("baz")
}
const foo=()=>{
    console.log("foo")
    setTimeout(bar,0)
    new Promise((resolve,reject)=>{
        resolve("在baz之后,bar之前")
    }.then(resolve=>console.log(resolve)))
}
foo()
//foo
//baz
//在baz之后,bar之前
//bar

了解process.nextTick()

​ 每当事件循环进行一次完整的行程时,都将其称为一个滴答。当将一个函数传给process.nextTick() 时,则指示引擎在当前操作结束(在下一个事件循环滴答开始之前)时调用此函数:

process.nextTick(()=>{
    //做些事情
})

​ 事件循环正在忙于处理当前的函数代码。当该操作结束时,Js引擎会运行在该操作期间传给nextTick调用的所有函数。这是告诉Js引擎异步地(在当前函数之后)处理函数地方式,但是尽快执行而不是将其排入队列。调用setTime(()=>{},0)会在下一个滴答结束时执行该函数,比使用nextTick() (其会优先执行该调用并在下一个滴答开始之前执行该函数)晚的多。当要确保在下一个事件循环迭代中代码已被执行,则使用nextTick()

了解setImmediate()

​ 当要异步地(但要尽可能快)执行某些代码时,其中一个选择是使用Node.js提供给地setImmediate()函数

setImmediate(()=>{
	//运行一些东西
})

​ 作为setImmediate()参数传入地任何函数都是在事件循环下一个迭代中执行地回调。

setImmediate()setTimeout(()=>{},0) (传入0毫秒的超时)、process.nextTick()有何不同:传给process.nextTick()的函数会在事件循环的当前迭代中(当前操作结束之后)被执行。这意味着他始终在setTimeoutsetImmediate之前执行。延迟0毫秒的setTimeout()回调与setImmediate()非常相似,执行顺序取决于各种因素,但是它们都会在事件循环的下一个迭代中运行。

探索JavaScript定时器

​ 1.setTimeout() 指定一个回调函数以供稍后执行,并指定希望它稍后运行的时间(以毫秒为单位)的值:

setTimeout(()=>{
	//2秒之后运行
},2000)
//也可以传入现有的函数名称和一组参数
const myFunction=(firstParam,secondParam)=>{
    //...
}
setTimeout(myFunction,2000,firstParam,secondParam)
//setTimeout会返回定时器的id。可以用来清除定时器
const id=setTimeout(()=>{
    
},2000)
clearTimeout(id)
//零延迟,如果将超时延迟指定为0,则回调函数会被尽快执行
setTimeout(()=>{
},0)

​ 2.setInterval() 它会在指定的特定时间间隔(以毫秒为单位)一直地运行回调函数,而不是只运行一次。通常在setInterval回调函数中调用clearInterval,以使其自行判断是否再次运行或停止。

​ 3.递归的setTimeout setInterval每n毫秒启动一个函数,而无需考虑函数何时完成执行。如果一个函数总是花费相同的时间,那就没问题了,但函数可能需要不同的执行时间,也许一个较长时间的执行会与下一次执行重叠。为了避免这种情况,可以在回调函数完成时安排要被调用的递归的setTimeout

const myFunction=()=>{
    //...
	setTimeout(myFunction,1000)
}
setTimeout(myFunction,1000)

JavaScript中的异步与回调

​ 1.回调 你不知道用户何时点击按钮。

document.getElementById('button').addEventListener('click',()=>{
    
})

回调是一个简答的函数,会作为值被传给另一个函数,并且仅在事件发生时才被执行。之所以这样做,是因为JavaScript具有顶级的函数,这些函数可以被分配给变量并传给其他函数(称为高阶函数)。

了解JavaScript Promise

​ 1.Promise 通常被定义为最终会变为可用值的代理

​ 2.promise被调用后,它会以处理中的状态开始。这意味调用的函数会被继续执行,而promise仍处于直到解决为止,从而为调用的函数提供所请求的任何数据。被创建的promise最终会以被解决状态被拒绝状态,并在完成时调用相应的回调函数

​ 3.创建Promise

let done=true
const isItDoneYet=new Promise((resolve,reject)=>{
    if(done){
        const workDone="这是创建的东西"
        resolve(workDone)
	}else{
        cosnt why="仍然在处理其他事情"
        reject(why)
    }
})

promise 检查了 done 全局常量,如果为真,则 promise 进入被解决状态(因为调用了 resolve 回调);否则,则执行 reject 回调(将 promise 置于被拒绝状态)。 如果在执行路径中从未调用过这些函数之一,则 promise 会保持处理中状态。使用resolvereject,可以向调用者传达最终的promise状态以及该如何处理。实例中,只返回了一个字符串,但是它可以是一个对象,也可以为null。由于上述代码中创建了promise,因此他已经开始执行。

​ 4.promisifying 这项技术能够使用经典的JavaScript函数来接受回调并使其返回promise

const fs=require('fs')
const getFile=(fileName)=>{
    return new Promise((resolve,reject)=>{
        fs.readFile(fileName,(err,data)=>{
            if(err){
                reject(err)
                return
			}
            resolve(data)
        })
    })
}
getFile('/stc/passwd')
.then(data=>console.log(data))
.catch(err=>console.err(err))

​ 5.使用promise

const isItDoneYet=new Promise(/* ...... */)
const checkIfItsDone=()=>{
    isItDoneYet
      	.then(ok=>{
        	console.log(ok)
    	})
   	 	.catch(err=>{
        	console.error(err)
    })
}

​ 6.链式promise Promise可以返回到另一个promise,从而创建一个promise链

const status=response=>{
    if(response.status>=200&&response.status<300){
        return Promise.resolve(response)
    }
    return Promise.reject(new Error(reponse.statusText))
}
const json=response=>response.json()
fetch('/todos.json')
	.then(status)	//注意,status函数实际上在这里被调用,并且同样返回promise
	.then(json)		//这里唯一的区别是json函数会返回解决时传入data的promise
	.then(data=>{	//这是data会在此作为匿名函数的第一个参数的原因
    	console.log('请求成功获得JSON响应',data)
	})
	.catch(error=>{
    	console.log('请求失败',error)
	})

​ 7.处理错误 当promise链中的任何内容失败并引发错误或拒绝promise时,则控制权会转到链中最近的catch() 语句

​ 8.编排promise 如果需要同步不同的promise,则promise.all()可以帮助定义promise列表,并在所有promise都被解决后执行一些操作

const f1=fetch('./something1.json')
const f2=fetch('./something2.json')
Promise.all([f1,f2])
	.then(
    	res=>console.log(res)
	)
	.catch(err=>{
    	console.error(err)
	})
Promise.all([f1,f2]).then((res1,res2)=>{
    console.log(res1,res2)
})

当传给Promise.race() 的首个promise被解决时,则其开始运行,并且只运行一次附加的回调(传入第一个被解决的promise的结果)

const firstPromise=new Promise((resolve,reject)=>{
    setTimeout(resolve,1000,'first')
})
const secondPromise=new Promise((resolve,reject)=>{
    setTimeout(resolve,500,'second')
})
Promise.race([firstPromise,secondPromise]).then(res=>{
    console.log(res)
})

具有 Async 和 Await 的现代异步 JavaScript

​ 1.一个简单的async和await实例

const doSomethingAsync=()=>{
    return new Promise((resolve)=>{
        setTimeout(()=>resolve('做些事情'),3000)
    })
}
const doSomething=async()=>{
    console.log(await doSomethingAsync())
}
doSomething()
//不使用async await写法对比
doSomethingAsync().then((res) => {
  console.log(res);
});

​ 2.在任何函数之前加上async关键字意味着该函数会返回promise

const aFunction=async()=>{
    return '测试'
}
aFunction().then(alert)

​ 2.async和await使代码更易阅读

//不使用async和await
const getFirstUserData=()=>{
    return fetch('/users.json') //获取用户列表
    	.then(response=>response.json()) //解析JSON
    	.then(users=>users[0]) //选择第一个用户
    	.then(user=>fetch(`/user/${user.name}`)) 
    	.then(userResponse=>userResponse.json())
}
getFirstUserData()
//使用async和await
const getFirstUserData=async()=>{
    const response=await fetch('/user.json')
    const users=await response.json()
    const user=user[0]
    const userResponse=await fetch(`/user/${user.name}`)
    const userData=userResponse.json()
    return userData
}

Node.js事件触发器

​ Node.js提供了event模块,提供EventEmitter类,用于处理事件

const EventEmitter =require('events')
const eventEmitter =new EventEmitter()

​ 该对象公开了onemit方法。emit用于触发事件,on用于添加回调函数

eventEmitter.on('start',()=>{
    console.log('开始规')
})
eventEmitter.emit('start')

搭建HTTP服务器

const http = require('http')
const port = 3000
const server = http.createServer((req, res) => {
  res.statusCode = 200
  res.setHeader('Content-Type', 'text/plain')
  res.end('你好世界\n')
})
server.listen(port, () => {
  console.log(`服务器运行在 http://${hostname}:${port}/`)
})

使用Node.js发送HTTP请求

​ 1.执行GET请求

const https=require('http')
const options={
    hostname:'nodejs.cn',
    port:443,
    path:'/todos',
    method:'GET'
}
const req=http.request(option.res=>{
    console.log(`状态码:${res.statusCode}`)
    res.on('data',d=>{
        process.stdout.write(d)
    })
})
req.on('error',error=>{
    console.error(error)
})
req.end()

​ 2.执行POST请求

const https=require('http')
const data=JSON.stringify({
    todo:'做点事情'
})
const option={
    hostname:'nodejs.cn',
    port:443,
    path:'/todos',
    method:'POST',
    headers:{
        'Content-Type':'application/json',
        'Content-Length':data.length
    }
}
const req=https.request(option,res=>{
    console.log(`状态码${res.statusCode}`)
    res.on('data',d=>{
        process.stdout.write(d)
    })
})
req.on('error',error=>{
    console.error(error)
})
req.write(data)
req.end()

使用Node.js获取HTTP请求的正文数据

​ 当使用http.createServer()初始化HTTP服务器,服务器会在获得所有HTTP请求头(而不是请求正文时)时调用回调。在连接回调中传入的request对象是一个流。因此,必须监听要处理的主题内容,并且其是按数据块处理的。首先,通过监听流的data事件来获取数据,然后再数据结束时调用一次流的end事件

const server=http.createServer((req,res)=>{
    req.on('data',chunk=>{
        console.log(`可用的数据块${chunk}`)
    })s
    req.on('end',()=>{
        //数据结束
    })
})

​ 因此,若要访问数据(假设期望接收到字符串),则必须将其放入数组中

const server=http.createServer((req,res)=>{
    let data='';
    req.on('data',chunk=>{
        data+chunk;
    })
    req.on('end',()=>{
        JSON.parse(data).todo //'做点事情'
    })
})

在Node.js中使用文件描述符

​ 在与位于文件系统中的文件进行交互之前,需要先获取文件的描述符。文件描述符是使用fs模块提供的open()方法打开文件后返回的

const fs=require('fs')
fs.open('/User/joe/test.txt','r',(err,fd)=>{
    //fd是文件描述符
})

rfs.open()调用的第二个参数。意味着打开文件用于读取

其他常用的标志有:

  • r+ 打开文件用于读写。
  • w+ 打开文件用于读写,将流定位到文件的开头。如果文件不存在则创建文件。
  • a 打开文件用于写入,将流定位到文件的末尾。如果文件不存在则创建文件。
  • a+ 打开文件用于读写,将流定位到文件的末尾。如果文件不存在则创建文件。

也可以使用fs.openSync

const fs=require('fs')
try{
	const fd=fs.openSync('/Users/joe/test.txt',r)
}catch(err){
	console.error(err)
}

Node.js文件属性

fs模块提供的stat()方法,调用时传入文件的路径,一旦Node.js获得文件的详细信息,则会调用传入的回调函数,并带上两个参数:错误消息和文件属性

const fs=require('fs')
fs.stat('/users/joe/test.txt',(err,stats)=>{
    if(err){
        console.error(err)
        return
    }
    //使用 stats.isFile() 和 stats.isDirectory() 判断文件是否目录或文件
	//使用 stats.isSymbolicLink() 判断文件是否符号链接
	//使用 stats.size 获取文件的大小(以字节为单位)
  	stats.isFile() //true
  	stats.isDirectory() //false
  	stats.isSymbolicLink() //false
  	stats.size //1024000 //= 1MB
})

或者使用同步方法statSync

const fs=require('fs')
trr{
    const stats=fs.statSync('/users/joe/test.txt')
}catch(err){
    console.error(err)
}

Node.js事件模块

const EventEmitter = require('events')
const door = new EventEmitter()

​ 1.emitter.addListener() emitter.on()的别名

​ 2.emitter.emit() 触发事件。按照事件被注册的顺序同步地调用每个事件监听器

​ 3.emitter.eventNames() 返回字符串(表示在当前EventEmitter对象上注册地事件)数组

door.eventNames()

​ 4.emitter.getMaxListeners() 获取可以添加EventEmitter对象地监听器地最大数量(默认为10

,但可以使用setMaxListeners()进行增加或减少

​ 5.emitter.off() emitter.removeListener()别名

​ 6.emitter.on() 添加当事件被触发时调用地回调函数

​ 7.emitter.once() 添加当事件在注册之后首次被触发时调用地回调函数。该回调只会调用一次,不会再被调用

​ 8.**emitter.removeAllListeners()**移除 EventEmitter 对象的所有监听特定事件的监听器:

door.removeAllListeners('open')

​ 9.**emitter.removeListener()**移除特定的监听器。 可以通过将回调函数保存到变量中(当添加时),以便以后可以引用它:

const doSomething = () => {}
door.on('open', doSomething)
door.removeListener('open', doSomething)

Node.js http模块

​ 1.http.METHODS 此属性列出来所有支持地HTTP方法

​ 2.http.STATUS_CODES 此属性列出了所有HTTP状态代码及其描述

​ 3.http.createServer() 返回http.Server类地实列

​ 4.http.request() 发送HTTP请求到服务器,并创建http.ClinentRequest类地实例

​ 5.http.get() 类似于http.request(),但会自动地设置HTTP方法为GET,并自动地调用req.end()

​ 6.http.Agent Node.js会创建http.Agent类地全局实例,以管理HTTP客户端连接地持久性和复用性,该对象会确保对服务器地每个请求进行排队并且单个socket被复用。它会维护一个socket池。

​ 7.http.ClientRequesthttp.request()http.get() 被调用时,会创建 http.ClientRequest 对象。当响应被接收时,则会使用响应(http.IncomingMessage 实例作为参数)来调用 response 事件。返回的响应数据可以通过以下两种方式读取:

​ <1>可以调用 response.read() 方法。

​ <2>在 response 事件处理函数中,可以为 data 事件设置事件监听器,以便可以监听流入的数据。

​ 8.**http.Server ** 当使用http.createServer() 创建新的服务器时,通常会实例化并返回它。拥有服务器对象后,就可以访问其方法:close() 停止服务器不再接受新的连接。listen() 启动HTTP服务器并监听连接

​ 9.http.ServerResponse 由http.Server创建,并作为第二个参数传给它触发地request事件。

//通常在代码中用作res
const server=http.CreateServer((req,res)=>{
    //res是一个http.ServerResponse对象
})

Node.js Buffer

​ 1.buffer是内存区域,它表示在V8 JavaScript引擎外部分配地固定大小地内存块(无法调整大小)。可以将buffer视为整数数组,每个整数代表一个数据字节。

​ 2.使用 Buffer.from()Buffer.alloc()Buffer.allocUnsafe()方法可以创建 buffer。