代码之家  ›  专栏  ›  技术社区  ›  dvsoukup

节点。js、express和对象实例化内部/外部路由端点

  •  3
  • dvsoukup  · 技术社区  · 6 年前

    我有一个关于对象实例化的问题。现在,我已经建立了一个相当大的应用程序,在后端实现了RESTful端点。

    我想知道什么是对象实例化的“最佳”方式,以及我所做的事情如何影响垃圾收集器。

    以以下内容为例:

    const MyClass = require('../controllers/myClassController');
    const router = require('express').Router();
    
    router.get('/', (req, res) => {
        console.log('GET request at api/someEndPoint');
        
        const myClass = new MyClass();
        myClass.doSomePromise()
            .then(() => {
                res.end();
            })
            .catch((err) => {
                res.status(500).end();
            });
    });
    

    这里,在路由路径实现中实例化了一个对象。

    现在,以这个为例:

    const MyClass = require('../controllers/myClassController');
    const router = require('express').Router();
    
    const myClass = new MyClass();
    
    router.get('/', (req, res) => {
        console.log('GET request at api/someEndPoint');
        
        myClass.doSomePromise()
            .then(() => {
                res.end();
            })
            .catch((err) => {
                res.status(500).end();
            });
    });
    

    在这里,对象在路由路径实现之外实例化。在我看来,这将在应用程序的生命周期内无限期地持续下去,而第一个示例是变量 myClass 会被清理干净。

    所以我真的想知道:

    1. 我这样想对吗?

    2. 我为什么要用一种方法和另一种方法做比较?似乎如果我选择了选项2,我还不如 static 类本身上的方法。。。

    3. 每种方法的优缺点是什么?

      我觉得在我的脑海里,我正在战斗,事情没有得到正确的清理,一种方式比另一种方式更好,就有多少交通量,避免比赛条件等!

    2 回复  |  直到 4 年前
        1
  •  4
  •   jfriend00    6 年前

    这真的很简单。

    1. 如果在整个应用程序期间只需要一个对象 你可以将一个对象用于任何需要使用它的请求,那么它就是“应用程序周期”类型的对象。在初始化时创建一个实例,存储其实例(可能作为模块级变量),并从那时起使用它。例如,如果您有一种缓存对象,设计用于许多请求处理程序,那么您可能希望创建一次,然后保留它,并由所有使用它的请求处理程序每次使用同一个对象。另一个例子是,一些应用程序在启动时创建一个到其主数据库的连接,然后所有其他请求都可以使用该连接。

    2. 如果每个请求都需要一个新对象 Date() 对象为了测量请求期间的某个持续时间,最简单的实现将创建 new Date() 对象,将其用于您需要使用它的目的,然后让GC在请求完成时处理它。

    3. 如果每个请求都需要一个独立的对象 因此,当多个请求处理程序同时“在运行”时,您需要确保每个请求处理程序都有自己的对象,并且没有共享或冲突的对象。

    4. 还有混合动力车型 (通常以这种方式进行性能优化),您可以创建一个对象池,然后每个需要的请求都可以“签出一个”,使用一段时间,然后在完成后将其返回到池中。当一个小的对象池通常可以满足服务器的需要时,这通常可以节省从头开始重建对象的时间。一些数据库使用这种“池”模型进行数据库连接。请求可能只是从池中获取数据库连接,进行查询,然后返回到池的连接,而不是为每个要执行查询的请求创建到数据库的新连接。

    在节点内部。js中,它使用线程池来实现磁盘I/O的某些部分。类似的概念。


    我想知道什么是对象实例化的“最佳”方式,以及我所做的事情如何影响垃圾收集器。

    最好的方法是选择与用法匹配的实例化模型。在上述选项1中,对象可能会在整个应用程序周期内存储,而根本不会进行GCed。在选项2和3中,将为每个请求创建和GCed对象。您不应该担心创建和使用对象,然后让它GC。这就是语言设计用于不具有持久状态并且应该保持更长时间的事物的方式。

    如果对象中没有特定于请求的状态,并且如果有多个请求处理程序试图同时使用该对象,则对象本身的状态不会出现问题,那么您实际上只是拥有一个全局服务,任何想要使用它的请求处理程序都可以使用它。这取决于您是否将其实现为您创建的对象,存储实例,然后在该实例上调用传统方法,或者将其实现为自身实例化的单例,您只需在其上调用函数或静态方法。这只是编码风格的差异,而不是功能上的差异。

    每种方法的优缺点是什么?

    通常,您需要尽可能多的封装和局部状态。无需将特定于请求的内容放入共享全局。为特定请求计算或计算的内容属于请求处理程序逻辑的内部。因此,只需遵循上述四个问题来确定对象是属于本地请求处理程序还是在更高范围内共享。如果你能通过保持事物的局部性来实现你的目标,那通常会更干净、更安全、更灵活。

    我觉得在我的脑海里,我正在战斗,事情没有得到正确的清理,一种方式比另一种方式更好,就有多少交通量,避免比赛条件等!

    GC负责为您清理。你可以信赖。只要您不在具有持久作用域的变量中持久存储对对象的引用,垃圾收集就会很好地为您解决问题。这就是设计语言的目的。你不应该害怕。如果您来自C或C++之类的非GC语言,那么您可能考虑过了。GC是你的朋友。非常、非常、非常偶尔地,您可能需要进行一些性能调整,以更清楚地了解您要求GC做多少事情,但在您实际发现需要以这种方式进行特殊优化之前,您几乎不应该担心这件事。

    避免请求处理程序之间竞争条件的最简单方法是在请求处理程序之间不共享任何状态。如果您需要处理给定请求的所有内容都是在本地创建和使用的,并且从未提供给任何其他请求处理程序,那么您将绝对避免多个请求处理程序之间的任何竞争或共享条件。一般来说,从那里开始,因为这是最简单和最安全的。然后,如果您发现有一些事情需要进行性能优化,那么您可能需要探索某些类型的对象的共享或缓存,您必须小心这样做,以避免引入共享问题。但是,您很少会在设计开始时尝试这样做,因为大多数时候不需要额外的复杂性。

        2
  •  0
  •   kentor    6 年前
    1. 是的,你的想法是合法的。即使像对象实例化这样的东西可能是一种微观优化。

    2. 正如Bergi在评论中已经提到的那样,如果不需要的话,你不应该在你的路线内重建或处理任何东西。您始终需要记住,路由处理程序中的代码是在到达该路由的每个请求上执行的。

    3. 所以你真正想问的是:我应该做什么 const myClass = new MyClass(); 每个应用程序生命周期一次,或者可能每秒数千次。只有在每次必须处理此路由的请求时,如果确实需要创建类的新实例,您才能回答。也许你不需要每次都创建一个实例。因此,为了避免不必要的工作,请将其放在路由处理程序之外。

    由于上述原因,截至#3,没有真正的赞成/反对列表。如果您可以避免路由处理程序中的实例化,那么请避免它!