How is JavaScript finding function names and variables at different times when different events occur? Code Answer

Hello Developer, Hope you guys are doing great. Today at Tutorial Guruji Official website, we are sharing the answer of How is JavaScript finding function names and variables at different times when different events occur? without wasting too much if your time.

The question is published on by Tutorial Guruji team.

I am working on creating a draggable function for SVG elements. It is working exactly as I intend for what I want, but I am wonder if someone can help explain a few questions I have about how the functions work.

This trimmed down example shows my function where:

  • The orange line by itself is draggable.
  • The red circle is not draggable.
  • The blue square and green triangle are draggable together.

"use strict"

/* Make an SVG element dragable using the translate transfrom. */
const makeDraggable = (el) => {

    /* Pointer SVG coordinates */
    const getPointerPosition = (e) => {
        if (e.touches) e = e.touches[0];
        return new DOMPoint(e.clientX, e.clientY).matrixTransform(svg.getScreenCTM().inverse());
    }

    /* Mouse down / touch start event */
    const dragStart = (e) => {
        /* Add event listeners. */
        root.addEventListener('mousemove', dragMove);
        root.addEventListener('touchmove', dragMove);
        root.addEventListener('mouseup', dragEnd);
        root.addEventListener('mouseleave', dragEnd);
        root.addEventListener('touchend', dragEnd);
        root.addEventListener('touchleave', dragEnd);
        root.addEventListener('touchcancel', dragEnd);

        /* Recursive check to find first parent group.  If found, apply transform to group. */
        let g = el;
        while (g && g.tagName !== 'g') g = g.parentNode;
        if (g) el = g;

        /* Check if element already has a translate as it's first transform.  If not, add it. */
        const transforms = el.transform.baseVal;
        if (transforms.length === 0 || transforms.getItem(0).type !== SVGTransform.SVG_TRANSFORM_TRANSLATE)
            translate = transforms.insertItemBefore(svg.createSVGTransform(), 0);
        else translate = transforms.getItem(0);

        /* Get pointer position during initial down/touch and offset the difference to the element translate origin. */
        offset = getPointerPosition(e);
        offset.x -= translate.matrix.e;
        offset.y -= translate.matrix.f;
    }

    /* Mouse move / touch move event */
    const dragMove = (e) => {
        /* Get pointer position of move, apply difference from initial pointer position to translate transform. */
        const point = getPointerPosition(e);
        translate.setTranslate(point.x -= offset.x, point.y -= offset.y);
    }

    /* Mouse up / leave / touch end / leave / cancel event */
    const dragEnd = () => {
        /* Remove inactive event listeners. */
        root.removeEventListener('mousemove', dragMove);
        root.removeEventListener('touchmove', dragMove);
        root.removeEventListener('mouseup', dragEnd);
        root.removeEventListener('mouseleave', dragEnd);
        root.removeEventListener('touchend', dragEnd);
        root.removeEventListener('touchleave', dragEnd);
        root.removeEventListener('touchcancel', dragEnd);
    }

    /* Attach event listeners to dragable element. */
    el.addEventListener('mousedown', dragStart);
    el.addEventListener('touchstart', dragStart);

    /* Set variables. */
    let svg = el;
    while (svg && svg.tagName !== 'svg') svg = svg.parentNode;
    const doc = svg.ownerDocument;
    const root = doc.documentElement || doc.body || svg;
    let translate, offset;

}

const loadSVG = () => {
    document.querySelectorAll(".draggable").forEach(el => makeDraggable(el))
}
* {
    margin: 0;
}

svg {
    background-color: lightblue;
    width: 200px;
    height: 200px;
}

.draggable {
    cursor: move;
}

.notdraggable {
    cursor: not-allowed;
}
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Drag Test</title>
</head>

<body>
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" onload="loadSVG()">
        <line class="draggable" x1="25" y1="10" x2="25" y2="40" stroke="orange" />
        <circle class="notdraggable" cx="75" cy="25" r="15" fill="red" />
        <g>
            <rect class="draggable" x="10" y="60" width="30" height="30" fill="blue" />
            <path class="draggable" d="M75,60 L 90,90 L60,90 z" fill="green" />
        </g>
    </svg>

</body>

</html>

My questions are:

  1. When the SVG element loads the loadSVG function is called, that function executes the makeDraggable function on each of the draggable elements. That function adds the mousedown & touchstart event listener and assigns them to the dragStart function to each of the draggable element.

    So later, after all the load functions are complete, and a mousedown/touchstart event occurs on one of the draggable element, and it goes to call the dragStart function, how does it find the dragStart function, because this function is found inside the makeDraggable funcntion?

  2. Similarly, the makeDraggble function also creates the variables translate & offset which is used to store information values used between the dragStart function (called on the mousedown/touchstart events) and dragMove (called on the mousemove/touchmove events). But I only see these variables created only when the makeDraggable function is called, which is only when the loadSVG function is called, only when the SVG is loaded.

    So, again later, after all the load functions are run, and the mousedown/touchstart & mousemove/touchmove events are triggered, how is it still finding these variables?

  3. And another example I see. The el variable comes from a parameter from the makeDraggable function, which again is only called during the load. But the el variable is used during the dragStart (mousedown/touchstart) function, which is called at a different time. So how is the dragStart function still using this variable? It’s not a global variable, and its use changes based on the element clicked on.

I suspect the answer to al these questions may be the same answer?

Answer

When adding an event listener to an object, the function that is added (and execution upon the event) has a certain enclosing scope. Included in that scope are variables and functions.

While function and arrow functions differ in their enclosing scope, I think the answer to your question is, that dragStart, which is indeed declared in makeDraggable is ‘bound’ to el via the event listener.

el is not just a value like 1 or true but a reference to an object. That object continues to exist after makeDraggable was executed.

The browser in your case ‘stores’ a reference to dragStart which again contains references to other functions and objects. The browser only ‘forgets’ about those when you deregister the event listener.

There is a lot more detail to this than what I described.

If you want to learn more about that, you could look into

  • enclosing scope of functions/arrow functions
  • JavaScript Event Loop
  • passing by value vs. passing by reference
We are here to answer your question about How is JavaScript finding function names and variables at different times when different events occur? - If you find the proper solution, please don't forgot to share this with your team members.

Related Posts

Tutorial Guruji