Welcome to Software Development on Codidact!
Will you help us build our independent community of developers helping developers? We're small and trying to grow. We welcome questions about all aspects of software development, from design to code to QA and more. Got questions? Got answers? Got code you'd like someone to review? Please join us.
Post History
TL;DR To count the number of li items, just do: console.log(document.querySelectorAll('ul.example > li').length); That's all, no need to complicate with filter.call (but I'll explain that t...
Answer
#5: Post edited
- # TL;DR
- To count the number of `li` items, just do:
- ```javascript
- console.log(document.querySelectorAll('ul.example > li').length);
- ```
That's all, no need to complicate with `filter.call`.- ---
- # Adapt that to your HTML
- Of course you could change the selector to be more (or less) specific. Ex: if you want any `ul`, you could use `document.querySelectorAll('ul > li')`, or if you want `ul` with specific id, use `document.querySelectorAll('ul#specificId > li')`.
- Just reminding that `>` is a [Child Combinator](https://developer.mozilla.org/en-US/docs/Web/CSS/Child_combinator), which means it gets only the `li` tags that are direct descendants of the `ul`. Example, if you have this HTML:
- ```html
- <ul class="example">
- <li>item 1</li>
- <li>item 2</li>
- <li>
- Item 3:
- <ul>
- <li>sub item 3.1</li>
- <li>sub item 3.2</li>
- </ul>
- </li>
- </ul>
- ```
- Then `document.querySelectorAll('ul.example > li').length` will return `3`, because the selector gets only the `li`'s containing "Item 1", "Item 2" and "Item 3". The tags with "sub item 3.x" won't be returned, because they're not direct descendants of `ul.example`.
- On the other hand, if I do `document.querySelectorAll('ul.example li').length`, it'll return `5`, because now it gets all `li` tags inside `ul.example` (no matter how many depth levels they're in).
- Which one to use will depend on what you mean by "_`li` list items of a given `ul`_" (based on your second code with `filter`, I guess you need `ul.example > li`).
- ---
- # Regarding `undefined`
- [According to MDN](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll#return_value), `querySelectorAll` returns a `NodeList` (even if nothing is found, it still returns an empty one). And its [`length` property](https://developer.mozilla.org/en-US/docs/Web/API/NodeList/length) returns the number of items (zero if it's empty). So there's no way to have `document.querySelectorAll('whatever').length` equals `undefined`.
- What is *probably* happening: when you call `console.log(whatever)` in browser's console (or in Node's console - when you run just `node` in the command line), it prints the value of `whatever` and **it also shows the return of `console.log` function** (which is `undefined`). If that's the case (you're testing in a console), try typing just `document.querySelectorAll(etc...` (without `console.log`) and see that it won't show `undefined` anymore.
- ---
- # Explaining `filter`
- Using `filter`, IMO, is not necessary in this case, as just using `querySelectorAll` (with the correct selector) is enough. But let's analyze your code.
- First, [according to the documentation](https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementsByClassName), `getElementsByClassName` "returns an array-like object" (actually a [`HTMLCollection`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCollection)), which means it can return more than one element (just as `querySelectorAll`).
- Then you get the first element of this `HTMLCollection` (when you do `[0]` on it). As `example` is the `ul`'s class, the search is returning this `ul` inside the collection, and by using `[0]` you got that `ul` element.
- After that, you get the `children` property of this `ul`, which may contain the `li` elements, but [it also can have other elements](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ul). So we need to filter those elements.
- One alternative would be to use `Array.prototype.filter`, but the `children` property is not an array and it doesn't have this method:
- ```javascript
- // get ul's children
- var children = document.getElementsByClassName('example')[0].children;
- // children is not an array
- console.log(Array.isArray(children)); // false
- // and it doesn't have the filter method
- console.log(children.filter); // undefined
- // just to compare with arrays, they have the filter method
- console.log([].filter); // function filter() { ... }
- ```
- So one "trick" is to use [`call`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call), which is a way to change the `this` value that a function "sees".
- Just to give a brief explanation, every `function` has a `this` binding ([see here](https://software.codidact.com/posts/277856) for some examples), but that can be changed with `call`:
- ```javascript
- function someFunc() {
- return this;
- }
- // in a browser, "this" is the same as "window"
- console.log(someFunc() == window); // true
- // passing some object to call, it becomes "this"
- let someObject = { id: 1, name: 'Some Object' };
- console.log(someFunc.call(someObject)); // { id: 1, name: 'Some Object' }
- ```
- By using `someFunc.call(someObject)`, I'm saying "call the `someFunc` function, but using `someObject` as the `this` value".
- And that's what is happening in your code: it calls [`Array.prototype.filter`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter), but instead of calling in in the array (`[]`, an empty array), it's calling it on the `children` property (which is a `HTMLCollection` containing all the child nodes of the `ul` element).
- In other words, when you call `filter` in an array:
- ```javascript
- var array = [1, 2, 3];
- array.filter(function(etc...))
- ```
- Inside the `filter` method, `this` refers to the array itself. But if I use `call`:
- ```javascript
- var array = [1, 2, 3];
- array.filter.call(someOtherObject, function(etc...))
- ```
- Then I'm saying that `filter` should consider `someOtherObject` as the `this` value. In your code, the array is `[]` (an empty array), and `someOtherObject` is the `children` nodes of the `ul` element. Hence, `filter` is checking which children nodes of `ul` are `li` tags.
- > `this` is a very complicate thing in JavaScript, I recommend reading [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this) for more info.
- But anyway, I wouldn't use `filter` in this case. IMO, it makes the code more confusing (although many might disagree with me), not to mention that it returns another array (and creating another array just to get its length is a bit overkill IMO). I would do it like this:
- ```java
- // get ul's children
- var children = document.getElementsByClassName('example')[0].children;
- // count how many children are "li"
- var count = 0;
- for (var element of children)
- if (element.nodeName === 'LI')
- count++;
- console.log(count);
- ```
- Of course, if you "know" that the `ul` contains only `li` tags and nothing else, you could simply use `children.length`.
- ---
- # But there's a difference...
- One important difference is that a `HTMLCollection` is a live collection, so it'll change if the DOM is updated. On the other hand, the `NodeList` returned by `querySelectorAll` doesn't change.
- Let's consider this HTML:
- ```html
- <ul class="example">
- <li>item 1</li>
- <li>item 2</li>
- </ul>
- ```
- Now let's see what happens when we count `ul`'s children and then update the DOM:
- ```javascript
- var list = document.querySelectorAll('ul.example > li');
- var children = document.getElementsByClassName('example')[0].children;
- console.log(list.length); // 2
- console.log(children.length); // 2
- // update the DOM, add another li
- var ul = document.querySelector('ul.example');
- ul.appendChild(document.createElement('li'));
- console.log(list.length); // 2
- console.log(children.length); // 3 <--- it's updated!
- ```
- After adding another `li` in the `ul`, the `NodeList` returned by `querySelectorAll` remains unchanged, while the `HTMLCollection` is updated. Depending on what you do with the DOM while counting, choosing one of them might lead to different results.
- # TL;DR
- To count the number of `li` items, just do:
- ```javascript
- console.log(document.querySelectorAll('ul.example > li').length);
- ```
- That's all, no need to complicate with `filter.call` (but I'll explain that too, hang on).
- ---
- # Adapt that to your HTML
- Of course you could change the selector to be more (or less) specific. Ex: if you want any `ul`, you could use `document.querySelectorAll('ul > li')`, or if you want `ul` with specific id, use `document.querySelectorAll('ul#specificId > li')`.
- Just reminding that `>` is a [Child Combinator](https://developer.mozilla.org/en-US/docs/Web/CSS/Child_combinator), which means it gets only the `li` tags that are direct descendants of the `ul`. Example, if you have this HTML:
- ```html
- <ul class="example">
- <li>item 1</li>
- <li>item 2</li>
- <li>
- Item 3:
- <ul>
- <li>sub item 3.1</li>
- <li>sub item 3.2</li>
- </ul>
- </li>
- </ul>
- ```
- Then `document.querySelectorAll('ul.example > li').length` will return `3`, because the selector gets only the `li`'s containing "Item 1", "Item 2" and "Item 3". The tags with "sub item 3.x" won't be returned, because they're not direct descendants of `ul.example`.
- On the other hand, if I do `document.querySelectorAll('ul.example li').length`, it'll return `5`, because now it gets all `li` tags inside `ul.example` (no matter how many depth levels they're in).
- Which one to use will depend on what you mean by "_`li` list items of a given `ul`_" (based on your second code with `filter`, I guess you need `ul.example > li`).
- ---
- # Regarding `undefined`
- [According to MDN](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll#return_value), `querySelectorAll` returns a `NodeList` (even if nothing is found, it still returns an empty one). And its [`length` property](https://developer.mozilla.org/en-US/docs/Web/API/NodeList/length) returns the number of items (zero if it's empty). So there's no way to have `document.querySelectorAll('whatever').length` equals `undefined`.
- What is *probably* happening: when you call `console.log(whatever)` in browser's console (or in Node's console - when you run just `node` in the command line), it prints the value of `whatever` and **it also shows the return of `console.log` function** (which is `undefined`). If that's the case (you're testing in a console), try typing just `document.querySelectorAll(etc...` (without `console.log`) and see that it won't show `undefined` anymore.
- ---
- # Explaining `filter`
- Using `filter`, IMO, is not necessary in this case, as just using `querySelectorAll` (with the correct selector) is enough. But let's analyze your code.
- First, [according to the documentation](https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementsByClassName), `getElementsByClassName` "returns an array-like object" (actually a [`HTMLCollection`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCollection)), which means it can return more than one element (just as `querySelectorAll`).
- Then you get the first element of this `HTMLCollection` (when you do `[0]` on it). As `example` is the `ul`'s class, the search is returning this `ul` inside the collection, and by using `[0]` you got that `ul` element.
- After that, you get the `children` property of this `ul`, which may contain the `li` elements, but [it also can have other elements](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ul). So we need to filter those elements.
- One alternative would be to use `Array.prototype.filter`, but the `children` property is not an array and it doesn't have this method:
- ```javascript
- // get ul's children
- var children = document.getElementsByClassName('example')[0].children;
- // children is not an array
- console.log(Array.isArray(children)); // false
- // and it doesn't have the filter method
- console.log(children.filter); // undefined
- // just to compare with arrays, they have the filter method
- console.log([].filter); // function filter() { ... }
- ```
- So one "trick" is to use [`call`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call), which is a way to change the `this` value that a function "sees".
- Just to give a brief explanation, every `function` has a `this` binding ([see here](https://software.codidact.com/posts/277856) for some examples), but that can be changed with `call`:
- ```javascript
- function someFunc() {
- return this;
- }
- // in a browser, "this" is the same as "window"
- console.log(someFunc() == window); // true
- // passing some object to call, it becomes "this"
- let someObject = { id: 1, name: 'Some Object' };
- console.log(someFunc.call(someObject)); // { id: 1, name: 'Some Object' }
- ```
- By using `someFunc.call(someObject)`, I'm saying "call the `someFunc` function, but using `someObject` as the `this` value".
- And that's what is happening in your code: it calls [`Array.prototype.filter`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter), but instead of calling in in the array (`[]`, an empty array), it's calling it on the `children` property (which is a `HTMLCollection` containing all the child nodes of the `ul` element).
- In other words, when you call `filter` in an array:
- ```javascript
- var array = [1, 2, 3];
- array.filter(function(etc...))
- ```
- Inside the `filter` method, `this` refers to the array itself. But if I use `call`:
- ```javascript
- var array = [1, 2, 3];
- array.filter.call(someOtherObject, function(etc...))
- ```
- Then I'm saying that `filter` should consider `someOtherObject` as the `this` value. In your code, the array is `[]` (an empty array), and `someOtherObject` is the `children` nodes of the `ul` element. Hence, `filter` is checking which children nodes of `ul` are `li` tags.
- > `this` is a very complicate thing in JavaScript, I recommend reading [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this) for more info.
- But anyway, I wouldn't use `filter` in this case. IMO, it makes the code more confusing (although many might disagree with me), not to mention that it returns another array (and creating another array just to get its length is a bit overkill IMO). I would do it like this:
- ```java
- // get ul's children
- var children = document.getElementsByClassName('example')[0].children;
- // count how many children are "li"
- var count = 0;
- for (var element of children)
- if (element.nodeName === 'LI')
- count++;
- console.log(count);
- ```
- Of course, if you "know" that the `ul` contains only `li` tags and nothing else, you could simply use `children.length`.
- ---
- # But there's a difference...
- One important difference is that a `HTMLCollection` is a live collection, so it'll change if the DOM is updated. On the other hand, the `NodeList` returned by `querySelectorAll` doesn't change.
- Let's consider this HTML:
- ```html
- <ul class="example">
- <li>item 1</li>
- <li>item 2</li>
- </ul>
- ```
- Now let's see what happens when we count `ul`'s children and then update the DOM:
- ```javascript
- var list = document.querySelectorAll('ul.example > li');
- var children = document.getElementsByClassName('example')[0].children;
- console.log(list.length); // 2
- console.log(children.length); // 2
- // update the DOM, add another li
- var ul = document.querySelector('ul.example');
- ul.appendChild(document.createElement('li'));
- console.log(list.length); // 2
- console.log(children.length); // 3 <--- it's updated!
- ```
- After adding another `li` in the `ul`, the `NodeList` returned by `querySelectorAll` remains unchanged, while the `HTMLCollection` is updated. Depending on what you do with the DOM while counting, choosing one of them might lead to different results.
#4: Post edited
- # TL;DR
- ```javascript
- console.log(document.querySelectorAll('ul.example > li').length);
- ```
- That's all, no need to complicate with `filter.call`.
- ---
- # Adapt that to your HTML
- Of course you could change the selector to be more (or less) specific. Ex: if you want any `ul`, you could use `document.querySelectorAll('ul > li')`, or if you want `ul` with specific id, use `document.querySelectorAll('ul#specificId > li')`.
- Just reminding that `>` is a [Child Combinator](https://developer.mozilla.org/en-US/docs/Web/CSS/Child_combinator), which means it gets only the `li` tags that are direct descendants of the `ul`. Example, if you have this HTML:
- ```html
- <ul class="example">
- <li>item 1</li>
- <li>item 2</li>
- <li>
- Item 3:
- <ul>
- <li>sub item 3.1</li>
- <li>sub item 3.2</li>
- </ul>
- </li>
- </ul>
- ```
- Then `document.querySelectorAll('ul.example > li').length` will return `3`, because the selector gets only the `li`'s containing "Item 1", "Item 2" and "Item 3". The tags with "sub item 3.x" won't be returned, because they're not direct descendants of `ul.example`.
- On the other hand, if I do `document.querySelectorAll('ul.example li').length`, it'll return `5`, because now it gets all `li` tags inside `ul.example` (no matter how many depth levels they're in).
Which one to use will depend on what you mean by "_`li` list items of a given `ul`_" (based on your second code with `filter`, I guess you meant `ul.example > li`).- ---
- # Regarding `undefined`
- [According to MDN](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll#return_value), `querySelectorAll` returns a `NodeList` (even if nothing is found, it still returns an empty one). And its [`length` property](https://developer.mozilla.org/en-US/docs/Web/API/NodeList/length) returns the number of items (zero if it's empty). So there's no way to have `document.querySelectorAll('whatever').length` equals `undefined`.
What is *probably* happening is that, when you call `console.log(whatever)` in browser's console (or in Node's console - when you run just `node` in the command line), it prints the value of `whatever` and **it also shows the return of `console.log` function** (which is `undefined`). If that's the case, try typing just `document.querySelectorAll(etc...` (without `console.log`) and see that it won't show `undefined` anymore.- ---
# `filter`- Using `filter`, IMO, is not necessary in this case, as just using `querySelectorAll` (with the correct selector) is enough. But let's analyze your code.
- First, [according to the documentation](https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementsByClassName), `getElementsByClassName` "returns an array-like object" (actually a [`HTMLCollection`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCollection)), which means it can return more than one element (just as `querySelectorAll`).
Then you get the first element of this `HTMLCollection` (when you do `[0]` on it). As `example` is the `ul`'s class, the search is returning this `ul` inside the collection, and by using `[0]` you got that `ul` element.After that, you get the `children` property of this `ul`, which may contain `li`, but [it also can have other elements](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ul). So we need to filter those elements.- One alternative would be to use `Array.prototype.filter`, but the `children` property is not an array and it doesn't have this method:
- ```javascript
- // get ul's children
- var children = document.getElementsByClassName('example')[0].children;
- // children is not an array
- console.log(Array.isArray(children)); // false
- // and it doesn't have the filter method
- console.log(children.filter); // undefined
- // just to compare with arrays, they have the filter method
- console.log([].filter); // function filter() { ... }
- ```
- So one "trick" is to use [`call`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call), which is a way to change the `this` value that a function "sees".
Just to give a brief explanation, every `function` has a `this` binding ([see here](https://software.codidact.com/posts/277856) for more info), but that can be changed with `call`:- ```javascript
- function someFunc() {
- return this;
- }
- // in a browser, "this" is the same as "window"
- console.log(someFunc() == window); // true
- // passing some object to call, it becomes "this"
- let someObject = { id: 1, name: 'Some Object' };
- console.log(someFunc.call(someObject)); // { id: 1, name: 'Some Object' }
- ```
- By using `someFunc.call(someObject)`, I'm saying "call the `someFunc` function, but using `someObject` as the `this` value".
- And that's what is happening in your code: it calls [`Array.prototype.filter`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter), but instead of calling in in the array (`[]`, an empty array), it's calling it on the `children` property (which is a `HTMLCollection` containing all the child nodes of the `ul` element).
- In other words, when you call `filter` in an array:
- ```javascript
- var array = [1, 2, 3];
- array.filter(function(etc...))
- ```
- Inside the `filter` method, `this` refers to the array itself. But if I use `call`:
- ```javascript
- var array = [1, 2, 3];
- array.filter.call(someOtherObject, function(etc...))
- ```
- Then I'm saying that `filter` should consider `someOtherObject` as the `this` value. In your code, the array is `[]` (an empty array), and `someOtherObject` is the `children` nodes of the `ul` element. Hence, `filter` is checking which children nodes of `ul` are `li` tags.
- > `this` is a very complicate thing in JavaScript, I recommend reading [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this) for more info.
But anyway, I wouldn't use `filter` in this case. IMO, it makes the code more confusing (although many might disagree with me). I would do it like this:- ```java
- // get ul's children
- var children = document.getElementsByClassName('example')[0].children;
- // count how many children are "li"
- var count = 0;
- for (var element of children)
- if (element.nodeName === 'LI')
- count++;
- console.log(count);
- ```
- Of course, if you "know" that the `ul` contains only `li` tags and nothing else, you could simply use `children.length`.
- ---
- # But there's a difference...
- One important difference is that a `HTMLCollection` is a live collection, so it'll change if the DOM is updated. On the other hand, the `NodeList` returned by `querySelectorAll` doesn't change.
- Let's consider this HTML:
- ```html
- <ul class="example">
- <li>item 1</li>
- <li>item 2</li>
- </ul>
- ```
- Now let's see what happens when we count `ul`'s children and then update the DOM:
- ```javascript
- var list = document.querySelectorAll('ul.example > li');
- var children = document.getElementsByClassName('example')[0].children;
- console.log(list.length); // 2
- console.log(children.length); // 2
- // update the DOM, add another li
- var ul = document.querySelector('ul.example');
- ul.appendChild(document.createElement('li'));
- console.log(list.length); // 2
- console.log(children.length); // 3 <--- it's updated!
- ```
- After adding another `li` in the `ul`, the `NodeList` returned by `querySelectorAll` remains unchanged, while the `HTMLCollection` is updated. Depending on what you do with the DOM while counting, choosing one of them might lead to different results.
- # TL;DR
- To count the number of `li` items, just do:
- ```javascript
- console.log(document.querySelectorAll('ul.example > li').length);
- ```
- That's all, no need to complicate with `filter.call`.
- ---
- # Adapt that to your HTML
- Of course you could change the selector to be more (or less) specific. Ex: if you want any `ul`, you could use `document.querySelectorAll('ul > li')`, or if you want `ul` with specific id, use `document.querySelectorAll('ul#specificId > li')`.
- Just reminding that `>` is a [Child Combinator](https://developer.mozilla.org/en-US/docs/Web/CSS/Child_combinator), which means it gets only the `li` tags that are direct descendants of the `ul`. Example, if you have this HTML:
- ```html
- <ul class="example">
- <li>item 1</li>
- <li>item 2</li>
- <li>
- Item 3:
- <ul>
- <li>sub item 3.1</li>
- <li>sub item 3.2</li>
- </ul>
- </li>
- </ul>
- ```
- Then `document.querySelectorAll('ul.example > li').length` will return `3`, because the selector gets only the `li`'s containing "Item 1", "Item 2" and "Item 3". The tags with "sub item 3.x" won't be returned, because they're not direct descendants of `ul.example`.
- On the other hand, if I do `document.querySelectorAll('ul.example li').length`, it'll return `5`, because now it gets all `li` tags inside `ul.example` (no matter how many depth levels they're in).
- Which one to use will depend on what you mean by "_`li` list items of a given `ul`_" (based on your second code with `filter`, I guess you need `ul.example > li`).
- ---
- # Regarding `undefined`
- [According to MDN](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll#return_value), `querySelectorAll` returns a `NodeList` (even if nothing is found, it still returns an empty one). And its [`length` property](https://developer.mozilla.org/en-US/docs/Web/API/NodeList/length) returns the number of items (zero if it's empty). So there's no way to have `document.querySelectorAll('whatever').length` equals `undefined`.
- What is *probably* happening: when you call `console.log(whatever)` in browser's console (or in Node's console - when you run just `node` in the command line), it prints the value of `whatever` and **it also shows the return of `console.log` function** (which is `undefined`). If that's the case (you're testing in a console), try typing just `document.querySelectorAll(etc...` (without `console.log`) and see that it won't show `undefined` anymore.
- ---
- # Explaining `filter`
- Using `filter`, IMO, is not necessary in this case, as just using `querySelectorAll` (with the correct selector) is enough. But let's analyze your code.
- First, [according to the documentation](https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementsByClassName), `getElementsByClassName` "returns an array-like object" (actually a [`HTMLCollection`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCollection)), which means it can return more than one element (just as `querySelectorAll`).
- Then you get the first element of this `HTMLCollection` (when you do `[0]` on it). As `example` is the `ul`'s class, the search is returning this `ul` inside the collection, and by using `[0]` you got that `ul` element.
- After that, you get the `children` property of this `ul`, which may contain the `li` elements, but [it also can have other elements](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ul). So we need to filter those elements.
- One alternative would be to use `Array.prototype.filter`, but the `children` property is not an array and it doesn't have this method:
- ```javascript
- // get ul's children
- var children = document.getElementsByClassName('example')[0].children;
- // children is not an array
- console.log(Array.isArray(children)); // false
- // and it doesn't have the filter method
- console.log(children.filter); // undefined
- // just to compare with arrays, they have the filter method
- console.log([].filter); // function filter() { ... }
- ```
- So one "trick" is to use [`call`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call), which is a way to change the `this` value that a function "sees".
- Just to give a brief explanation, every `function` has a `this` binding ([see here](https://software.codidact.com/posts/277856) for some examples), but that can be changed with `call`:
- ```javascript
- function someFunc() {
- return this;
- }
- // in a browser, "this" is the same as "window"
- console.log(someFunc() == window); // true
- // passing some object to call, it becomes "this"
- let someObject = { id: 1, name: 'Some Object' };
- console.log(someFunc.call(someObject)); // { id: 1, name: 'Some Object' }
- ```
- By using `someFunc.call(someObject)`, I'm saying "call the `someFunc` function, but using `someObject` as the `this` value".
- And that's what is happening in your code: it calls [`Array.prototype.filter`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter), but instead of calling in in the array (`[]`, an empty array), it's calling it on the `children` property (which is a `HTMLCollection` containing all the child nodes of the `ul` element).
- In other words, when you call `filter` in an array:
- ```javascript
- var array = [1, 2, 3];
- array.filter(function(etc...))
- ```
- Inside the `filter` method, `this` refers to the array itself. But if I use `call`:
- ```javascript
- var array = [1, 2, 3];
- array.filter.call(someOtherObject, function(etc...))
- ```
- Then I'm saying that `filter` should consider `someOtherObject` as the `this` value. In your code, the array is `[]` (an empty array), and `someOtherObject` is the `children` nodes of the `ul` element. Hence, `filter` is checking which children nodes of `ul` are `li` tags.
- > `this` is a very complicate thing in JavaScript, I recommend reading [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this) for more info.
- But anyway, I wouldn't use `filter` in this case. IMO, it makes the code more confusing (although many might disagree with me), not to mention that it returns another array (and creating another array just to get its length is a bit overkill IMO). I would do it like this:
- ```java
- // get ul's children
- var children = document.getElementsByClassName('example')[0].children;
- // count how many children are "li"
- var count = 0;
- for (var element of children)
- if (element.nodeName === 'LI')
- count++;
- console.log(count);
- ```
- Of course, if you "know" that the `ul` contains only `li` tags and nothing else, you could simply use `children.length`.
- ---
- # But there's a difference...
- One important difference is that a `HTMLCollection` is a live collection, so it'll change if the DOM is updated. On the other hand, the `NodeList` returned by `querySelectorAll` doesn't change.
- Let's consider this HTML:
- ```html
- <ul class="example">
- <li>item 1</li>
- <li>item 2</li>
- </ul>
- ```
- Now let's see what happens when we count `ul`'s children and then update the DOM:
- ```javascript
- var list = document.querySelectorAll('ul.example > li');
- var children = document.getElementsByClassName('example')[0].children;
- console.log(list.length); // 2
- console.log(children.length); // 2
- // update the DOM, add another li
- var ul = document.querySelector('ul.example');
- ul.appendChild(document.createElement('li'));
- console.log(list.length); // 2
- console.log(children.length); // 3 <--- it's updated!
- ```
- After adding another `li` in the `ul`, the `NodeList` returned by `querySelectorAll` remains unchanged, while the `HTMLCollection` is updated. Depending on what you do with the DOM while counting, choosing one of them might lead to different results.
#3: Post edited
- # TL;DR
- ```javascript
- console.log(document.querySelectorAll('ul.example > li').length);
- ```
- That's all, no need to complicate with `filter.call`.
- ---
- # Adapt that to your HTML
- Of course you could change the selector to be more (or less) specific. Ex: if you want any `ul`, you could use `document.querySelectorAll('ul > li')`, or if you want `ul` with specific id, use `document.querySelectorAll('ul#specificId > li')`.
- Just reminding that `>` is a [Child Combinator](https://developer.mozilla.org/en-US/docs/Web/CSS/Child_combinator), which means it gets only the `li` tags that are direct descendants of the `ul`. Example, if you have this HTML:
- ```html
- <ul class="example">
- <li>item 1</li>
- <li>item 2</li>
- <li>
- Item 3:
- <ul>
- <li>sub item 3.1</li>
- <li>sub item 3.2</li>
- </ul>
- </li>
- </ul>
- ```
- Then `document.querySelectorAll('ul.example > li').length` will return `3`, because the selector gets only the `li`'s containing "Item 1", "Item 2" and "Item 3". The tags with "sub item 3.x" won't be returned, because they're not direct descendants of `ul.example`.
- On the other hand, if I do `document.querySelectorAll('ul.example li').length`, it'll return `5`, because now it gets all `li` tags inside `ul.example` (no matter how many depth levels they're in).
- Which one to use will depend on what you mean by "_`li` list items of a given `ul`_" (based on your second code with `filter`, I guess you meant `ul.example > li`).
- ---
- # Regarding `undefined`
- [According to MDN](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll#return_value), `querySelectorAll` returns a `NodeList` (even if nothing is found, it still returns an empty one). And its [`length` property](https://developer.mozilla.org/en-US/docs/Web/API/NodeList/length) returns the number of items (zero if it's empty). So there's no way to have `document.querySelectorAll('whatever').length` equals `undefined`.
- What is *probably* happening is that, when you call `console.log(whatever)` in browser's console (or in Node's console - when you run just `node` in the command line), it prints the value of `whatever` and **it also shows the return of `console.log` function** (which is `undefined`). If that's the case, try typing just `document.querySelectorAll(etc...` (without `console.log`) and see that it won't show `undefined` anymore.
- ---
- # `filter`
- Using `filter`, IMO, is not necessary in this case, as just using `querySelectorAll` (with the correct selector) is enough. But let's analyze your code.
- First, [according to the documentation](https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementsByClassName), `getElementsByClassName` "returns an array-like object" (actually a [`HTMLCollection`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCollection)), which means it can return more than one element (just as `querySelectorAll`).
- Then you get the first element of this `HTMLCollection` (when you do `[0]` on it). As `example` is the `ul`'s class, the search is returning this `ul` inside the collection, and by using `[0]` you got that `ul` element.
- After that, you get the `children` property of this `ul`, which may contain `li`, but [it also can have other elements](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ul). So we need to filter those elements.
- One alternative would be to use `Array.prototype.filter`, but the `children` property is not an array and it doesn't have this method:
- ```javascript
- // get ul's children
- var children = document.getElementsByClassName('example')[0].children;
- // children is not an array
- console.log(Array.isArray(children)); // false
- // and it doesn't have the filter method
- console.log(children.filter); // undefined
- // just to compare with arrays, they have the filter method
- console.log([].filter); // function filter() { ... }
- ```
- So one "trick" is to use [`call`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call), which is a way to change the `this` value that a function "sees".
- Just to give a brief explanation, every `function` has a `this` binding ([see here](https://software.codidact.com/posts/277856) for more info), but that can be changed with `call`:
- ```javascript
- function someFunc() {
- return this;
- }
- // in a browser, "this" is the same as "window"
- console.log(someFunc() == window); // true
- // passing some object to call, it becomes "this"
- let someObject = { id: 1, name: 'Some Object' };
- console.log(someFunc.call(someObject)); // { id: 1, name: 'Some Object' }
- ```
- By using `someFunc.call(someObject)`, I'm saying "call the `someFunc` function, but using `someObject` as the `this` value".
- And that's what is happening in your code: it calls [`Array.prototype.filter`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter), but instead of calling in in the array (`[]`, an empty array), it's calling it on the `children` property (which is a `HTMLCollection` containing all the child nodes of the `ul` element).
> `this` is a complicate thing in JavaScript, I recommend reading [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this) for more info.- But anyway, I wouldn't use `filter` in this case. IMO, it makes the code more confusing (although many might disagree with me). I would do it like this:
- ```java
- // get ul's children
- var children = document.getElementsByClassName('example')[0].children;
- // count how many children are "li"
- var count = 0;
- for (var element of children)
- if (element.nodeName === 'LI')
- count++;
- console.log(count);
- ```
- Of course, if you "know" that the `ul` contains only `li` tags and nothing else, you could simply use `children.length`.
- ---
- # But there's a difference...
- One important difference is that a `HTMLCollection` is a live collection, so it'll change if the DOM is updated. On the other hand, the `NodeList` returned by `querySelectorAll` doesn't change.
- Let's consider this HTML:
- ```html
- <ul class="example">
- <li>item 1</li>
- <li>item 2</li>
- </ul>
- ```
- Now let's see what happens when we count `ul`'s children and then update the DOM:
- ```javascript
- var list = document.querySelectorAll('ul.example > li');
- var children = document.getElementsByClassName('example')[0].children;
- console.log(list.length); // 2
- console.log(children.length); // 2
- // update the DOM, add another li
- var ul = document.querySelector('ul.example');
- ul.appendChild(document.createElement('li'));
- console.log(list.length); // 2
- console.log(children.length); // 3 <--- it's updated!
- ```
- After adding another `li` in the `ul`, the `NodeList` returned by `querySelectorAll` remains unchanged, while the `HTMLCollection` is updated. Depending on what you do with the DOM while counting, choosing one of them might lead to different results.
- # TL;DR
- ```javascript
- console.log(document.querySelectorAll('ul.example > li').length);
- ```
- That's all, no need to complicate with `filter.call`.
- ---
- # Adapt that to your HTML
- Of course you could change the selector to be more (or less) specific. Ex: if you want any `ul`, you could use `document.querySelectorAll('ul > li')`, or if you want `ul` with specific id, use `document.querySelectorAll('ul#specificId > li')`.
- Just reminding that `>` is a [Child Combinator](https://developer.mozilla.org/en-US/docs/Web/CSS/Child_combinator), which means it gets only the `li` tags that are direct descendants of the `ul`. Example, if you have this HTML:
- ```html
- <ul class="example">
- <li>item 1</li>
- <li>item 2</li>
- <li>
- Item 3:
- <ul>
- <li>sub item 3.1</li>
- <li>sub item 3.2</li>
- </ul>
- </li>
- </ul>
- ```
- Then `document.querySelectorAll('ul.example > li').length` will return `3`, because the selector gets only the `li`'s containing "Item 1", "Item 2" and "Item 3". The tags with "sub item 3.x" won't be returned, because they're not direct descendants of `ul.example`.
- On the other hand, if I do `document.querySelectorAll('ul.example li').length`, it'll return `5`, because now it gets all `li` tags inside `ul.example` (no matter how many depth levels they're in).
- Which one to use will depend on what you mean by "_`li` list items of a given `ul`_" (based on your second code with `filter`, I guess you meant `ul.example > li`).
- ---
- # Regarding `undefined`
- [According to MDN](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll#return_value), `querySelectorAll` returns a `NodeList` (even if nothing is found, it still returns an empty one). And its [`length` property](https://developer.mozilla.org/en-US/docs/Web/API/NodeList/length) returns the number of items (zero if it's empty). So there's no way to have `document.querySelectorAll('whatever').length` equals `undefined`.
- What is *probably* happening is that, when you call `console.log(whatever)` in browser's console (or in Node's console - when you run just `node` in the command line), it prints the value of `whatever` and **it also shows the return of `console.log` function** (which is `undefined`). If that's the case, try typing just `document.querySelectorAll(etc...` (without `console.log`) and see that it won't show `undefined` anymore.
- ---
- # `filter`
- Using `filter`, IMO, is not necessary in this case, as just using `querySelectorAll` (with the correct selector) is enough. But let's analyze your code.
- First, [according to the documentation](https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementsByClassName), `getElementsByClassName` "returns an array-like object" (actually a [`HTMLCollection`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCollection)), which means it can return more than one element (just as `querySelectorAll`).
- Then you get the first element of this `HTMLCollection` (when you do `[0]` on it). As `example` is the `ul`'s class, the search is returning this `ul` inside the collection, and by using `[0]` you got that `ul` element.
- After that, you get the `children` property of this `ul`, which may contain `li`, but [it also can have other elements](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ul). So we need to filter those elements.
- One alternative would be to use `Array.prototype.filter`, but the `children` property is not an array and it doesn't have this method:
- ```javascript
- // get ul's children
- var children = document.getElementsByClassName('example')[0].children;
- // children is not an array
- console.log(Array.isArray(children)); // false
- // and it doesn't have the filter method
- console.log(children.filter); // undefined
- // just to compare with arrays, they have the filter method
- console.log([].filter); // function filter() { ... }
- ```
- So one "trick" is to use [`call`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call), which is a way to change the `this` value that a function "sees".
- Just to give a brief explanation, every `function` has a `this` binding ([see here](https://software.codidact.com/posts/277856) for more info), but that can be changed with `call`:
- ```javascript
- function someFunc() {
- return this;
- }
- // in a browser, "this" is the same as "window"
- console.log(someFunc() == window); // true
- // passing some object to call, it becomes "this"
- let someObject = { id: 1, name: 'Some Object' };
- console.log(someFunc.call(someObject)); // { id: 1, name: 'Some Object' }
- ```
- By using `someFunc.call(someObject)`, I'm saying "call the `someFunc` function, but using `someObject` as the `this` value".
- And that's what is happening in your code: it calls [`Array.prototype.filter`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter), but instead of calling in in the array (`[]`, an empty array), it's calling it on the `children` property (which is a `HTMLCollection` containing all the child nodes of the `ul` element).
- In other words, when you call `filter` in an array:
- ```javascript
- var array = [1, 2, 3];
- array.filter(function(etc...))
- ```
- Inside the `filter` method, `this` refers to the array itself. But if I use `call`:
- ```javascript
- var array = [1, 2, 3];
- array.filter.call(someOtherObject, function(etc...))
- ```
- Then I'm saying that `filter` should consider `someOtherObject` as the `this` value. In your code, the array is `[]` (an empty array), and `someOtherObject` is the `children` nodes of the `ul` element. Hence, `filter` is checking which children nodes of `ul` are `li` tags.
- > `this` is a very complicate thing in JavaScript, I recommend reading [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this) for more info.
- But anyway, I wouldn't use `filter` in this case. IMO, it makes the code more confusing (although many might disagree with me). I would do it like this:
- ```java
- // get ul's children
- var children = document.getElementsByClassName('example')[0].children;
- // count how many children are "li"
- var count = 0;
- for (var element of children)
- if (element.nodeName === 'LI')
- count++;
- console.log(count);
- ```
- Of course, if you "know" that the `ul` contains only `li` tags and nothing else, you could simply use `children.length`.
- ---
- # But there's a difference...
- One important difference is that a `HTMLCollection` is a live collection, so it'll change if the DOM is updated. On the other hand, the `NodeList` returned by `querySelectorAll` doesn't change.
- Let's consider this HTML:
- ```html
- <ul class="example">
- <li>item 1</li>
- <li>item 2</li>
- </ul>
- ```
- Now let's see what happens when we count `ul`'s children and then update the DOM:
- ```javascript
- var list = document.querySelectorAll('ul.example > li');
- var children = document.getElementsByClassName('example')[0].children;
- console.log(list.length); // 2
- console.log(children.length); // 2
- // update the DOM, add another li
- var ul = document.querySelector('ul.example');
- ul.appendChild(document.createElement('li'));
- console.log(list.length); // 2
- console.log(children.length); // 3 <--- it's updated!
- ```
- After adding another `li` in the `ul`, the `NodeList` returned by `querySelectorAll` remains unchanged, while the `HTMLCollection` is updated. Depending on what you do with the DOM while counting, choosing one of them might lead to different results.
#2: Post edited
- # TL;DR
- ```javascript
console.log(document.querySelectorAll('ul > li').length);- ```
- That's all, no need to complicate with `filter.call`.
- ---
- # Adapt that to your HTML
Of course you could change the selector to be more specific. Ex: if you want a `ul` that has a specific class or id, you could change to `document.querySelectorAll('ul.specificClass > li')` or `document.querySelectorAll('ul#specificId > li')`.In your case, *it seems* that `example` is the class of the `ul` tag, so it should be:```javascriptconsole.log(document.querySelectorAll('ul.example > li').length);```- Just reminding that `>` is a [Child Combinator](https://developer.mozilla.org/en-US/docs/Web/CSS/Child_combinator), which means it gets only the `li` tags that are direct descendants of the `ul`. Example, if you have this HTML:
- ```html
- <ul class="example">
- <li>item 1</li>
- <li>item 2</li>
- <li>
- Item 3:
- <ul>
- <li>sub item 3.1</li>
- <li>sub item 3.2</li>
- </ul>
- </li>
- </ul>
- ```
Then `document.querySelectorAll('ul.example > li').length` will return `3`, because the selector gets only the ones containing "Item 1", "Item 2" and "Item 3". The tags with "sub item 3.x" won't be returned, because they're not direct descendants of `ul.example`.- On the other hand, if I do `document.querySelectorAll('ul.example li').length`, it'll return `5`, because now it gets all `li` tags inside `ul.example` (no matter how many depth levels they're in).
- Which one to use will depend on what you mean by "_`li` list items of a given `ul`_" (based on your second code with `filter`, I guess you meant `ul.example > li`).
- ---
- # Regarding `undefined`
- [According to MDN](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll#return_value), `querySelectorAll` returns a `NodeList` (even if nothing is found, it still returns an empty one). And its [`length` property](https://developer.mozilla.org/en-US/docs/Web/API/NodeList/length) returns the number of items (zero if it's empty). So there's no way to have `document.querySelectorAll('whatever').length` equals `undefined`.
- What is *probably* happening is that, when you call `console.log(whatever)` in browser's console (or in Node's console - when you run just `node` in the command line), it prints the value of `whatever` and **it also shows the return of `console.log` function** (which is `undefined`). If that's the case, try typing just `document.querySelectorAll(etc...` (without `console.log`) and see that it won't show `undefined` anymore.
- ---
- # `filter`
- Using `filter`, IMO, is not necessary in this case, as just using `querySelectorAll` (with the correct selector) is enough. But let's analyze your code.
- First, [according to the documentation](https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementsByClassName), `getElementsByClassName` "returns an array-like object" (actually a [`HTMLCollection`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCollection)), which means it can return more than one element (just as `querySelectorAll`).
Then you get the first element of this `HTMLCollection` (when you do `[0]` on it). That's why _it seems_ that `example` is the `ul`'s class (you've searched for all elements that have this class, and got the first one).- After that, you get the `children` property of this `ul`, which may contain `li`, but [it also can have other elements](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ul). So we need to filter those elements.
- One alternative would be to use `Array.prototype.filter`, but the `children` property is not an array and it doesn't have this method:
- ```javascript
- var children = document.getElementsByClassName('example')[0].children;
- // children is not an array
- console.log(Array.isArray(children)); // false
- // and it doesn't have the filter method
- console.log(children.filter); // undefined
- // just to compare with arrays, they have the filter method
- console.log([].filter); // function filter() { ... }
- ```
- So one "trick" is to use [`call`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call), which is a way to change the `this` value that a function "sees".
- Just to give a brief explanation, every `function` has a `this` binding ([see here](https://software.codidact.com/posts/277856) for more info), but that can be changed with `call`:
- ```javascript
- function someFunc() {
- return this;
- }
- // in a browser, "this" is the same as "window"
- console.log(someFunc() == window); // true
- // passing some object to call, it becomes "this"
- let someObject = { id: 1, name: 'Some Object' };
- console.log(someFunc.call(someObject)); // { id: 1, name: 'Some Object' }
- ```
- By using `someFunc.call(someObject)`, I'm saying "call the `someFunc` function, but using `someObject` as the `this` value".
- And that's what is happening in your code: it calls [`Array.prototype.filter`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter), but instead of calling in in the array (`[]`, an empty array), it's calling it on the `children` property (which is a `HTMLCollection` containing all the child nodes of the `ul` element).
But anyway, I wouldn't use `filter` in this case. IMO, it makes the code more confusing. I would do it like this:- ```java
- var children = document.getElementsByClassName('example')[0].children;
- var count = 0;
- for (var element of children)
- if (element.nodeName === 'LI')
- count++;
- console.log(count);
- ```
- Of course, if you "know" that the `ul` contains only `li` tags and nothing else, you could simply use `children.length`.
- ---
- # But there's a difference...
- One important difference is that a `HTMLCollection` is a live collection, so it'll change if the DOM is updated. On the other hand, the `NodeList` returned by `querySelectorAll` doesn't change.
- Let's consider this HTML:
- ```html
- <ul class="example">
- <li>item 1</li>
- <li>item 2</li>
- </ul>
- ```
- Now let's see what happens when we count `ul`'s children and then update the DOM:
- ```javascript
- var list = document.querySelectorAll('ul.example > li');
- var children = document.getElementsByClassName('example')[0].children;
- console.log(list.length); // 2
- console.log(children.length); // 2
- // update the DOM, add another li
- var ul = document.querySelector('ul.example');
- ul.appendChild(document.createElement('li'));
- console.log(list.length); // 2
- console.log(children.length); // 3 <--- it's updated!
- ```
- After adding another `li` in the `ul`, the `NodeList` returned by `querySelectorAll` remains unchanged, while the `HTMLCollection` is updated. Depending on what you do with the DOM while counting, choosing one of them might lead to different results.
- # TL;DR
- ```javascript
- console.log(document.querySelectorAll('ul.example > li').length);
- ```
- That's all, no need to complicate with `filter.call`.
- ---
- # Adapt that to your HTML
- Of course you could change the selector to be more (or less) specific. Ex: if you want any `ul`, you could use `document.querySelectorAll('ul > li')`, or if you want `ul` with specific id, use `document.querySelectorAll('ul#specificId > li')`.
- Just reminding that `>` is a [Child Combinator](https://developer.mozilla.org/en-US/docs/Web/CSS/Child_combinator), which means it gets only the `li` tags that are direct descendants of the `ul`. Example, if you have this HTML:
- ```html
- <ul class="example">
- <li>item 1</li>
- <li>item 2</li>
- <li>
- Item 3:
- <ul>
- <li>sub item 3.1</li>
- <li>sub item 3.2</li>
- </ul>
- </li>
- </ul>
- ```
- Then `document.querySelectorAll('ul.example > li').length` will return `3`, because the selector gets only the `li`'s containing "Item 1", "Item 2" and "Item 3". The tags with "sub item 3.x" won't be returned, because they're not direct descendants of `ul.example`.
- On the other hand, if I do `document.querySelectorAll('ul.example li').length`, it'll return `5`, because now it gets all `li` tags inside `ul.example` (no matter how many depth levels they're in).
- Which one to use will depend on what you mean by "_`li` list items of a given `ul`_" (based on your second code with `filter`, I guess you meant `ul.example > li`).
- ---
- # Regarding `undefined`
- [According to MDN](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll#return_value), `querySelectorAll` returns a `NodeList` (even if nothing is found, it still returns an empty one). And its [`length` property](https://developer.mozilla.org/en-US/docs/Web/API/NodeList/length) returns the number of items (zero if it's empty). So there's no way to have `document.querySelectorAll('whatever').length` equals `undefined`.
- What is *probably* happening is that, when you call `console.log(whatever)` in browser's console (or in Node's console - when you run just `node` in the command line), it prints the value of `whatever` and **it also shows the return of `console.log` function** (which is `undefined`). If that's the case, try typing just `document.querySelectorAll(etc...` (without `console.log`) and see that it won't show `undefined` anymore.
- ---
- # `filter`
- Using `filter`, IMO, is not necessary in this case, as just using `querySelectorAll` (with the correct selector) is enough. But let's analyze your code.
- First, [according to the documentation](https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementsByClassName), `getElementsByClassName` "returns an array-like object" (actually a [`HTMLCollection`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCollection)), which means it can return more than one element (just as `querySelectorAll`).
- Then you get the first element of this `HTMLCollection` (when you do `[0]` on it). As `example` is the `ul`'s class, the search is returning this `ul` inside the collection, and by using `[0]` you got that `ul` element.
- After that, you get the `children` property of this `ul`, which may contain `li`, but [it also can have other elements](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ul). So we need to filter those elements.
- One alternative would be to use `Array.prototype.filter`, but the `children` property is not an array and it doesn't have this method:
- ```javascript
- // get ul's children
- var children = document.getElementsByClassName('example')[0].children;
- // children is not an array
- console.log(Array.isArray(children)); // false
- // and it doesn't have the filter method
- console.log(children.filter); // undefined
- // just to compare with arrays, they have the filter method
- console.log([].filter); // function filter() { ... }
- ```
- So one "trick" is to use [`call`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call), which is a way to change the `this` value that a function "sees".
- Just to give a brief explanation, every `function` has a `this` binding ([see here](https://software.codidact.com/posts/277856) for more info), but that can be changed with `call`:
- ```javascript
- function someFunc() {
- return this;
- }
- // in a browser, "this" is the same as "window"
- console.log(someFunc() == window); // true
- // passing some object to call, it becomes "this"
- let someObject = { id: 1, name: 'Some Object' };
- console.log(someFunc.call(someObject)); // { id: 1, name: 'Some Object' }
- ```
- By using `someFunc.call(someObject)`, I'm saying "call the `someFunc` function, but using `someObject` as the `this` value".
- And that's what is happening in your code: it calls [`Array.prototype.filter`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter), but instead of calling in in the array (`[]`, an empty array), it's calling it on the `children` property (which is a `HTMLCollection` containing all the child nodes of the `ul` element).
- > `this` is a complicate thing in JavaScript, I recommend reading [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this) for more info.
- But anyway, I wouldn't use `filter` in this case. IMO, it makes the code more confusing (although many might disagree with me). I would do it like this:
- ```java
- // get ul's children
- var children = document.getElementsByClassName('example')[0].children;
- // count how many children are "li"
- var count = 0;
- for (var element of children)
- if (element.nodeName === 'LI')
- count++;
- console.log(count);
- ```
- Of course, if you "know" that the `ul` contains only `li` tags and nothing else, you could simply use `children.length`.
- ---
- # But there's a difference...
- One important difference is that a `HTMLCollection` is a live collection, so it'll change if the DOM is updated. On the other hand, the `NodeList` returned by `querySelectorAll` doesn't change.
- Let's consider this HTML:
- ```html
- <ul class="example">
- <li>item 1</li>
- <li>item 2</li>
- </ul>
- ```
- Now let's see what happens when we count `ul`'s children and then update the DOM:
- ```javascript
- var list = document.querySelectorAll('ul.example > li');
- var children = document.getElementsByClassName('example')[0].children;
- console.log(list.length); // 2
- console.log(children.length); // 2
- // update the DOM, add another li
- var ul = document.querySelector('ul.example');
- ul.appendChild(document.createElement('li'));
- console.log(list.length); // 2
- console.log(children.length); // 3 <--- it's updated!
- ```
- After adding another `li` in the `ul`, the `NodeList` returned by `querySelectorAll` remains unchanged, while the `HTMLCollection` is updated. Depending on what you do with the DOM while counting, choosing one of them might lead to different results.
#1: Initial revision
# TL;DR ```javascript console.log(document.querySelectorAll('ul > li').length); ``` That's all, no need to complicate with `filter.call`. --- # Adapt that to your HTML Of course you could change the selector to be more specific. Ex: if you want a `ul` that has a specific class or id, you could change to `document.querySelectorAll('ul.specificClass > li')` or `document.querySelectorAll('ul#specificId > li')`. In your case, *it seems* that `example` is the class of the `ul` tag, so it should be: ```javascript console.log(document.querySelectorAll('ul.example > li').length); ``` Just reminding that `>` is a [Child Combinator](https://developer.mozilla.org/en-US/docs/Web/CSS/Child_combinator), which means it gets only the `li` tags that are direct descendants of the `ul`. Example, if you have this HTML: ```html <ul class="example"> <li>item 1</li> <li>item 2</li> <li> Item 3: <ul> <li>sub item 3.1</li> <li>sub item 3.2</li> </ul> </li> </ul> ``` Then `document.querySelectorAll('ul.example > li').length` will return `3`, because the selector gets only the ones containing "Item 1", "Item 2" and "Item 3". The tags with "sub item 3.x" won't be returned, because they're not direct descendants of `ul.example`. On the other hand, if I do `document.querySelectorAll('ul.example li').length`, it'll return `5`, because now it gets all `li` tags inside `ul.example` (no matter how many depth levels they're in). Which one to use will depend on what you mean by "_`li` list items of a given `ul`_" (based on your second code with `filter`, I guess you meant `ul.example > li`). --- # Regarding `undefined` [According to MDN](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll#return_value), `querySelectorAll` returns a `NodeList` (even if nothing is found, it still returns an empty one). And its [`length` property](https://developer.mozilla.org/en-US/docs/Web/API/NodeList/length) returns the number of items (zero if it's empty). So there's no way to have `document.querySelectorAll('whatever').length` equals `undefined`. What is *probably* happening is that, when you call `console.log(whatever)` in browser's console (or in Node's console - when you run just `node` in the command line), it prints the value of `whatever` and **it also shows the return of `console.log` function** (which is `undefined`). If that's the case, try typing just `document.querySelectorAll(etc...` (without `console.log`) and see that it won't show `undefined` anymore. --- # `filter` Using `filter`, IMO, is not necessary in this case, as just using `querySelectorAll` (with the correct selector) is enough. But let's analyze your code. First, [according to the documentation](https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementsByClassName), `getElementsByClassName` "returns an array-like object" (actually a [`HTMLCollection`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCollection)), which means it can return more than one element (just as `querySelectorAll`). Then you get the first element of this `HTMLCollection` (when you do `[0]` on it). That's why _it seems_ that `example` is the `ul`'s class (you've searched for all elements that have this class, and got the first one). After that, you get the `children` property of this `ul`, which may contain `li`, but [it also can have other elements](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ul). So we need to filter those elements. One alternative would be to use `Array.prototype.filter`, but the `children` property is not an array and it doesn't have this method: ```javascript var children = document.getElementsByClassName('example')[0].children; // children is not an array console.log(Array.isArray(children)); // false // and it doesn't have the filter method console.log(children.filter); // undefined // just to compare with arrays, they have the filter method console.log([].filter); // function filter() { ... } ``` So one "trick" is to use [`call`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call), which is a way to change the `this` value that a function "sees". Just to give a brief explanation, every `function` has a `this` binding ([see here](https://software.codidact.com/posts/277856) for more info), but that can be changed with `call`: ```javascript function someFunc() { return this; } // in a browser, "this" is the same as "window" console.log(someFunc() == window); // true // passing some object to call, it becomes "this" let someObject = { id: 1, name: 'Some Object' }; console.log(someFunc.call(someObject)); // { id: 1, name: 'Some Object' } ``` By using `someFunc.call(someObject)`, I'm saying "call the `someFunc` function, but using `someObject` as the `this` value". And that's what is happening in your code: it calls [`Array.prototype.filter`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter), but instead of calling in in the array (`[]`, an empty array), it's calling it on the `children` property (which is a `HTMLCollection` containing all the child nodes of the `ul` element). But anyway, I wouldn't use `filter` in this case. IMO, it makes the code more confusing. I would do it like this: ```java var children = document.getElementsByClassName('example')[0].children; var count = 0; for (var element of children) if (element.nodeName === 'LI') count++; console.log(count); ``` Of course, if you "know" that the `ul` contains only `li` tags and nothing else, you could simply use `children.length`. --- # But there's a difference... One important difference is that a `HTMLCollection` is a live collection, so it'll change if the DOM is updated. On the other hand, the `NodeList` returned by `querySelectorAll` doesn't change. Let's consider this HTML: ```html <ul class="example"> <li>item 1</li> <li>item 2</li> </ul> ``` Now let's see what happens when we count `ul`'s children and then update the DOM: ```javascript var list = document.querySelectorAll('ul.example > li'); var children = document.getElementsByClassName('example')[0].children; console.log(list.length); // 2 console.log(children.length); // 2 // update the DOM, add another li var ul = document.querySelector('ul.example'); ul.appendChild(document.createElement('li')); console.log(list.length); // 2 console.log(children.length); // 3 <--- it's updated! ``` After adding another `li` in the `ul`, the `NodeList` returned by `querySelectorAll` remains unchanged, while the `HTMLCollection` is updated. Depending on what you do with the DOM while counting, choosing one of them might lead to different results.