-1

I have the following code.

class Node {
     constructor(value, parent, possibleChildren = []) {
     this.value = value;
     this.parent = parent;
     this.children = []
     this.setChildren(possibleChildren);
   }

setChildren(possibleChildren) {
  if (possibleChildren.length === 0) return [];

  while (possibleChildren.length > 0) {
      const value = possibleChildren.pop();
      // keyword *this* messes up the context. Save them function calls for lazy execution
      let childNode = () => new Node(value, this, possibleChildren);
      this.children.push(childNode);
    }
    this.children = this.children.map(child => child())
  }

getChildrenValues() {
    return this.children.map((child) => child.value);
  }
}

In the above the this.children variable is set properly. If I save the this.children array directly, without wrapping it in a function, I see incorrect children being set.

Example:

setChildren(possibleChildren) {
  if (possibleChildren.length === 0) return [];

  while (possibleChildren.length > 0) {
    const value = possibleChildren.pop();
    // keyword *this* messes up the context. Save them function calls for lazy execution
    let childNode = new Node(value, this, possibleChildren);
    this.children.push(childNode);
  }
}

I know that the context of this is not consitent without the function wrapper. What I do not understand is why. Any ideas?

Calling getChildrenValues on the first example returns ["A", "B", "C"].

Calling getChildrenValues on the second example returns ["C"]

class Node {
  constructor(value, parent, possibleChildren = []) {
    this.value = value;
    this.parent = parent;
    this.children = []
    this.setChildren(possibleChildren);
  }

  setChildren(possibleChildren) {
    if (possibleChildren.length === 0) return [];

    while (possibleChildren.length > 0) {
      const value = possibleChildren.pop();
      // keyword *this* messes up the context. Save them function calls for lazy execution
      const childNode = new Node(value, this, possibleChildren);
      this.children.push(childNode);
    }
  }
  
  getChildrenValues() {
    return this.children.map((child) => child.value);
  }
}

let root = new Node(null, null, "ABC".split(""));
console.log(root.getChildrenValues())
M.Nar
  • 512
  • 1
  • 9
  • 24
  • This has been _extensively_ explained elsewhere. E.g. https://stackoverflow.com/q/20279484/3001761. – jonrsharpe Apr 22 '22 at 20:49
  • 1
    @jonrsharpe This doesn't look like the usual problem with `this` in callbacks. The second version doesn't have a callback. – Barmar Apr 22 '22 at 20:52
  • Both pieces of code should have one and the same reference to `this`. I don't see why it would be different. – VLAZ Apr 22 '22 at 20:52
  • 1
    Please post a [mcve]. You can use a [Stack Snippet](https://meta.stackoverflow.com/questions/358992/ive-been-told-to-create-a-runnable-example-with-stack-snippets-how-do-i-do) to make it executable. – Barmar Apr 22 '22 at 20:54
  • How do you even determine that the value of `this` is lost? If you have a breakpoint, you'd observe it's *different* but that is going to be because of the recursion. If you do the instantiation inside the loop, then that will kick off another `setChildren` by the new Node being created. This the `this` would be that new node. However, the whole context would also be different anyway and unrelated to the parent executing `setChildren`. Is *that* what you're seeing and miscategorising it as an error? As @Barmar an example can clear things up. – VLAZ Apr 22 '22 at 20:56
  • 1
    What exactly is the problem with `this` in the example? It seems to work exactly as it should. The only thing that might be wrong is that you're passing around the same array of `possibleChildren`, so modifications to it in recursive calls will also change what the array is for the parent. While if you delay the instantiation of the children, the array would be processed and empty before they are initialised, so they'd have no possible children themselves. But that's not really related to `this` at all. I don't see why `possibleChidren` should even be passed down. – VLAZ Apr 22 '22 at 21:28

1 Answers1

0

I know that the context of this is not consistent without the function wrapper. What I do not understand is why?

This has nothing to do with the this keyword. Since you used an arrow function, it does refer to exactly the same object in both your code snippets, there is no difference.

The reason why you get different results from the two snippets is the lazy execution, but not with respect to this, but rather the possibleChildren array. In your first code, the while (possibleChildren.length > 0) runs and empties the possibleChildren array before you do the recursive new Node calls. In the second example, you call new Node during that loop, and you pass on the reference to the same possibleChildren array, which is being emptied by the recursive call and the loop therefore terminates right after the first iteration.

To fix this, just don't recursively pass the possibleChildren:

class Node {
  constructor(value, parent, childrenValues = []) {
    this.value = value;
    this.parent = parent;
    this.children = []
    this.setChildrenValues(childrenValues);
  }

  setChildrenValues(childrenValues) {
    for (let i=childrenValues.length; i--; ) {
      const value = childrenValues[i];
      const childNode = new Node(value, this  );
//                                           ^ no third argument, no grandchildren
      this.children.push(childNode);
    }
  }
  
  getChildrenValues() {
    return this.children.map((child) => child.value);
  }
}

let root = new Node(null, null, "ABC".split(""));
console.log(root.getChildrenValues())
Bergi
  • 630,263
  • 148
  • 957
  • 1,375