Day1: Node.js

写在前面


这几天太懒了, 过年前还有点时间, 就想自己学点东西. 写这个纯粹是为了督促自己.

大学生活就整下这最后半年了, 现在想想, 当时觉得学了很多东西, 但是现在觉得也没学到什么东西. 不得不承认: 一些难的东西, 数据结构啊, 编译原理啊, 机组啊, 操作系统啊, 并不常用, 忘得太快了. 而语言方面用的又太少了, 剩下的只有大体内容, 各种边边角角的, 也说不定什么时候忘记了. 始终觉得自己动手能力不够. 最后这半年, 就尽力做点东西吧.

首先总结下自己. 语言上, 软件工程学的那些都会点, 说出来会的语言挺多, 但是觉得都是会用而已的程度. 看过一些设计模式, 但是仅仅能理解... 也许能拿出来说的, 只有喜欢写点东西而已了吧.

Node.js

忘记在什么时候接触了, 应该挺早的, 因为当时还没有中文资料, 好像半年多之后才出了第一本中文Node教程, 好像是<Node.js开发指南>. 当时就觉得Bootstrap + Node.js + MongoDB一套很好, 因为一种语言打通前后端. 但是可恨自己当时没耐下心, 出去打工补贴生活费了. 后来提出了"全栈", MEAN什么的, 觉得当时如果没有中断学习, 大概现在也能自己做出来点什么了.

虽然说, 有视频教程, 也有中文的书籍了, 但是我还是觉得, 看官方文档也许能更快地有个大致地理解. 如果向深入一点, 看看相关的"动物书", 一般是去Salttiger, 或者Bookdl上找. 穷学生, 当然是能省则省了.

Node 官网
Node 文档

这里不得不吐槽一下, 终于有一个稳定版本了!

重新思考了一下, 觉得对于Node.js来说, 有几个比较难理解的概念, 然后熟悉下标准库, 就可以去npm找框架去开发了.

异步


这个其实主要体现在IO上, 例如读取文件, 查看其中有多少the单词. 一般情况下是你的程序先打开文件, 在这里, 操作系统会对文件系统进行操作, 文件会被部分缓冲到内存中, 创建文件描述符等等, 等一切准备妥当之后, 再交给用户进行操作, 操作完了以后, 你的程序关闭文件, 这个时候, 操作系统会对资源进行释放等等操作后, 你的程序再推出. 那么问题就是你的程序始终是在等操作系统完成后再进行自己的任务.

你的程序    |-打开文件-|         |数the的个数, 关闭文件|
                      v         ^                   v
系统                  |-打开文件-|                   |关闭文件|

于是有人想, 我可不可以利用程序等待系统完成操作的这部分时间做点别的呢? 于是流程变成了你的程序告诉系统, 我要读取文件, 读取完毕了就数里面有多少个the, 我先干点别的. 当系统读取完文件之后就会查看你告诉我做什么, 直接去做. 在数the的个数之后, 再告诉系统, 关闭文件, 这个时候你又可以干点别的了


你的程序    |-打开文件, 巴拉巴拉-||数the的个数, 关闭文件, 巴拉巴拉|
                      v                      v
系统                  |-打开文件-|            |关闭文件|

处理明显利用了CPU以外, IO操作被击中起来, 也利于系统对IO请求的优化.

回调


回调就像菜谱一样, 厨艺丰富的人把如何做菜都告诉你了, 你只要按照上面做就差不多了, 先放油, 油温起来了就放入打好的鸡蛋, 等鸡蛋饼熟了, 两面略有硬度就可以盛盘了(忽略个人厨艺影响).

事件 该干什么
油温升起来了 倒入打好的鸡蛋
鸡蛋饼熟了, 两面略有硬度 盛盘

Node.js主要是依赖V8和libuv. 由于libuv是异步事件驱动的, 所以Node很自然, 也是异步事件驱动的. 这个感觉和Win32的回调函数一样, 传入函数指针, 这样, 你的代码在一些情况下就会被调用, 不必担心里面到底是什么情况. V8更不用说了, 有JIT, 性能很是厉害.

之所以是回调, 我猜是因为普遍情况下, 都是外层程序调用内层程序提供的API接口, 外部程序是调用者, 内部程序是被调用者, 而这些回调函数的调用, 正好反过来了.

EventEmitter


大部分Node对象都是EventEmitter, EventEmitter就是可以触发事件的对象. 你可以在EventEmitter上设置监听函数, 当某个事件触发时, 你的监听函数就会被调用. 用代码解释的话就像下面这样.

'use strict'

class Emitter{
  constructor(){
    this._handlers = {};
  }
  on(event, callback){
    this._handlers = this._handlers || {};
    this._handlers[event] = this._handlers[event] || [];
    if(this._handlers[event].indexOf(callback) < 0){
      this._handlers[event].push(callback);
    }
  }
  off(event, callback){
    if(this._handlers && this._handlers[event] instanceof Array){
      if(event && callback){
        this._handlers[event].splice(this._handlers[event].indexOf(callback), 1);
      }else if(event){
        this._handlers[event] = [];
      }else{
        console.log('invalid argument');
      }
    }
  }
  emit(event){
    if(this._handlers[event] instanceof Array){
      this._handlers[event].forEach(function(handler){
        handler();
      });
    }else{
      console.log('unhandled event', event);
    }
  }
}


var person = new Emitter();
function hungry(){
  console.log('I\'m hungry.');
}
function needFood(){
  console.log('I need food.');
}
person.on('hungry', needFood);
person.on('hungry', hungry);
person.emit('hungry');

当然, 功能不止这些, 可以看EventEmitter 手册. 很多Node对象都是EventEmitter, 例如process, http.Server等等. 每个类都有自己的事件, 接受的回调函数也会有不同的参数, 这个就得查手册了. 比如http.Server就会有这么多事件:

Event Loop


Node.js是单线程的, 也是异步事件驱动的, 那么他的执行就是不断的循环. 每次循环都进行检查, 如果有需要处理的事件就处理, 没有就休眠, 腾出CPU, 如果所有的事件都处理完了, 那么就自动退出. 事件有很多, 比如IO操作, 定时器等等.

首先触发一个事件, 就是执行你的程序. 然后Node就会运行你的代码, 非阻塞的操作会作为请求, 发送出去, 然后不管请求的结果怎么样, 操作会怎么样, 直接执行你后面的代码. 执行完所有的阻塞的代码后, 结束当前的事件循环

在下一个事件循环中再次看是否有命令需要执行, 没有就退出. 因为你之前发出了操作的请求, 所以会有事件需要处理. 可是此时没有事件发生, 那么就让出CPU, 只到被唤醒.

当某个, 或者某些操作结束了, 就会触发事件, Node.js知道事件发生了, 就会利用非阻塞请求时提供的回调函数去处理. 所有的事件都会排队, 等待Node一一处理. Node.js在每次事件循环中只会处理一个事件, 因为Node.js是单线程的.

同样, 问题容易出现在单线程这里. 假设当前有很多事件需要处理, 那么如果你在当前事件循环中做了很耗时的操作, 那么下个事件就不能被及时处理.

process.nextTick(function(){
  console.log('next event loop')
});
for(var i = 0; i < 1999999999; i++){
  i++;i--;
}
console.log('current event loop');

process.nextTick是用来将一个函数放到下一个事件循环中执行的. 这里会首先将一个函数放到下一个事件循环中执行, 然后进行一个耗时操作, 再输出一行字. 执行结果就是当前事件循环输出current event loop后, 进入下一个事件循环周期, 输出next event loop. 如果没有耗时操作, 两条输出信息会立刻出现. 但是这里有一个耗时操作, 使Node在第一个事件循环中停留很长事件, 才进入第二个事件循环, 没有完成当前事件循环的node是没法进入下一个事件循环的, 导致第二个事件循环中的输出语句没有立即显示出来.

这个和多线程不一样, 因为两个线程执行两个操作, 在一个线程上阻塞了, 另一个线程不受影响.

因为模型的优势, 在占用更少资源的情况下, Node.js能处理同样多的网络请求数量, 甚至更多. 至于单线程无法利用多核CPU的问题, 可以启用多个Node实例来达成多线程, 而每个Node.js实例是独立的, 其中一个崩溃不会影响其他的Node.js, 比多线程更加稳定. 这个也和Chrome浏览器的多进程设计不谋而合. Node.js本身的优劣势已经很清晰了, 性能好, 但是不怎么适合写复杂逻辑, JavaScript语法有点坑, 但是适合前端的人来写逻辑等等.

官方例子


const http = require('http');

const hostname = '127.0.0.1';
const port = 1337;

http.createServer((req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/plain' });
  res.end('Hello World\n');
}).listen(port, hostname, () => {
  console.log(`Server running at http://${hostname}:${port}/`);
});

这个是官方说明里的代码, 用来阐述Node.js写Web的样子. 先不管里面的ES6. 总体的意思是加载http模块[1], 然后利用http模块创建一个server对象并监听在hostname:ip[2]. 每次接到HTTP请求的时候, 就会自动调用createServer接受的函数.

在看Node.js的时候, 总感觉和写C语言的程序一样, 设置一个个函数指针作为回调函数. 这个不免有几个问题, 比如层层回调, 套嵌的代码越来越多[3]; 改变了上下文, 让this指向不明确[4]; ES5里, 循环体内引用循环变量的问题[5]. 但是解决方案也有很多, 所以需要看的东西就更多了, 这就导致, 学习曲线不是很陡峭, 但是需要学的东西非常多.


  1. const http = require('http');

  2. http.createServer会创建http.Server对象, 然后直接调用该对象的listen方法

  3. 回调金字塔, 参考SegmentFault

  4. 在回调函数中, 函数运行的上下文在全局上, 而不在某个对象或者new的上下文中, 参考SegmentFault

  5. 由于异步, 循环会先结束, 此时循环体内引用的循环变量是循环结束的值.

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

推荐阅读更多精彩内容