Node知识点整理

1.CommonJS 和 ES6 Module 究竟有什么区别?

CommonJS
CommonJS 的模块主要由原生模块 module 来实现,这个类上的一些属性对我们理解模块机制有很大帮助

Module {
 id: '.', // 如果是 mainModule id 固定为 '.',如果不是则为模块绝对路径
 exports: {}, // 模块最终 exports
 filename: '/absolute/path/to/entry.js', // 当前模块的绝对路径
 loaded: false, // 模块是否已加载完毕
 children: [], // 被该模块引用的模块
 parent: '', // 第一个引用该模块的模块
 paths: [ // 模块的搜索路径
 '/absolute/path/to/node_modules',
 '/absolute/path/node_modules',
 '/absolute/node_modules',
 '/node_modules'
 ]
}
require 从哪里来?

在编写 CommonJS 模块的时候,我们会使用 require 来加载模块,使用 exports 来做模块输出,还有 module,__filename, __dirname 这些变量,为什么它们不需要引入就能使用?

原因是 Node 在解析 JS 模块时,会先按文本读取内容,然后将模块内容进行包裹,在外层裹了一个 function,传入变量
再通过 vm.runInThisContext 将字符串转成 Function形成作用域,避免全局污染。
let wrap = function(script) {
 return Module.wrapper[0] + script + Module.wrapper[1];
};

const wrapper = [
 '(function (exports, require, module, __filename, __dirname) { ',
 '\n});'
];
于是在 CommmonJS 的模块中可以不需要 require,直接访问到这些方法,变量。
参数中的 module 是当前模块的的 module 实例(尽管这个时候模块代码还没编译执行),
exports 是 module.exports 的别名,最终被 require 的时候是输出 module.exports 的值,
require 最终调用的也是 Module._load 方法。
__filename,__dirname 则分别是当前模块在系统中的绝对路径和当前文件夹路径。
ES6 模块

ES6 模块是前端开发同学更为熟悉的方式,使用 import, export 关键字来进行模块输入输出。ES6 不再是使用闭包和函数封装的方式进行模块化,而是从语法层面提供了模块化的功能。

ES6 模块中不存在 require, module.exports, __filename 等变量,CommonJS 中也不能使用 import。两种规范是不兼容的,一般来说平日里写的 ES6 模块代码最终都会经由 Babel, Typescript 等工具处理成 CommonJS 代码。

使用 Node 原生 ES6 模块需要将 js 文件后缀改成 mjs,或者 package.json "type"`` 字段改为 "module",通过这种形式告知Node使用ES Module` 的形式加载模块。

为什么平时开发可以混写?
前面提到 ES6 模块和 CommonJS 模块有很大差异,不能直接混着写。
这和开发中表现是不一样的,原因是开发中写的 ES6 模块最终都会被打包工具处理成 CommonJS 模块,以便兼容更多环境,
同时也能和当前社区普通的 CommonJS 模块融合。

2.关于webpack,babel,以及es6和commonJS之间的联系

现在的浏览器很多都不支持es6的语法,或者仅仅是部分支持,比如你用.360浏览器,你会发现它支持let却不支持箭头函数等。
babel就承担了“翻译”的角色,把es6的写法转换成es5的写法。

babel转换后的代码是遵循commonJS规范的,而这个规范,浏览器并不能识别。因此导入到浏览器中会报错,而nodeJS是commonJS的实现者,所以在babel转换后的代码是可以在node中运行的

为了将babel生成的commonJS规范的es5写法能够在浏览器上直接运行,我们就借住了webpack这个打包工具来完成,
因为webpack本身也是遵循commonJS这个规范的,从它的配置文件webpack.config.js中就可以看出来
//module.exports是commonJS的接口输出规范,es6的规范是export
module.exports = {
 entry: path.join(__dirname, 'index.js'),
 output: {
 path: path.join(__dirname, 'outs'),
 filename: 'index.js'
 },
};
babel:把es6的写法转换成es5的写法
webpack:将babel生成的commonJS规范的es5写法能够在浏览器上直接运行

3.path.resolve和path.join的区别?

两者都是拼接文件路径
path.resolve 获取绝对路径
path.join 获取在相对路径

4.Node.js的Event Loop

除了setTimeout和setInterval这两个方法,Node.js还提供了另外两个与"任务队列"有关的方法:process.nextTick和setImmediate。它们可以帮助我们加深对"任务队列"的理解。

process.nextTick方法可以在当前"执行栈"的尾部----下一次Event Loop(主线程读取"任务队列")之前----触发回调函数。也就是说,它指定的任务总是发生在所有异步任务之前。setImmediate方法则是在当前"任务队列"的尾部添加事件,也就是说,它指定的任务总是在下一次Event Loop时执行,这与setTimeout(fn, 0)很像

process.nextTick(function A() {
 console.log(1);
 process.nextTick(function B(){console.log(2);});
});

setTimeout(function timeout() {
 console.log('TIMEOUT FIRED');
}, 0)
// 1
// 2
// TIMEOUT FIRED

上面代码中,由于process.nextTick方法指定的回调函数,总是在当前"执行栈"的尾部触发,所以不仅函数A比setTimeout指定的回调函数timeout先执行,而且函数B也比timeout先执行。这说明,如果有多个process.nextTick语句(不管它们是否嵌套),将全部在当前"执行栈"执行。

setImmediate(function (){
 setImmediate(function A() {
 console.log(1);
 setImmediate(function B(){console.log(2);});
 });

 setTimeout(function timeout() {
 console.log('TIMEOUT FIRED');
 }, 0);
});
// 1
// TIMEOUT FIRED
// 2

上面代码中,setImmediate与setTimeout(fn,0)各自添加了一个回调函数A和timeout,都是在下一次Event Loop触发。
那么,哪个回调函数先执行呢?答案是不确定。运行结果可能是1--TIMEOUT FIRED--2,也可能是TIMEOUT FIRED--1--2。

令人困惑的是,Node.js文档中称,setImmediate指定的回调函数,总是排在setTimeout前面。实际上,这种情况只发生在递归调用的时候。

5.session如何实现登录

session 存储到redis

6.请描述koa2和express的中间件机制

koa2中间件实例

koa2的中间件是通过 async await 实现的,中间件执行顺序是“洋葱圈”模型。
中间件之间通过next函数联系,当一个中间件调用 next() 后,会将控制权交给下一个中间件, 直到下一个中间件不再执行 next() 后, 将会沿路折返,将控制权依次交换给前一个中间件

app.js

const Koa = require('koa');
const app = new Koa();


// logger
app.use(async (ctx, next) => {
 console.log('第一层 - 开始')
 await next();
 const rt = ctx.response.get('X-Response-Time');
 console.log(`${ctx.method} ----------- ${ctx.url} ----------- ${rt}`);
 console.log('第一层 - 结束')
});


// x-response-time
app.use(async (ctx, next) => {
 console.log('第二层 - 开始')
 const start = Date.now();
 await next();
 const ms = Date.now() - start;
 ctx.set('X-Response-Time', `${ms}ms`);
 console.log('第二层 - 结束')
});


// response
app.use(async ctx => {
 console.log('第三层 - 开始')
 ctx.body = 'Hello World';
 console.log('第三层 - 结束')
});
app.listen(3000);

控制台输出:
第一层 - 开始
第二层 - 开始
第三层 - 开始
第三层 - 结束
第二层 - 结束
打印第一次执行的结果: GET -------- /text ------ 4ms
第一层 - 结束
express中间件
与 koa2 中间件不同的是,express中间件一个接一个的顺序执行, 通常会将 response 响应写在最后一个中间件中

主要特点:
app.use 用来注册中间件
遇到 http 请求,根据 path 和 method 判断触发哪些中间件
实现 next 机制,即上一个中间件会通过 next 触发下一个中间件

const express = require('express')

const app = express()

app.use((req, res, next) => {
 console.log('第一层 - 开始')
 setTimeout(() => {
 next()
 }, 0)
 console.log('第一层 - 结束')
})

app.use((req, res, next) => {
 console.log('第二层 - 开始')
 setTimeout(() => {
 next()
 }, 0)
 console.log('第二层 - 结束')
})

app.use('/api', (req, res, next) => {
 console.log('第三层 - 开始')
 res.json({
 code: 0
 })
 console.log('第三层 - 结束')
})

app.listen(3000, () => {
 console.log('server is running on port 3000')
})

控制台输出:
第一层 - 开始
第一层 - 结束
第二层 - 开始
第二层 - 结束
第三层 - 开始
第三层 - 结束

const express = require('express')

const app = express()

app.use((req, res, next) => {
 console.log('第一层 - 开始')
 next()
 console.log('第一层 - 结束')
})

app.use((req, res, next) => {
 console.log('第二层 - 开始')
 next()
 console.log('第二层 - 结束')
})

app.use('/api', (req, res, next) => {
 console.log('第三层 - 开始')
 res.json({
 code: 0
 })
 console.log('第三层 - 结束')
})

app.listen(3000, () => {
 console.log('server is running on port 3000')
})

控制台输出:
第一层 - 开始
第二层 - 开始
第三层 - 开始
第三层 - 结束
第二层 - 结束
第一层 - 结束

7.如何读取一个1g大小的日志文件

b9P74J.png

8.nodejs是单线程还是多线程

Node.js的单线程指的是主线程是“单线程”,由主要线程去按照编码顺序一步步执行程序代码,假如遇到同步代码阻塞,主线程被占用,后续的程序代码执行就会被卡住。

nodejs运行机制:
a、V8引擎解析JavaScript脚本
b、解析后的代码,调用Node API
c、libuv(c++库)库负责Node API的执行。它将不同的任务分配给不同的线程,形成一个Event Loop(事件循环),以异步的方式将任务的执行结果返回给V8引擎
d、V8引擎再将结果返回给用户
单线程怎么实现高并发

每个Node.js进程只有一个主线程在执行程序代码,形成一个执行栈(execution context stack)。

主线程之外,还维护了一个"事件队列"(Event queue)。当用户的网络请求或者其它的异步操作到来时,node都会把它放到Event Queue之中,此时并不会立即执行它,代码也不会被阻塞,继续往下走,直到主线程代码执行完毕。

主线程代码执行完毕完成后,然后通过Event Loop,也就是事件循环机制,开始到Event Queue的开头取出第一个事件,从线程池中分配一个线程去执行这个事件,接下来继续取出第二个事件,再从线程池中分配一个线程去执行,然后第三个,第四个。主线程不断的检查事件队列中是否有未执行的事件,直到事件队列中所有事件都执行完了,此后每当有新的事件加入到事件队列中,都会通知主线程按顺序取出交EventLoop处理。当有事件执行完毕后,会通知主线程,主线程执行回调,线程归还给线程池。

3、单线程的好处:
(1)多线程占用内存高
(2)多线程间切换使得CPU开销大
(3)多线程由内存同步开销
(4)编写单线程程序简单
(5)线程安全

4、单线程的劣势:
(1)CPU密集型任务占用CPU时间长(可通过cluster方式解决)
(2)无法利用CPU的多核(可通过cluster方式解决)
(3)单线程抛出异常使得程序停止(可通过try catch方式或自动重启机制解决)
node可以实现多进程
1\. 创建多进程的模块
1.1 child_process
1.2 cluster
2. 创建多进程的方法

2.1 child_process有4种方法:
1. spawn: 创建子进程,执行非node程序,执行结果以流形式返回
2. execFile: 创建子进程,执行非node程序,执行结果以回调返回
3. exec: 创建子进程,执行shell命令,执行结果以回调返回,可以直接执行一串shell命令
4. fork: 创建子进程,执行node程序,执行结果以流返回

2.2 cluster有1种方法:
cluster.fork:创建一个子进程

3. nodejs多进程模型
nodejs的多进程是master-work模式,一个主进程中,创建出多个子进程

4\ 进程间消息传递
通过on('message')监听和send分发

5. 多个work监听同一个端口
nodejs中的多个work进程可以同时监听同一个端口。具体实现方式,child_process和cluster有区别。

5.1 child_process方式
child_process通常在master进程中创建socket或server,
通过send方法将socket或server,发送到worker进程,在worker进程中同时监听一个端口

5.2 cluster方式
cluster通过cluster.isMaster和cluster.isWorker来区分主进程和worker进程,在worker进程中,
可以直接启动server,来监听同一个端口cluster主要是提供了对多个子进程的管理,
子进程的创建、退出和重启,可以方便的进行。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容