|
/

技术实践第十一期|关于Node.js的那些事(上)

官方小二
发布于 5 年前
21319
移动统计 U-App
其他


*更新于2022-3-25*

亲爱的开发者们,



🙋大家好,欢迎大家阅读本期技术内容:关于Node.js的那些事(上集)。我们将带领大家一起了解Node.js的历史、特点、应用场景、模块机制等等一系列内容。


🎩开发者们有任何想要探讨的技术内容,欢迎在本帖下方畅所欲言哦~



*作者:llkevin13579*


一. Node.js的历史、特点与应用场景



用了4年多的node.js,再去阅读《深入浅出Node.js》这本书,发现别有一番滋味。接下来我将用几小篇文章重新带大家领略Node.js的风采。


故事要追溯到2009年3月,28岁的美国程序猿Ryan Dahl宣布要基于V8创建一个轻量级的Web服务器并提供一套utils,之后他仅仅花了两个月时间,也就是当年5月份就发布了第一个版本。




2010年年底,就拿到了硅谷云服务商Joyent的赞助,他也加入了Joyent并全职负责Node的发展。可以看得出,他是一个能力很强并且很有天赋的程序猿,通过查看Wiki发现,他6岁的时候他妈妈就给了一台苹果电脑(Apple IIc, 如下图)给他,拿到电脑他不是第一时间拿来打游戏,而是拿来研究技术。因为他对技术的热爱,后面他进入了罗切斯特大学(University of Rochester)学习数学专业。





这东西为啥叫Node呢?为啥不叫什么D语言、E语言呢?起初,这个项目是叫web.js,也就是一个Web服务器,但项目的发展很给力,渐渐变成了基础框架,大家可以在它的基础上构建更多的东西,例如服务器、客户端、命令行等工具。它自身非常简单,而且易扩展,大家可以通过统一的规范来构建大型网络应用。每一个Node进程都构成这个网络应用中的一个节点,这就是为什么它叫Node.js。


那Node.js有哪些特点呢?总结下来有4点,异步I/O、事件与回调函数、单线程、跨平台。




一、异步I/O

我们知道对于Java语言,程序语句是一行行执行的,但是对于node.js来说,底层有很多异步I/O的API,包含文件读取(https://nodejs.org/dist/latest-v12.x/docs/api/fs.html)、网络请求(https://nodejs.org/dist/latest-v12.x/docs/api/http.html)等等,每个调用之间无需等之前I/O调用结束,这样的意义在于,我们可以从语言层面很自然地进行并行I/O操作



二、事件与回调函数

对于Node.js来说,我们处理请求,只需要关注成功和失败的时候的业务逻辑即可,在多个异步任务的场景下,事件与事件之间各自独立,具有轻量级、松耦合的特点。但这样的设计对于如何协助是一个问题,它和常规的同步方法相比,变得不是那么一目了然。后期我们可以通过一些方法可以解决这个问题,比如promise、async/await。



三、单线程

Node.js保持了Javascript在浏览器中单线程的特点,它最大的好处是不用像多线程编程那样处处在意状态的同步问题,没有死锁的存在,也没有线程上下文交换带来的性能上的开销。但它也存在一些缺点,比如无法利用多核的CPU,错误会引起整个应用退出、对于CPU密集型的事件需要等待计算任务完成才能重新到事件循环取事件。



四、跨平台

起初,Node.js只能在Linux平台上运行,后面随着Node.js的发展,微软帮助了它实现了Windows平台的兼容,在v0.6.0的时候已经可以在Windows上运行了。这得益于中间的这层libuv,它是一个可以在许多平台上实现跨平台的基础组件,Node的第三方C++模块也可以通过libuv实现跨平台。



由于Node.js具有以上四个特点,那么它的应用场景有哪些呢?第一个当然是需要高性能I/O的实时应用,比如聊天工具,花瓣网、蘑菇街的实时通知功能就是通过socket.io来实现的;第二个是利用并行I/O高效利用分布式环境,比如eBay的ql.io就是借用Node并行I/O的能力,高效地利用已有的数据。第三个是利用稳定接口提升Web渲染能力,例如LinkedIn的移动版网站就摒弃了同步的方式,利用并行I/O加速数据的获取,提升Web渲染速度。




二. Node.js的模块机制

背景

对于很多语言来说都有模块机制,比如Java有类,Python有import,Ruby有require,PHP有include和require。而js是通过<script>的方式导入,显得很凌乱,没办法做很好的抽象,也不方便进行逻辑的编写。社区的人也意识到了这一点,给js提出了CommonJS的规范,CommonJS的提出给js增加了很多模块化的成分,为js的发展提供了更好的保障。



我们说CommonJS解决了js的很多痛点,那这些痛点分别是什么呢?刚刚说的模块化是一点,还有就是没有自己的标准库,也没有包管理系统,当CommonJS解决了这些问题之后我们就可以用js来开发后台的服务了,这个时候Node就出现在人们面前了。



CommonJS的模块规范主要分为两部分:



(1)模块引用

比如引入一个math模块进来

var math = require("math");


(2)模块定义

比如在math.js中定义一个加法函数

// math.js

exports.add = function(num1, num2) {

return num1 + num2;

}

在另外一个文件中导入这个模块,就可以调用里面的属性或者方法了。

// Other file
var math = require('math');
math.add(1, 2);



对于Node.js来说引入模块有三步:(1)路径分析(2)文件定位(3)编译执行

为了区别Node的核心模块(如fs、http)、自定义模块(如request)、文件模块(如./math.js),我们引入的方法不同,对于核心模块和自定义模块我们直接在require当中写模块的名字,对于文件模块,我们在require当中写自定义模块的相对路径或者绝对路径。对于核心模块,在Node的源代码当中已经编译成二进制的代码了,所以它的加载会非常快,其次是文件模块、自定义模块。在node.js当中支持文件不包含后缀名(如js、json),Node.js会按照.js、.json、.node的顺序去尝试加载。编译和执行是最后一步,对于这三种类型的文件,Node.js会采用三种不同的方法去处理。对于.js文件,是采用核心模块fs去读取然后编译执行,对于.node文件,通过dlopen()方法加载后编译执行,对于.json文件,先是通过fs核心模块去读取,然后通过JSON.parse()解析返回结果。



有了这些前置的知识,我们就可以开始了解包和NPM了。


在Node.js当中,包要从package.json文件说起,这个文件是用来定义有关包的相关信息的,包含包的名字、描述、版本号、关键字、贡献者、依赖包、脚本说明等,有了这些定义,我们就可以完成第三方模块的发布、安装等操作。



(1)第三方模块的发布


a. 定义package.json文件
b. 完成包里面的代码,并在编写的过程中不断更新package.json当中的依赖包
c. 注册npm仓库账号
d. 上传包


(2)第三方包的安装与使用


发布的第三方包会在npmjs.com当中显示,当我们需要安装第三方包的时候,我们使用npm install(或者npm i)命令来进行安装,然后使用的时候根据上面说的引用自定义模块的方法进行使用。



总结来说,CommonJS的发展令到这门语言有了一个质的飞跃,摆脱了js过去凌乱的局面,进而形成了良好的生态系统,发展到此刻,npmjs.com上面的包已经有1,325,093次贡献了,可以说是一个很大、很好的社区了。





三. Node.js之异步I/O

试想一下,如果一个网页打开之后加载数据5秒才加载完,然后渲染出来,这个用户估计以为这个网站挂掉了吧?然后大部分用户会选择离开。如何解决这个问题?我们可以采用一部分一部分加载的形式去加载,同时去请求,响应的请求就可以先实现响应的部分,这样的用户体验就更好一些。Node.js正好有相关的机制,可以去做到这个效果,叫做异步I/O。



假设业务场景中有一组互不相关的任务需要完成,现在主流的两种方法是:(1)单线程串行依次执行(2)多线程并行完成。如果创建多线程的开销小于并行执行的开销,那么毫无疑问是选择创建多线程,但是多线程会存在锁的问题。至于单线程串行执行,这个对于程序员来说,当然是最友好的,但是通常在很多业务逻辑里面,需要进行多次略慢的任务,一个任务的执行受到上一个任务的影响,所以整体来说也不是特别高校。



Node.js在这两种方法当中做了一个平衡,利用单线程,远离多线程死锁、状态同步等问题,利用异步I/O,让单线程避免来阻塞的问题,更好的利用CPU资源。



在进程启动时,Node会创建一个类似while(true)的循环,每次循环为一个Tick,每次Tick都会看是否有还没完成的事件处理,如果有就进行处理,如果没有就跳出进程,流程图如下:




在每个Tick 的过程中,如何判断是否有事件需要处理呢?Node.js引入了观察者的角色,比如文件I/O观察者、网络I/O观察者等等,这些事件被传递到对应的观察者那里,事件循环从观察者那里取出事件并处理。



可以看出,事件循环是异步实现的核心,它与浏览器中的执行模型基本保持了一致,Node正是依靠构建了一套完善的高性能异步I/O框架,打破了Javascript在服务器端止步不前的局面。


🎩「大家都想知道的技术干货」👉:

技术实践第十期|令牌桶算法在小程序数据采集中的应用

技术实践第九期|前端开发:Vue.js中事件修饰符和按键修饰符的使用

技术实践第八期|前端开发:JavaScript中数组去重的方法汇总

技术实践第七期|业务型团队里的前端高效高质开发之路

技术实践第六期|如何屏蔽网站统计后台的垃圾广告

技术实践第五期|如何通过友盟+应用性能监控定位APP闪退问题

技术实践第四期|解读移动开发者日常-性能监控平台应用

技术实践第三期|HashTag在Redis集群环境下的使用

技术实践第二期|Flutter异常捕获

技术实践第一期|友盟+开发者平台Node.js重构之路


2个回答
 
5 年前

👍🏻💯👍🏻💯👍🏻💯👍🏻💯👍🏻💯👍🏻💯

 
5 年前

必须点赞支持,厉害的很

Loading ...
登录社区,分享你的经验