最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 常见js宿主环境(二):node.js

    正文概述 掘金(卖油条的。)   2021-02-04   420

    本文是系列文章第二篇,介绍js的第二种宿主环境,即node.js,想了解本系列其他参考这里。


    1 概述

    node.js是一个开源和跨平台的js运行时环境(runtime environment,也被称为host environment),和chrome一样 内置 v8 js引擎,另外的宿主api为js提供了服务端工作的能力。

    本文会对node.js的实现原理和常见用法做简要介绍。

    2 实现方式

    node.js的实现方式主要是封装了c++/c项目v8和libuv的代码,并结合一些其他library为开发者提供了js接口,具体依赖的library包括

    • v8
    • libuv
    • llhttp
    • c-ares
    • OpenSSL
    • zlib

    注意在源码中有两个主要目录,其中lib包含所有我们使用的js模块,src中是下面介绍的各种以c++/c为主的依赖实现。

    2.1 v8

    v8是一个跨平台的js引擎,由c++编写,实现了ecma规范,其他常见js引擎还包括

    • firefox的SpiderMonkey
    • safari的JavaScriptCore

    js通常被认为是一个解释型语言,但是现代js引擎为了加速运行会通过 just-in-time (JIT) 编译,虽然在编译js时花了一些事件,但是执行时会比单纯的解释性能更好。
    js引擎会首先将js代码解析为ast,并进一步通过解释器解释为字节码(bytecode),字节码是一种通常与机器无关的中间代码,如果按传统方式便可以直接在js引擎运行。为了更快运行,字节码被发送到优化编译器,后者会将字节码优化为执行更快地机器码(machine code),如果编译优化出现错误会返回字节码,更多细节参考JavaScript engine fundamentals: Shapes and Inline Caches。

    常见js宿主环境(二):node.js

    2.2 libuv

    由c编写,设计的初衷是为node.js提供跨平台的事件驱动非阻塞异步i/o模型,提供的功能包括

    • 支持epoll, kqueue, IOCP, event ports的全功能event loop
    • 异步TCP and UDP sockets
    • 异步dns解析
    • 异步文件和文件系统操作
    • 文件系统时间
    • 子进程
    • 线程池, 具体实现因系统而异,epoll on Linux, kqueue on OSX and other BSDs, event ports on SunOS and IOCP on Windows
    • 线程和同步原语

    其中阻塞指的是另外的js代码需要等待非js操作完成才能执行,i/o指的是与磁盘和网络之间的交互。

    常见js宿主环境(二):node.js

    2.2.1 Handles and requests

    libuv提供了两个和event loop一起使用的抽象Handles(中文翻译为句柄) and requests。
    其中handle表示long-lived对象,可以在激活时处理一些特定的操作,比如

    • 一个prepare handle会在每次event loop前都会获取它的回调
    • 一个tcp server handle在每次有新连接时都会获取它的连接回调

    request表示short-lived操作,这些操作可以被一个handle执行,比如write request可以被handle或单独用来写数据。

    2.2.2 i/o loop

    i/o loop,或被称为event loop,是libuv的核心部分,用单线程来执行,这部分会结合node.js实际使用的event loop来理解。

    浏览器中的event loop在html标准中被定义,我们在这篇文章有过讨论,这里简要重复一下,在浏览器中的event loop中存在一个microtask queue和一个或多个task(或被称为macroTask) queues,每个macro task完成后会检查microtask queue,将包含的microtask及其生成的microtask执行结束,就执行必要的渲染,然后执行下一个macrotask。 常见js宿主环境(二):node.js

    在node.js中的event loop有所不同,包含一个microtask queue和各种phase,每个phase执行的是macrotask,每个macrotask结束后执行microtask queue中的microtask,直到microtask queue为空(在node.js@11 之前是执行完每个phase后再执行microtask,详情查看New Changes to the Timers and Microtasks in Node v11.0.0)。

    其中microtask在node.js中包括process.nextTick()和Promise相关回调.

    具体每个event loop中的phase包括

       ┌───────────────────────────┐
    ┌─>│           timers          │
    │  └─────────────┬─────────────┘
    │  ┌─────────────┴─────────────┐
    │  │     pending callbacks     │
    │  └─────────────┬─────────────┘
    │  ┌─────────────┴─────────────┐
    │  │       idle, prepare       │
    │  └─────────────┬─────────────┘      ┌───────────────┐
    │  ┌─────────────┴─────────────┐      │   incoming:   │
    │  │           poll            │<─────┤  connections, │
    │  └─────────────┬─────────────┘      │   data, etc.  │
    │  ┌─────────────┴─────────────┐      └───────────────┘
    │  │           check           │
    │  └─────────────┬─────────────┘
    │  ┌─────────────┴─────────────┐
    └──┤      close callbacks      │
       └───────────────────────────┘
    

    具体为

    • timers 本阶段如果有到期的timer(包括setTimeout 和 setInterval)会执行其回调,否则结束本阶段。
    • Pending callbacks 执行上一轮loop被延迟到本次的回调,比如tcp错误
    • ide,prepare node.js内部使用
    • poll 轮询阶段,处理close回调、timer、setImmediate外所有异步i/o回调,主要是计算各个回调还有多久调用,如果可以调用了就被放在poll queue。
      • 如果没有timer到期
        • 如果poll queue非空,则会迭代这个queue同步执行,直到都被执行完或者到达系统相关的限制节点
        • 如果poll queue空的,则
          • 如果存在setImmediate()回调,则会到下一阶段执行该回调
          • 否则,event loop会等待回调被加入poll queue然后立刻执行
      • 一旦poll queue为空,就会检查timer,如果有timer到期,event loop会返回timer阶段执行会掉
    • check 执行setImmediate回调
    • Close callbacks 执行close回调

    注意下面这个案例

    setTimeout(() => {
      console.log('timeout');
    }, 0);
    
    setImmediate(() => {
      console.log('immediate');
    });
    

    因为timer的最小时间为1(见timer文档),因此如果在1ms内执行了setImmediate就会先执行setImmediate,否则先执行setTimeout

    其他参考

    • JavaScript Event Loop vs Node JS Event Loop
    • NodeJS 系列之事件循环
    • Libuv design overview

    2.2.3 File I/O

    对于文件i/o,libuv没有依赖特定平台的原语,用的是一个线程池的i/o操作,该线程池是全局的,可以执行以下操作,

    • file操作
    • dns方法
    • 用户特定的代码

    2.3 llhttp

    由c和ts编写,用来解析http,因为没有系统调用和分配,因此占用很小内存

    2.4 c-ares

    由c编写,用来处理异步的dns请求

    2.5 OpenSSL

    提供加密功能,用在tls and crypto模块

    2.6 zlib

    用来压缩和解压缩

    3 常用模块

    node.js借助上述底层依赖提供了丰富的模块和详实的api文档,下面我们挑选常用的几个进行介绍。

    3.1 process

    表示当前的进程,不需要require直接使用,可以从其获取一些信息,比如可以通过process.env.NODE_ENV获取环境变量。

    3.2 http

    使用时需要require('http')引入,可以用来做http server和client,一个简单的使用如

    const http = require('http');
    
    const hostname = '127.0.0.1';
    const port = 3000;
    
    const server = http.createServer((req, res) => {
      res.statusCode = 200;
      res.setHeader('Content-Type', 'text/plain');
      res.end('Hello World');
    });
    
    server.listen(port, hostname, () => {
      console.log(`Server running at http://${hostname}:${port}/`);
    });
    

    是我们最常用的模块之一

    3.3 events

    大部分的node.js 核心api都是基于异步的事件驱动架构,其中包含一类对象(即emitter)用来触发事件来调用相关监听器。比如

    const EventEmitter = require('events');
    
    class MyEmitter extends EventEmitter {}
    
    const myEmitter = new MyEmitter();
    myEmitter.on('event', () => {
      console.log('an event occurred!');
    });
    myEmitter.emit('event');
    

    和浏览器中的Event接口类似。

    3.4 file

    提供了和文件系统交互的方法

    3.5 path

    提供了处理文件和目的时路径相关的工具

    3.6 module

    这篇文章详细介绍了es module,在node.js中除此之外还有commonjs,两者的主要区别是前者对变量是live bindings,后者是浅拷贝(对原始类型复制值,对引用类型复制地址),因此前者对循环引用也能好的处理(如果出现循环引用,后者会只输出已经执行的部分,具体参考上面提到的链接)。

    4 框架

    node.js框架内容很多,基本功能是处理http连接。由于目前工作不涉及选型这一块,因此先丢两个参考链接

    • The complete guide to Node.js frameworks
    • 10 Best Nodejs Frameworks for Web Apps in 2021

    这里只讨论使用的最多的express.js和koa。

    4.1 express

    express是一个内置了部分常用功能的框架,可以用来处理一些基本操作,比如路由、使用中间件、使用模板引擎和错误处理。

    4.1.1 路由

    路由决定了怎么响应一个client对特定endpoint(指一个uri和一个特定的http方法)的请求,每个路由可以有一个或多个handler方法,会在路由匹配到时被调用。用来响应各种http请求。

    4.1.2 中间件

    中间件(Middleware )指的是可以作为路由handler的函数,可以访问到req,res和next函数,其中req表示请求对象,res表示响应对象,通过next调用可以将控制权向下传递。本质就是在应用的req-res循环过程中执行的函数。

    常见js宿主环境(二):node.js

    可以用来

    • 执行任何代码
    • 修改req,res对象
    • 结束req-res循环
    • 调用下一个中间价

    4.1.3 模板引擎

    模板引擎(template engine)用于将模板中的变量用实际数据表示,并将模板转换为html文件。

    4.1.4 基本使用

    express导出的是一个函数,并且挂载了多个静态方法。express源码入口文件如下

    exports = module.exports = createApplication;
    function createApplication() {
      var app = function(req, res, next) {
        app.handle(req, res, next);
      };
      app.init();
      return app;
    }
    
    /**
     * Expose the prototypes.
     */
    
    exports.application = proto;
    exports.request = req;
    exports.response = res;
    
    /**
     * Expose constructors.
     */
    
    exports.Route = Route;
    exports.Router = Router;
    
    /**
     * Expose middleware
     */
    
    exports.json = bodyParser.json
    exports.query = require('./middleware/query');
    exports.raw = bodyParser.raw
    exports.static = require('serve-static');
    exports.text = bodyParser.text
    exports.urlencoded = bodyParser.urlencoded
    

    具体实现细节参考How express.js works。

    导出的函数express调用后返回一个app对象,在这里表示application,包含各种方法,表示启用中间件、路由和模板引擎等。
    express.Router()可以返回一个可以作为handler的router实例,比如

    var express = require('express')
    var router = express.Router()
    
    // middleware that is specific to this router
    router.use(function timeLog (req, res, next) {
      console.log('Time: ', Date.now())
      next()
    })
    // define the home page route
    router.get('/', function (req, res) {
      res.send('Birds home page')
    })
    // define the about route
    router.get('/about', function (req, res) {
      res.send('About birds')
    })
    
    module.exports = router
    
    var birds = require('./birds')
    
    // ...
    
    app.use('/birds', birds)
    

    express.static(root, [options])是一个内置中间件用来作为静态文件服务器,比如

    app.use(express.static('public'))
    

    我们可以使用以下路径访问public目录下的文件,注意参数中的文件夹本身并不在目录路径中

    http://localhost:3000/images/kitten.jpg
    http://localhost:3000/css/style.css
    http://localhost:3000/js/app.js
    http://localhost:3000/images/bg.png
    http://localhost:3000/hello.html
    

    其他参考官方文档

    4.2 koa

    koa是有express团队出的另一个框架,和express的区别主要是引入了async函数和没有内置任何中间件,利用async和next可以实现真正意义上的中间件,即express等框架只是利用中间件将控制权向下传递,而koa可以向下传递后,然后可以再返回第一个中间件,类似于dom中的捕获和冒泡,这部分可以参考官方说明

    源码分析可以参考十分钟带你看完 KOA 源码

    5 进程管理工具

    进程管理工具(Process managers)用来管理node.js应用,可以保证用来在crash时重启、查看运行时性能和资源使用情况以及集群控制等,比如pm2。


    起源地下载网 » 常见js宿主环境(二):node.js

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元