node.js 面试有关

什么是node.js

        Node.js采用模块化结构,按照CommonJS规范定义和使用模块。模块与文件是一一对应关系,即加载一个模块,实际上就是加载对应的一个模块文件。

        JS是脚本语言,脚本语言都需要一个解析器才能运行。对于写在HTML页面里的JS,浏览器充当了解析器的角色。而对于需要独立运行的JS,NodeJS就是一个解析器。

每一种解析器都是一个运行环境,不但允许JS定义各种数据结构,进行各种计算,还允许JS使用运行环境提供的内置对象和方法做一些事情。例如运行在浏览器中的JS的用途是操作DOM,浏览器就提供了document之类的内置对象。而运行在NodeJS中的JS的用途是操作磁盘文件或搭建HTTP服务器,NodeJS就相应提供了fshttp等内置对象。

  Node.js 被设计用来开发大规模高并发的网络应用,这种网络应用的瓶颈之一是在 I/O 的处理效率上。由于硬件及网络的限制,I/O 的速度往往是固定的,如何在此前提下尽可能处理更多的客户请求,提高 CPU 使用效率,便成了开发人员面临的最大问题。得益于基于事件驱动的编程模型,Node.js 使用单一的 Event loop 线程处理客户请求,将 I/O 操作分派至各异步处理模块(这里一般人不理解,node.js包含很多模块,这些模块可以使用js直接调用系统的api),既解决了单线程模式下 I/O 阻塞的问题,又避免了多线程模式下资源分配及抢占的问题。

单线程模式

       客户端发起一个 I/O 请求(数据库查询),然后等待服务器端返回 I/O 结果,结果返回后再对其进行操作,但这种请求常常需要很长时间(对于服务器的 CPU 处理能力来说)。这一过程中,服务器无法接受新的请求,即阻塞式 I/O。这种处理方式虽然简单,却不实用,尤其是面对大量请求的时候,简直就不可用。这种情景类似在火车站售票窗口排队买票,如果您在春节期间去北京火车站排队买过票,绝不会认为这是一种好的处理方式。庆幸的是,现在很少有服务器采取这种处理方式。

多线程模式

        该方式下,服务器为每个请求分配一个线程,所有任务均在自己的线程内执行,就像火车站多开了几个卖票窗口,处理效率高了许多。但就如读者看到的那样,在春节期间各个售票窗口前还是人满为患,为什么火车站不再多开一些售票窗口呢?当然是因为成本。线程也一样,服务器每创建一个线程,每个线程大概会占用 2M 的系统内存,而且线程之间的切换也会降低服务器的处理效率,基于成本的考虑,这种处理方式也有一定的局限性。然而,这却不是最主要的,主要的是开发多线程程序非常困难,容易出错。程序员需考虑死锁,数据不一致等问题,多线程的程序极难调试和测试。基本上在程序运行出错的时候,程序员才知道自己的程序有错误。而这种错误的代价往往又是巨大的,那些访问量巨大的电子商务网站时常会曝出价格错误等导致公司损失的新闻

事件驱动

       客户发起 I/O 请求的同时传入一个函数,该函数会在 I/O 结果返回后被自动调用,而且该请求不会阻塞后续操作。就像电话订票,设想你一大早来到办公室,给火车站打个电话,将自己的票务信息,地址告诉对方,然后放下电话,泡杯茶,浏览一下网页,回复一下今天的电子邮件,你完全不用管火车票的事了,如果订到票,火车站会派快递公司按你电话中提到的联系方式送票给你。无疑,这是一种极其理想的处理方式。所有请求以及同时传入的回调函数均发送至同一线程,该线程通常叫做 Event loop 线程,该线程负责在 I/O 执行完毕后,将结果返回给回调函数。这里要注意的是 I/O 操作本身并不在该线程内执行,所以不会阻塞后续请求。比如:请求a要访问数据库,请求b要访问文件系统,假设Event loop先接受到a请求,这时Event loop会把a的回调方法交给处理访问数据库的异步处理模块。然后Event loop就可以去接受请求b,并把b的回调方法交给处理文件系统的一部处理模块。然后Event loop继续等待请求。当访问数据的异步处理模块处理完成后,会主动调用a的回调方法。在a的回调方法中,就会给客户a发送查询到的数据(当然这里需要短暂的使用Event loop来操作)。

核心内置类库

  • 流 (stream)

         为什么要使用流呢?在node中,读取文件的方式有两种,一种是利用fs模块将文件一次性读取到本地内存,一种是采用流来读取。如果读取小文件可以使用fs模块读取 ,当读取一个大的文件的时候,一次性读取会占用大量的内存,效率很低,这个时候就要采用流来读取。流是将数据分割段,一段一段的读取,效率很高。

         stream 是一个抽象的接口,Node中有很多对象实现这个接口,例如htpp服务器发起请求的request对象就是一个Stream。

也可以手动创建一个Stream 。依然要用到fs模块,提供2个方法,其中createReadSream('文件名') 创建可读流 用来读取文件,createWriteStream('文件名')创建写入流,用来将数据写入到文件里 。官网代码如下:

         首先创建 input.txt 文件 和main.js 。下面是js代码

// 创建文件可读流
var fs = require("fs");
var data = '';

// 创建可读流
var readerStream = fs.createReadStream('input.txt');

// 设置编码为 utf8。
readerStream.setEncoding('UTF8');

// 处理流事件 --> data, end, and error
readerStream.on('data', function(chunk) {
   data += chunk;
});

readerStream.on('end',function(){
   console.log(data);
});

readerStream.on('error', function(err){
   console.log(err.stack);
});

console.log("程序执行完毕");

// 创建文件写入流
var fs = require("fs");
var data = '菜鸟教程官网地址:www.runoob.com';

// 创建一个可以写入的流,写入到文件 output.txt 中
var writerStream = fs.createWriteStream('output.txt');

// 使用 utf8 编码写入数据
writerStream.write(data,'UTF8');

// 标记文件末尾
writerStream.end();

// 处理流事件 --> data, end, and error
writerStream.on('finish', function() {
    console.log("写入完成。");
});

writerStream.on('error', function(err){
   console.log(err.stack);
});

console.log("程序执行完毕");

 

         所有的Stream对象都是EventEmitter 的实例。常用的事件有: 

         1.data - 当有数据可读时触发。

         2.end - 没有更多的数据可读时触发。

         3.error - 在接收和写入过程中发生错误时触发。
         4.finish - 所有数据已被写入到底层系统时触发。

  • 文件

         NodeJS通过内置fs模块提供对文件的操作,fs模块提供的API基本上可以分为以下三类:

          文件属性的读写:常用的有:fs.stat

          文件内容的读写:常用的有:fs.readFile、fs.readdir、fs.writeFile、fs.mkdir等等

          底层文件的操作:常用的有:fs.open、fs.read、fs.write、fs.close等等
          简单的删除某项目下的node_modules文件夹的例子如下:

var fs = require('fs')
var path = require('path')
function deletAll (path) {
    var files = []
    if (fs.existsSync(path)){
        files = fs.readdirSync(path)
        console.log(files)
        files.forEach(function(file,index){
            var curPath = path + '/'+ file
            if(fs.statSync(curPath).isDirectory()) {
                deletAll(curPath)
            } else {
                fs.unlinkSync(curPath)
            }
        })
        fs.rmdirSync(path);
    }
}
deletAll(path.resolve('E:/pgdemo/react/my-app/node_modules'))

 

  • 事件

        Node.js 所有的异步 I/O 操作在完成时都会发送一个事件到事件队列。

        Node.js 里面的许多对象都会分发事件:一个 net.Server 对象会在每次有新连接时触发一个事件, 一个 fs.readStream 对象会在文件被打开的时候触发一个事件。 所有这些产生事件的对象都是 events.EventEmitter 的实例。下面一个简单的官网例子说明EventEmitter 的用法

//event.js 文件
var EventEmitter = require('events').EventEmitter; 
var event = new EventEmitter(); 
event.on('some_event', function() { 
    console.log('some_event 事件触发'); 
}); 
setTimeout(function() { 
    event.emit('some_event'); 
}, 1000); 
// 执行结果如下:

//运行这段代码,1 秒后控制台输出了 'some_event 事件触发'。其原理是 event 对象注册了事件 //some_event 的一个监听器,然后我们通过 setTimeout 在 1000 毫秒以后向 event 对象发送事件 //some_event,此时会调用some_event 的监听器。
  • 网络

http模块主要用于创建http server服务并且支持

  1. 支持更多特性
  2. 不缓冲请求和响应
  3. 处理流相关

接下来看看最简单的应用

// 文件名:demo.js

// 引入http模块
var http = require('http');

// 创建http server
http.createServer(function (req, res) {
    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.end('Hello World\n');
}).listen(1337, '127.0.0.1');

console.log('Server running at http://127.0.0.1:1337/');

$ node demo.js运行后,在浏览器可看到Hello World

若想通过http 请求其他服务 则需要使用http.get() 或者http.request() 这两个方法,由于大多数请求都是没有主体的 GET 请求,因此 Node.js 提供了这个便捷的方法。 这个方法与 http.request() 的唯一区别是它将方法设置为 GET 并自动调用 req.end()。 注意 回调必须注意消费响应数据。

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值