Node.js基础学习指南(含node事件循环)

416 阅读7分钟

引言

说起node,它应该是作为前端的我们与后端语言距离忽而最近却又最远的知识了。在大前端盛行的时代各种技术纷至沓来,很多同学早已应接不暇,我们应该如何迈向下一步呢,因此接下来我们来探讨一下为什么要学习node呢?

  • 提高技术竞争力,提升在团队存在感
  • 很多项目是需要做中间层的,而node就是最好且唯一的选择
  • 我们不可能一辈子只做前端或者只做后端,随着时间的拉长后起之秀会有很多,然对于前端同学来说,node无非是最好的对后端切入窗口。 本篇文章小编仅简单介绍node基础

思维图

我画了一个图,帮助大家理解 本篇仅介绍图中模块

正文

下面小编就要放开心扉啦哈哈,各位小明同学们也要畅所欲言喽,首先我们来看下全局变量的概念。

全局变量

小编:全局变量也就是可以我们可以在全局上访问到的属性,而可以在全局上可以访问到的属性不一定是全局变量!

小明:那个不一定指的是?

小编:同学你知道commonJs规范么,__dirname,__filename,export,module,require就不是全局变量,但是他们是可以在全局中访问到的。如果你想知道原理,可以在评论区留言并关注,因为接下来我会写一篇commonJS原理源码剖析

那global全局变量有什么呢?

console.log('global',global)  ;//并run code 

global Object [global] {
  global: [Circular],
  clearInterval: [Function: clearInterval],
  clearTimeout: [Function: clearTimeout],
  setInterval: [Function: setInterval],
  setTimeout: [Function: setTimeout] {
    [Symbol(nodejs.util.promisify.custom)]: [Function]
  },
  queueMicrotask: [Function: queueMicrotask],
  clearImmediate: [Function: clearImmediate],
  setImmediate: [Function: setImmediate] {
    [Symbol(nodejs.util.promisify.custom)]: [Function]
  }
}
//如果你想查看所有的全局变量 
console.log(global,{showHidden:true})
//这里省略300行。。。实在是太长了,了解几个常用的就可以了 please following me。

process进程

所有的前后端代码都会跑在进程上。是不是觉得process这么熟悉,和webpack中的process是一个吗?是的,webpack就是node写的嘛,当然是同一个process。 列几个常用的process属性

[
  'version',          		          //版本
  'platform',        		         //代码运行在什么平台下
  'memoryUsage',    		        //内存占用情况
  'nextTick',      		       //是一个函数
  'argv',        		      //参数:用户执行时命令行传递的参数
  'chdir',       		     //变更nodejs当前工作目录
  'cwd',        		    //current working directory 当前工作目录(可以改变)
  'env',                           //默认读取全局环境变量
]

  • process.cwd():输出命令运行的工作目录,即cd..切换到learn-node上级目录Desktop之后,node learn-node/1.js,输出......./Desktop。因此不常用,一般用绝对路径 __dirname
  • process.chdir():变更nodeJs当前工作目录,既然是变更工作目录,那肯定是结合process.cwd()来使用的,
try{
        console.log(process.cwd());            //    /Users/yourName/Desktop/learn-node
        process.chdir("../../../yourName")     //
        console.log(process.cwd());            //    /Users/yourName
}catch(err) {
	console.log(err)
}
要是不想这么麻烦,就直接绝对路径吧!
  • process.env:默认读取全局变量 (可以在终端设置临时变量,只针对当前环境,关闭终端窗口就释放了) 设置环境变量并运行:cross-env a1=development node 1.js
  • process.argv:命令行运行时传递的参数。
//比如: node 1.js   --port 3000 --config webpack.config.js
//console.log(process.argv)
[
  '/usr/local/bin/node',                           //node的可执行文件
  '/Users/yourName/Desktop/learn-node/1.js',       //node命令执行的哪个文件
  '--port',                                        //...other  就是用户传递的参数,进行参数解析
  '3000',
  '--config',
  'webpack.config.js'
]

process.argv是一个数组,我们需要对数组进行解析,找出我们需要的内容

let program = {};
process.argv.slice(2).forEach( (item,index,array) => {
	if(item.startsWith('--')) {
		program[item.slice(2)] = array[index + 1]
	}
})
console.log(program); //这就是我们解析之后的对象
//node 1.js --port 3000 --config webpack.config.js
//program { port: '3000', config: 'webpack.config.js' }

因此有了第三方包commander帮我们做解析参数的工作: 我的另一篇文章》 www.yuque.com/linhao-00ft…

  • process.memoryUsage():内存占用情况
 {
  rss: 19693568,
  heapTotal: 4907008,
  heapUsed: 2870368,
  external: 933161,
  arrayBuffers: 17695
}

node事件轮询

node中的事件轮询是处理非阻塞I/O操作的机制,node主要写了个libuv库,这个库是用多线程来模拟异步。那他是如何来调度的呢?他和js一样:有线程、可管理同步代码、有事件循环机制。

  • 浏览器和node事件循环机制的区别: js虽然是单线程的来处理的,但是有时会把操作转移到内核中;浏览器和node事件循环在node 10版本执行结果一样,但是本质不同;浏览器只有两个任务队列,即微任务队列和宏任务队列,但是node有多个宏任务队列。

参考链接: nodejs.org/zh-cn/docs/…

在每次运行的事件循环之间,node.js会检查自己是否在等待任何异步I/O或计时器,如果没有则完全关闭。图中的timer、poll 、pending callback、check......每一行都是一个队列。所以他的任务是被分配到不同的队列中的,当主栈代码执行完后会进入图中的执行队列中,依次扫描。

timer                  // 定时器:执行已经被setTimeout()、setInterval()调度的回调函数
pending callbacks     // 等待:执行延迟到下一个循环迭代的i/o回调
idle prepare	     //仅限系统内部使用
poll                //几乎所有的异步API的回调:检查新的I/O事件、执行与I/O相关的回调
		   //除了关闭的回调函数,已经被定时器调度的setTimeout/setInterval,或者在适当的时候(回调函数太多了)进行阻塞的函数
check 		  //  setImmediate()函数在这里执行
close  callbacks // 一些关闭的回调函数,如:socket.on('close', ...)

帮助大家理解,给大家画了流程图

看了图有同学可能疑问,为什么要在poll处进行阻塞呢?很简单,如果不阻塞的话就成了死循环啦!

每执行宏任务队列中的一个callback,就会清空一次微任务队列(和浏览器的事件循环机制相同)。上面6个队列[fn,fn,fn]中,timer(定时器)、poll(几乎所有的回调)、check(setImmediate)是宏任务队列,

提到微任务和宏任务就不得不说process.nextTick(),他是在栈底被调用的,即close callbacks后,可以看做是微任务,但优先级process.nextTick() > 微任务

setTimeout(() => {
	console.log('setTimeout')
},0)
promise.resolve().then(res => {
	console.log('then')
})
process.nextTick(() => {
	console.log('nextTick')
})
console.log('ok')
//run code 结果依次为: ok nextTick  then  setTimeout

那setTimeout和setImmediate谁更快呢? 情况1:setTimeout(() =>{},0) 这个零,要看性能的影响,要看循环的时候,setTimeout是否已经放到了任务队列,是优先于setImmediate还是在setImmediate之后 // 要看性能,比如我们的代码执行的非常快,定时器还没有来得及放到timer, 那就直接走到了check;也有可能主执行栈执行完了,定时器已经放到了timer中了,那再去走到check中

//正常思维是setTimeout快于setImmediate。但是受性能影响
setImmediate(() => {
    console.log('setImmediate')
})
console.time('start')
setTimeout(() => {
    console.log('setTimeout')
    console.timeEnd('start')
},0)

 

情况2:在pool中: fs.readFile是异步i/o ,也就是poll,>然后check(也就是setImmediate),>然后timer(也就是setTimeout),所以 setImmediate timeout

const fs = require('fs')
fs.readFile(__filename, () => {
    setTimeout(() => {
        console.log('timeout')
    },0)
    setImmediate(() => {
        console.log('setImmediate')
    })
})
// setImmediate   timeout

模块化

因为模块化内容比较多,所以准备写一篇《模块化及commonJs规范实现原理》,可以点个关注,下周会把链接贴出来!

推荐文章: juejin.cn/post/691450…

内置模块(核心模块)

核心模块是node中内置的模块(区分于自定义模块和第三方模块)

events

任务:

  1. 要知道什么是发布订阅 2.学会怎么使用events 3. 模仿源码自己实现events功能 回顾发布订阅要戳这里: juejin.cn/post/691760…

别着急,刚写了1/3,未完待续。。。。。。