skip to Main Content

I am working with tree-like structure in Javascript, I found for-loop of Javascript works unexpected way. Let me describe it:

<html>
<head><head>
<body>
<script>
class Test {
        constructor(name, children) {
            this.children = [];
            this.test = () => {
                let result = false;
                for (let i = 0; i < this.children.length; i++) {
                    console.log(this.children[i].name);
                    result = result || this.children[i].test();
                }
                if (this.name.includes('2')) result = true;
                return result;
            };
            this.name = name;
            this.children = children;
        }
    }

    const t = new Test('1', [
        new Test('11', [
            new Test('111', []),
            new Test('112', []),
        ]),
        new Test('12', [
            new Test('121', []),
            new Test('122', []),
        ]),
    ]);
    const result = t.test();
    console.log(result);
</script>
</body>
</html>

The above code is pure Javascript. A Test class may have children which are also Test class. It also have a test() method which returns boolean, the method has for-loop over its children, calls children’s test() method, then sums the result with || operator.

(So it is implementation of a kind of logical Some() operation on child nodes.)

However, the above code does not call the test() method of the Test instances with names 121 and 122.

  • If I replace the following part,

    result = result || this.children[i].test();
    

    with

    const subResult = this.children[i].test();
    result = result || subResult;
    

    then it works fine. It iterates over all the children.

  • If I modify the for-loop as following,

    for (let i = 0; i <= this.children.length; i++) { // <----- '<' has been replaced with '<='
        if (i === this.children.length) {
            console.log(i); // this prints '2' after calling '0'
        }
        console.log(this.children[i].name);
        result = result || this.children[i].test();
    }
    

    I can see the index of for-loop is jumping out.

  • If I make the method to return false by removing the if (this.name.includes('2')) result = true; then the all children’s test() methods are called.

  • I have tested the above codes in Chrome, Firefox and Safari, and got the same result.

Is this behavior correct? And if it is, can someone explain me the theory?

2

Answers


  1. That’s called Short-circuit evaluation – when the first operand of || is truthy, the second operand isn’t executed (no need, since the whole expression is truthy already):

    https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_OR#short-circuit_evaluation

    That’s why you don’t have recursive calls on the children when result is true.

    Login or Signup to reply.
  2. This is due to Short-circuit evaluation. OR returns the first truthy value and doesn’t evaluate the rest.

    The logical OR expression is evaluated left to right, it is tested for possible "short-circuit" evaluation using the following rule:

    (some truthy expression) || expr is short-circuit evaluated to the truthy expression.

    Short circuit means that the expr part above is not evaluated, hence any side effects of doing so do not take effect (e.g., if expr is a function call, the calling never takes place). This happens because the value of the operator is already determined after the evaluation of the first operand. See example:

    result = result || this.children[i].test();
    

    When the left hand side of the expression (result) is true, the right hand side of the or expression (the function call to this.children[i].test()) doesn’t get evaluated, so no function gets called.
    It’s actually making your program more efficent since when at least one of the children’s names contains 2, you don’t need to test the rest and already know the return value will be true.

    The reason

    const subResult = this.children[i].test();
    result = result || subResult;
    

    tests every child is because you call the function before the OR expression, whether or not result is true, and then use OR only on the already evaluated boolean result.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search