原文地址在我的博客,转载请注明出处,谢谢!Node是前端领域经常看到的一个词。node对于前端的重要性不言而喻,掌握node也是一个合格前端工程师的基本功。只有知道node,知道后端的一些东西,才能更好的和别人合作,发挥更大的价值。概述本文主要介绍我对node的一些核心特性的理解,包括node架构、特性、机制、核心模块和简单应用。浏览器到node的文本首先,node是一个使用javascript作为编程语言,运行在服务器端的平台。Node一般能做到服务器端语言能做到的,在某些情况下它还能做得更好,因为它有自己的特点。Node是一个javascript运行时(runtime),就像浏览器一样,它是一个平台。在浏览器中,V8引擎负责解释javascript。你在javascript中调用的接口是由浏览器实现和提供的。浏览器会调用其他语言(C++)实现封装的底层接口来完成任务;同样,在node中,V8引擎也负责解释javascript,而你在javascript中调用的浏览器提供的接口是不能用的,因为是在浏览器环境之外,但是因为你是在node环境中,你可以使用node提供各种用C++语言实现并用javascript封装的接口来完成后端任务。浏览器提供的API是用来处理前端任务的,比如弹窗、更换主题、处理用户操作等,而node是服务于后端的,所以提供的API是用来处理后台的-结束任务,比如响应请求和读取文件等,这些API由不同的模块提供。因为关注的领域不同,做的任务不同,提供的API也不同,但原理和相关实现与浏览器端大致相同。从浏览器到node,如果想了解更多,推荐IBM的文章Whatexactlyisnode.js?nodearchitecturenode架构分为三层(参考链接):图片来源Nodestandardlibrary:node标准库,即node模块提供各种接口的javascript实现,任何javascript代码,npminstall或者你写的模块都在这里Nodebindings:包括C/C++bindings(胶水代码)和Addon(添加其他C/C++库需要自己写的Bindings),该层向下封装V8和libuv接口,向上提供基础API接口,即connectionjavascript和C++之间的桥梁第三层是支持Node.js运行的关键,由C/C++实现。V8是谷歌开发的JavaScript引擎,提供JavaScript运行环境。可以说是Node.js的引擎,负责解释JavaScript,这点和chrome浏览器一样。libuv是专门为Node.js开发的封装库,提供跨平台的异步I/O能力,负责节点运行时的线程池调度。C-ares:提供异步处理DNS的能力。http_parser、OpenSSL、zlib等:提供对底层系统的访问,包括http解析、SSL、数据压缩等。我们通常使用的是第一层节点的各个模块实现的接口。那他们怎么合作呢?程序启动时,V8引擎会先解析javascript代码,通过Node绑定调用C/C++库。当当前事件被执行时,事件会被放到调用栈(stackandheap)上进行处理(可以理解为放到一个workspace中,如上图所示),任何I/O请求在栈会交给libuv处理,libuv维护一个线程池,里面有一些工作线程(如下图)。请求会调用这些线程来完成任务,而这些线程会调用底层的C/C++库。完成后,libuv将结果返回到事件队列,等待主线程执行。在此期间,主线程继续执行其他任务。NodeExecutionFeatures单线程,非阻塞I/O单线程意味着只在一个线程上运行javascript。首先javascript在浏览器端是单线程的,这是为了避免多线程造成的任务冲突;其次,java、PHP等多线程后端语言,为了避免同步I/O阻塞,每处理一个连接都会产生一个新的线程,这样在遇到大的时候会受到物理内存的限制并发请求数。Node延续浏览器端单线程javascript,只用一个主线程执行javascript,不断循环事件队列执行事件。实际上,主线程发出的I/O请求都会交给其他线程完成,其他线程完成后返回结果,放入事件队列。在此期间,主线程会继续执行其他任务,即交给libuv后直接返回,继续执行后面的任务。主线程只负责循环执行事件队列,所以这种模式称为非阻塞I/O。性能非常好,适合处理大量并发请求,还能简化开发。事件驱动机制还是和浏览器类似。一般来说,浏览器端将鼠标点击、键盘按键等定义为事件,而node也将网络请求、I/O操作等定义为事件。严格来说,所有的动作都是事件,这就是事件驱动的思想。当程序启动时,进入事件循环,不断遍历执行事件队列中产生的事件,在执行过程中,会产生新的事件,因此称为事件循环。当主线程执行事件时,会把麻烦的I/O请求交给libuv去调度其他工作线程帮忙。任务完成后会返回事件,并将结果送入事件队列等待主线程处理。在此期间,主线程继续执行其他任务。mbp曾经打过一个巧妙的比喻,把Node.js看成是一家餐厅。这里我借用他的例子,稍微修改一下,来说明Node.js的实现:把一个Node.js应用想象成一家星巴克,一个训练有素的前台服务员(唯一的主线程)在柜台接受订单。当很多顾客同时来时,排队(进入事件队列)等待接待;每当服务员接待顾客时,服务员会将订单通知经理(libuv),经理安排相应的专职人员煮咖啡(工作线程或系统属性)。这个全职人员会根据订单要求使用不同的食材和咖啡机(底层C/C++组件)制作咖啡或甜点。通常有四个这样的专职人员待命(线程池),高峰期也可以安排更多(但是需要安排人一大早就来上班,不能短时间通知中午)。服务员将订单传给经理后,不需要等待咖啡做好,而是直接开始为下一位顾客服务(事件循环放入调用栈的另一个事件)。您可以将当前调用堆栈中的事件视为正在柜台服务的客户。当咖啡喝完后,它会被送到顾客队列的最后一个位置。当它移动到柜台时,服务员会喊出对应顾客的名字,顾客就会过来拿咖啡(最后一段在现实生活中听起来有点奇怪,但是从程序执行)——ByAmio如果想深入了解javascript事件驱动机制,建议深入理解javascript事件循环机制节点模块节点模块机制是CommonJs的实现。起初,javascript标准混乱,没有其他成熟语言(如C++)的模块机制、标准库、接口等。为了让javascript具备开发大型应用的能力,也为了让javascript能够在后台运行,CommonJS制定了javascriptModule规范。Node借鉴此规范以模块的形式组织javascript。模块机制是成熟语言所必需的。一个模块代表了一个功能的封装。它就像积木一样。不同的模块可以连接在一起,使该语言具有极强的可扩展性。节点模块机制也制定模块规范,让全球开发者在节点官网上传自己的包。另外,node社区已经实现了node包管理器npm,可以使用npm轻松管理各种包。node的模块分为核心模块和用户模块。前者是底层内置模块,后者是第三方模块。核心模块包括Global(全局对象)、Http、fs(文件系统)、Buffer、Stream、Events、URL、path等,这些模块提供了后端服务的基本功能,都为自己的功能提供了API.使用模块时,require就可以了。但是在require后面,node有一个查找模块的机制:从上图可以看出,node先从缓存中读取,如果有直接从缓存中读取,就加载并缓存,所以你不用不得不一遍又一遍地寻找它。非常有效率。当节点在缓存中没有找到模块时,它会分析require路径和文件后缀。Node有一个模块路径搜索策略。我们可以在名为module_paths的js文件中console.log(module.paths)然后运行nodemodule_paths.js来间接查看node查找文件module具体文件的方式:['/home/username/nodeProject/node_modules','/home/username/node_modules','/home/node_modules','/node_modules']//Linux下面的数组输出(/home/username因电脑不同而不同)['c:\\nodeProject\\node_modules','c:\\node_modules']//windows也是按以下顺序:当前文件目录node_modules目录下。父目录下的node_modules目录。父目录parent目录下的node_modules目录。逐级递归路径,直到根目录下的node_modules目录。这些命令都在查找缓存之后。找到模块后,node会在导入之前编译并执行模块。编译成功后会缓存起来,并将执行结果返回给调用者。借助node自身核心模块的基础功能,简单的应用可以进一步封装更强大易操作的功能。就像jQuery对javascript的基本API一样,node社区也创建了Express和KOA等框架来构建node。js程序的这些框架详见2017Node.js开发框架对比此外,node还可以连接MySQL和MangoDB进行数据库操作。下面是使用express脚手架生成的node应用基础结构:.├──app.js//程序入口├──bin│└──www//二次机制文件├──package.json//项目配置文件├──public│├──images│├──javascripts│└──stylesheets│└──style.css├──routes│├──index.js//路由文件入口│└──users.js└──views├──error.jade//界面模板├──index.jade└──layout.jade现在使用node作为后端语言,通常需要配合类库和框架使用。node的单线程、非阻塞的特性,使其非常适合高并发的应用,适合处理大量重复简单的逻辑,适合构建Rest/JSONAPI服务;同时,也正是由于这些特点,node不适合用于CPU使用率重、IO使用率低的偏计算应用。缺点是因为是单线程,如果一个进程挂了,就全部挂了,可靠性低,但是这个是可以避免的。node更多的应用在前端、中间件、前后端分离等方面。由于node的诸多优势,现在越来越多的大公司开始使用node,并深入使用node。总结node的核心概念和思想远不止这些,还有很多应用。可惜本人水平有限,只能说说浅薄的水平。进程管理、异步编程、异常调试、部署、性能调优等很多东西。、与集群的协同、CDN等值得深入探讨。不管怎样,node是javascript迈向企业级开发语言的重要一步(也许已经是)。前端工程师从来没有像现在这样厉害,能做的事情越来越多,涉及的领域也越来越多。前端越来越精彩了。
