Javascript code help - Eloquent JS book

Javascript code help - Eloquent JS book
0.0 0

#1

First of all, sorry if I’m not supposed to post this kind of question here.

I am working in the front-end certification of FCC and I decided to dive a little bit deeper in the JS language while I’m at it. I’ve heard a lot of good feedbacks about Eloquent JavaScript (https://eloquentjavascript.net/ if you’re interested) and I feel like I am progressing. I’m one week or so into the book, and got stuck in chapter 14, about DOM manipulation, in the challenge below.

I am supposed to implement my own version of “document.getElementsByTagName” method. And the author gives us three tests.
I could figure out a way to solve 2 out of 3 tests the author gives us, and I tried everything I could to figure out why the second challenge fails (with the span element). I have pasted my code below. After some debugging, I discovered that my code is failing to push the information into the array with the bolded debug in line 12.

I have checked the author’s solution, which obviously is much more elegant than my personal mess (I’m proud of it either way), and it make totally sense. But I just can’t figure out why my code is failling to push the span element but not any other.

Any tips? Thanks in advance!

<h1>Heading with a <span>span</span> element.</h1>
<p>A paragraph with <span>one</span>, <span>two</span>
  spans.</p>

<script>
  function byTagName(node, tagName) {
    var arr = [];
    let tag = tagName.toUpperCase();
    if (node.children.length > 0) {
      for (let i = 0; i < node.children.length; i++) {
        if (node.children[i].nodeName == tag) {
          **console.log(node.children[i].nodeName, tag)**
          arr.push(node.children[i].nodeName)
        }
        if (node.children[i].children.length > 0) {
          byTagName(node.children[i], tag)
        }
      }
    } else if (node.nodeName == tag) {
        arr.push(node)
      }
    return arr;
  }
  //console.log(byTagName(document.body, "h1").length);
  // → 1
  console.log(byTagName(document.body, "span").length);
  // → 3
  //let para = document.querySelector("p");
  //console.log(byTagName(para, "span").length);
  // → 2
</script>

#2

Have a look at childNodes instead of children.


#3

I looked into childNodes earlier today, because the example uses it instead of .children (and also, the challenge itself tells me to use it). The answer I found is the following (from stackOverflow):

"
.children is a property of an Element. Only Elements have children, and these children are all of type Element.

However .childNodes is a property of Node. .childNodes can contain any node."
Source

Since the challenge wants me to retrieve elements, I figured it would be better to iterate over children instead of childNodes. What bugs me is that the code actually reads the “span” element, but doesn’t push it. Am I missing something here?


#4

Ok. I reviewed your code now (sorry, I was on the phone earlier).

The code is not doing what you expect it to do, above all, because of the recursive function.

byTagName(node.children[i], tag)

When JS executes this line from inside the same function, the first function won’t get any value from there (you are not assigning the output to any variable); as soon as it finishes executing its code, the first function continues with its own code ignoring that side process. Yes, logging the values will get “visually” what you’re looking for, but what you really need is to push those values into the array to return it, which never actually happens.

The solution to this behavior, would be to make an inner function inside your main function’s code and invoke it recursively from the inside whenever necessary.

I personally think it would be great for you to practice again. Empty the code editor, read the problem again and try to solve the problem from the beginning.

Hope it helps.

Happy coding!


#5

That makes sense, thanks a lot for your input! I am going to try it again.


#6

I figured out a way to fix my (messy)code. Your observation was on point, so before trying your proposed solution (declare an inner function and call it recursively) I decided to try and fix my code.

All I had to do was fix one line of code:

function byTagName(node, tagName) {
    var arr = [];
    let tag = tagName.toUpperCase();
    if (node.children.length > 0) {
      for (let i = 0; i < node.children.length; i++) {
        if (node.children[i].nodeName == tag) {
          arr.push(node.children[i].nodeName)
        }
        if (node.children[i].children.length > 0) {
          arr = arr.concat(byTagName(node.children[i], tag))
        }
      }
    } else if (node.nodeName == tag) {
        arr.push(node)
      }
    return arr;
  }

In the line where I call the function recursively, I simply added the concat() array method and it worked!
Thanks again for your help! :grin: