Search code examples
javascripttypescriptclass-methodarrow-functions

Arrow function vs Class methods memory footprint


I was reading about arrow functions (in typescript context). And I came across this line.

arrow function is created per object of type Handler. Methods, on the other hand, are only created once and attached to Handler's prototype. They are shared between all objects of type Handler.

Source : https://www.typescriptlang.org/docs/handbook/functions.html

I am unable to understand. Please answer if anyone can explain.


Solution

  • When you have this:

    class Example {
        method() {
        }
    }
    
    const e1 = new Example();
    const e2 = new Example();
    

    You have one copy of the method function, not two. It's on the object that both e1 and e2 use as their prototype, like this:

            +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−+
            |                                             |
            \   +−−−−−−−−−−−−+                            |
    Example−−+−>| (function) |                            |
                +−−−−−−−−−−−−+           +−−−−−−−−−−−−−+  |
                | prototype  |−−−−−−+−+−>|  (object)   |  |
                +−−−−−−−−−−−−+     /  /  +−−−−−−−−−−−−−+  |
                                   | |   | constructor |−−+  +−−−−−−−−−−−−−−−−+
                                   | |   | method      |−−−−>|   (function)   |
                                   | |   +−−−−−−−−−−−−−+     +−−−−−−−−−−−−−−−−+
                                   | |                       | name: "method" |
                +−−−−−−−−−−−−−−−+  | |                       +−−−−−−−−−−−−−−−−+
    e1−−−−−−−−−>|   (object)    |  | |
                +−−−−−−−−−−−−−−−+  | |
                | [[Prototype]] |−−+ |
                +−−−−−−−−−−−−−−−+    |
                                     |
                +−−−−−−−−−−−−−−−+    |
    e2−−−−−−−−−>|   (object)    |    |
                +−−−−−−−−−−−−−−−+    |
                | [[Prototype]] |−−−−+
                +−−−−−−−−−−−−−−−+
    

    But when you do this:

    class Example {
        constructor() {
            this.method = () => { };
        }
    }
    
    const e1 = new Example();
    const e2 = new Example();
    

    or this:

    class Example {
        method = () => { };
    }
    
    const e1 = new Example();
    const e2 = new Example();
    

    You have two copies of the method function, one for e1 and one for e2, like this:

            +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−+
            |                                                                      |
            \   +−−−−−−−−−−−−+                                                     |
    Example−−+−>| (function) |                                                     |
                +−−−−−−−−−−−−+                                    +−−−−−−−−−−−−−+  |
                | prototype  |−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−+−+−>|  (object)   |  |
                +−−−−−−−−−−−−+                              /  /  +−−−−−−−−−−−−−+  |
                                                            | |   | constructor |−−+
                +−−−−−−−−−−−−−−−+                           | |   +−−−−−−−−−−−−−+
    e1−−−−−−−−−>|   (object)    |                           | |
                +−−−−−−−−−−−−−−−+                           | |
                | [[Prototype]] |−−−−−−−−−−−−−−−−−−−−−−−−−−−+ |
                | method        |−−+                          |
                +−−−−−−−−−−−−−−−+  |     +−−−−−−−−−−−−−−−−+   |
                                   +−−−−>|   (function)   |   |
                                         +−−−−−−−−−−−−−−−−+   |
                +−−−−−−−−−−−−−−−+        | name: "method" |   |
    e2−−−−−−−−−>|   (object)    |        +−−−−−−−−−−−−−−−−+   |
                +−−−−−−−−−−−−−−−+                             |
                | [[Prototype]] |−−−−−−−−−−−−−−−−−−−−−−−−−−−−−+
                | method        |−−+
                +−−−−−−−−−−−−−−−+  |     +−−−−−−−−−−−−−−−−+
                                   +−−−−>|   (function)   |
                                         +−−−−−−−−−−−−−−−−+
                                         | name: "method" |
                                         +−−−−−−−−−−−−−−−−+
    

    Any decent JavaScript engine will share the code amongst those function instances, but the function instances themselves must be distinct, because they close over different contexts (the context of the call to the constructor where they're created). (The function objects themselves don't need to be very big, particularly in a thoroughly-optimized engine. If you look at the internal slots a function must have [in effect; engines can optimized provided they behave as described by the spec], only [[Environment]] varies between them.)

    The advantage to the arrow functions created per-instance is you don't have to worry about what this they're called with, because they ignore it; instead, they use the this they close over (which will refer to the instance created when they were created), which can be handy for callbacks.

    The advantage to the method is that it's shared, and (in highly-dynamic environments) if it's replaced on the prototype with a different implementation, e1 and e2 will use that updated implementation. (That's a rare edge case.)