Node.js 零散知识学习

引言

最近在学习公司的数据采集服务,由于之前从未接触过node.js,所以在看代码的过程中遇到许多方法、字段不是很了解,为了更好的学习记录,在查资料的过程中,同时写博客(后续技术文章也好交差)。

Node.js 零散知识

var和let的区别

1.基础语法

1.使用var声明的变量,其作用域为该语句所在的函数内,且存在变量提升现象;
2.使用let声明的变量,其作用域为该语句所在的代码块内,不存在变量提升;
3.let不允许在相同作用域内,重复声明同一个变量。

{
	// var
    var a = 123;
    // let
    let b = 321;
    console.log(a); // 123
    console.log(b); // 321
}
console.log(a+1); //124
console.log(b+1); //ReferenceError: b is not defined

可以看到当跳出{ }后,let定义的b变成了未定义,这是因为跳出了let定义的变量的作用域。换句话说,let声明的变量只在他所在的代码块有效。

下面是一个常见的面试题:

for (var i = 0; i <10; i++) {  
    setTimeout(function() {  // 同步注册回调函数到 异步的 宏任务队列。
         console.log(i);     // 执行此代码时,同步代码for循环已经执行完成
      }, 0);
}
 // 输出结果
1010

如果把var改成let声明:

// i虽然在全局作用域声明,但是在for循环体局部作用域中使用的时候,变量会被固定,不受外界干扰。
for (let i = 0; i < 10; i++) { 
  setTimeout(function() {
    console.log(i);    //  i 是循环体内局部作用域,不受外界影响。
  }, 0);
}
// 输出结果:
0  1  2  3  4  5  6  7  8 9

另外,for循环还有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域

for (let i = 0; i < 3; i++) {
  let i = 'love';
  console.log(i);
}
love love love

上面代码正确运行,输出了 3 次abc。这表明函数内部的变量i循环变量i不在同一个作用域,有各自单独的作用域.

2.不存在变量提升

var命令会发生”变量提升“现象,即变量可以在声明之前使用,值为undefined。

let命令则不同,它所声明的变量一定要在声明后使用,否则报错。

console.log(a); //undefined 
var a = 10;
console.log(b); //ReferenceError: Cannot access 'b' before initialization
let b = 20;

上面代码变量a用var声明,会发生变量提升,因没有值,所以会输出undefined。变量b用let声明的则不会发生变量提升,会报错。

var a=521;
if(true){
    a='abc';//ReferenceError: tmp is not defined
    let a;
}

3.let不能再同一作用域中对变量重复定义

{
    let a = 123;
    let a = 21;
 
    console.log(a);
    //SyntaxError: Identifier 'a' has already been declared
}

如果重复定义会直接报错。

在开发过程中,尽量少用甚至不用var声明变量,用let会避免很多后期代码执行过程中不必要的麻烦。

node.js 中的回调函数

Node.js 异步编程的直接体现就是回调。

异步编程依托于回调来实现,但不能说使用了回调后程序就异步化了。

回调函数在完成任务后就会被调用,Node 使用了大量的回调函数,Node 所有 API 都支持回调函数。

例如,我们可以一边读取文件,一边执行其他命令,在文件读取完成后,我们将文件内容作为回调函数的参数返回。这样在执行代码时就没有阻塞或等待文件 I/O 操作。这就大大提高了 Node.js 的性能,可以处理大量的并发请求。

回调函数一般作为函数的最后一个参数出现:

function foo1(name, age, callback) { }
function foo2(value, callback1, callback2) { }

阻塞和非阻塞代码实例

console.log('阻塞');

let fs = require('fs');
let txtData = fs.readFileSync('tt1.txt');

console.log(txtData.toString());
console.log('Over');

console.log('非阻塞');

fs.readFile('tt1.txt', function (err, data) {
    if (err) return console.log(err);
    console.log(data.toString());
});

console.log('over2');

输出:
阻塞
你在搞锤子?
Over
非阻塞
over2
你在搞锤子?

可以发现,在阻塞代码中(fs.readFileSync表示同步读取文件),程序在执行完文件读取后,输出‘Over’。而在非阻塞代码中(fs.readFile表示异步读取文件),程序在执行文件读取的同时,执行console.log(‘over2’)。即第一个实例在文件读取完后才执行程序第二个实例我们不需要等待文件读取完,这样就可以在读取文件时同时执行接下来的代码,大大提高了程序的性能。
换句话说,阻塞是按顺序执行的,而非阻塞是不需要按顺序的,所以如果需要处理回调函数的参数,我们就需要写在回调函数内

究竟什么是回调函数(Callback),其实回调函数并不复杂,明白两个重点即可:

  1. 函数可以作为一个参数在另一个函数中被调用。

  2. JS是异步编程语言,这就是说JS代码的执行顺序并不是从上至下按部就班完成的。见上述阻塞非阻塞实例。

let fs = require("fs");
let c;

function f(x) {
    console.log(x);
}

function writeFile(callback) {
    fs.writeFile('input.txt', '我是通过fs.writeFile 写入文件的内容', function (err) {
        if (!err) {
            console.log("文件写入完毕!");
            c = 1;
            callback(c);
        }
    });
}

c = 0;
writeFile(f);
//输出: 
文件写入完毕!
1

代码出现了两次callback关键字,第一个callback出现在writeFile的形参里,起定义的作用,表示这个参数并不是一个普通变量,而是一个函数,也就是前面所说的重点1,即所谓的“以函数为参数”。 第二个callback出现在c = 1下面,表示此处==“执行”从形参传递进来的那个函数==。

我们现在开始用一句话攻略做一个总结:

在大多数编程语言中,函数的形参总是从外向内传递参数,但在JS中,如果形参碰到“关键字” callback 则完全相反,它表示从内向外反向调用某个外部函数。

node.js 中的进程和线程

1.线程和进程的区别

标准答案:
进程是资源分配的最小单位,线程是CPU调度的最小单位
u1s1,这个属实抽象,并不能看懂。所以还是举例理解:
进程=火车,线程=车厢

  • 线程在进程下行进(单纯的车厢无法运行)
  • 一个进程可以包含多个线程(一辆火车可以有多个车厢)
  • 不同进程间数据很难共享(一辆火车上的乘客很难换到另外一辆火车,比如站点换乘)
  • 同一进程下不同线程间数据很易共享(A车厢换到B车厢很容易)
  • 进程要比线程消耗更多的计算机资源(采用多列火车相比多个车厢更耗资源)
  • 进程间不会相互影响,一个线程挂掉将导致整个进程挂掉(一列火车不会影响到另外一列火车,但是如果一列火车上中间的一节车厢着火了,将影响到所有车厢)
  • 进程可以拓展到多机,线程最多适合多核(不同火车可以开在多个轨道上,同一火车的车厢不能在行进的不同的轨道上)
  • 进程使用的内存地址可以上锁,即一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存。(比如火车上的洗手间)- “互斥锁”
  • 进程使用的内存地址可以限定使用量(比如火车上的餐厅,最多只允许多少人进入,如果满了需要在门口等,等有人出来了才能进去)-“信号量”

2.Node.js 的多进程

Node.js 是以单线程的模式运行的,但它使用的是事件驱动来处理并发,这样有助于我们在多核 cpu 的系统上创建多个子进程,从而提高性能。

每个子进程总是带有三个流对象:child.stdin, child.stdout 和child.stderr。他们可能会共享父进程的 stdio 流,或者也可以是独立的被导流的流对象。

Node 提供了 child_process 模块来创建子进程,方法有:

  • exec - child_process.exec 使用子进程执行命令,缓存子进程的输出,并将子进程的输出以回调函数参数的形式返回。

  • spawn - child_process.spawn 使用指定的命令行参数创建新进程。

  • fork - child_process.fork 是 spawn()的特殊形式,用于在子进程中运行的模块,如 fork(’./son.js’) 相当于 spawn(‘node’, [’./son.js’]) 。与spawn方法不同的是,fork会在父进程与子进程之间,建立一个通信管道,用于进程之间的通信。

由于项目中用的都是fork方法,所以本文主要说明fork方法。

1.exec()方法

child_process.exec 使用子进程执行命令,缓存子进程的输出,并将子进程的输出以回调函数参数的形式返回。

语法如下所示:

child_process.exec(command[, options], callback)

参数
参数说明如下:

  • command: 字符串, 将要运行的命令,参数使用空格隔开

  • options :对象,可以是:

  •  	cwd ,字符串,子进程的当前工作目录
    
  •  	env,对象 环境变量键值对
    
  •  	encoding ,字符串,字符编码(默认: 'utf8')
    
  •  	shell ,字符串,将要执行命令的 Shell(默认: 在 UNIX 中为/bin/sh, 在 Windows 中为cmd.exe, Shell 应当能识别 -c开关在 UNIX 中,或 /s /c 在 Windows 中。 在Windows 中,命令行解析应当能兼容cmd.exe)
    
  •  	timeout,数字,超时时间(默认: 0)
    
  •  	maxBuffer,数字, 在 stdout 或 stderr 中允许存在的最大缓冲(二进制),如果超出那么子进程将会被杀死 (默认: 200*1024)
    
  •  	killSignal ,字符串,结束信号(默认:'SIGTERM')
    
  •  	uid,数字,设置用户进程的 ID
    
  •  	gid,数字,设置进程组的 ID
    
  • callback :回调函数,包含三个参数error, stdout 和 stderr。

exec() 方法返回最大的缓冲区,并等待进程结束,一次性返回缓冲区的内容。
2.spawn() 方法
child_process.spawn 使用指定的命令行参数创建新进程,语法格式如下:

child_process.spawn(command[, args][, options])

参数
参数说明如下:

  • command: 将要运行的命令

  • args: Array 字符串参数数组

  • options Object

  •  	cwd String 子进程的当前工作目录
    
  •  	env Object 环境变量键值对
    
  •  	stdio Array|String 子进程的 stdio 配置
    
  •  	detached Boolean 这个子进程将会变成进程组的领导
    
  •  	uid Number 设置用户进程的 ID
    
  •  	gid Number 设置进程组的 ID
    

spawn() 方法返回流 (stdout & stderr),在进程返回大量数据时使用。进程一旦开始执行时 spawn() 就开始接收响应。
3.fork 方法
child_process.fork 是 spawn() 方法的特殊形式,用于创建进程,语法格式如下:

child_process.fork(modulePath[, args][, options])

参数
参数说明如下:

  • modulePath: String,将要在子进程中运行的模块

  • args: Array 字符串参数数组

  • options:Object

  •  	cwd String 子进程的当前工作目录
    
  •  	env Object 环境变量键值对
    
  •  	execPath String 创建子进程的可执行文件
    
  •  	execArgv Array 子进程的可执行文件的字符串参数数组(默认: process.execArgv)
    
  •  	silent Boolean 如果为true,子进程的stdin,stdout和stderr将会被关联至父进程,否则,它们将会从父进程中继承。(默认为:false)
    
  •  	uid Number 设置用户进程的 ID
    
  •  	gid Number 设置进程组的 ID
    

返回的对象除了拥有ChildProcess实例的所有方法,还有一个内建的通信信道。
实例

const childProcess = require('child_process');
const cpuNum = require('os').cpus().length;

for (let i = 0; i < cpuNum; ++i) {
  childProcess.fork('./worker.js');
}

Worker.js:

console.log('Worker-' + process.pid + ': Hello world.');

执行 node master.js,得到如下结果,master创建4个worker后输出HelloWorld信息,每个worker也分别输出自己的HelloWorld信息。
在这里插入图片描述

node.js null和undefine的区别

Undefined类型只有一个值,即undefined。当声明的变量还未被初始化时,变量的默认值为undefined。
Null类型也只有一个值,即null。null用来表示尚未存在的对象,常用来表示函数企图返回一个不存在的对象。

不同点

1.Number类型转换的值不同

console.log(Number(null)); // 0
console.log(Number(undefined)); // NaN

2.typeof 运算输出值不同

nullnull类型,代表“空”,代表一个空对象指针,使用typeof运算得到"object"。

undefinedundefined类型,当一个声明了一个变量未初始化时,得到的就是"undefined"

 console.log(typeof null); // 'object'
 console.log(typeof undefined); // 'undefined'

设置为null的变量或者对象会被内存收集器回收

使用场景

  • null表示"没有对象",此处应该无值。例如:
    1、作为函数的参数,表示该函数的参数不是对象
    2、作为对象原型链的终点

  • undefined表示"缺少值",此处应该有值却未定义。例如:
    1、声明的变量未赋值时等于undefined
    2、调用函数未传递应有的参数时,对应参数等于undefined
    3、对象的属性没有赋值时等于undefined
    4、函数没有返回值时,默认返回undefined

参考

 [1]: https://www.jianshu.com/p/84edd5cd93bd/
 [2]:https://www.zhihu.com/question/25532384/
 [3]: https://www.runoob.com/nodejs/nodejs-process.html/
 [4]:https://blog.csdn.net/qq_41381461/article/details/90643548/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值