Functional JS w/Recursive Functions - javascript

Alright, I realize this is probably very beginner-ish for many of you on here, but I'm hoping someone can explain this in a way I can wrap my head around. My question centers around what I've heard is the basis for functional JavaScript - recursion. I was working on a personal project, and found a great use case for one, but I still don't quite get what's going on - need a visual way of thinking through it.
So, here's an example. The problem that's being solved is a simple helper function to find the next sibling in the DOM matching a specific tagname (assume the current element and tagname are passed in when calling the function, i.e. findSibling(this, 'DIV')).
var findSibling = function(el, tagName) {
if (el.nextSibling.tagName === tagName.toUpperCase())
return el.nextSibling;
else
return findSibling(el.nextSibling, tagName);
}
Ok, so this works! Great! But, it took me forever to land here, and it really shouldn't have. I tried whiteboarding it out, the best I can understand is that something like this is happening:
findSibling(<span>, div) ▸ findSibling(<span>, div) ▸ findSibling(<span>, div) ▸ <div>
Assuming we have HTML something like this:
<div></div>
<span></span>
<span></span>
<span></span>
<div></div>
Can anyone help me visualize this a bit more? Any tips/tricks that you may have used when first learning this concept? I'm just looking for that lightbulb...
Also, the one thing I was stuck on for awhile was the second return statement. Why can't I just call the function in the else? Why do I need the return? Seems like it would just call the function with the sibling element.
Thank you!

To explain your second question first, let's rewrite your function a little:
var findSibling = function(el, tagName) {
var match;
if (el.nextSibling.tagName === tagName.toUpperCase()) {
match = el.nextSibling;
} else {
match = findSibling(el.nextSibling, tagName);
}
return match;
}
Both returns in your original code are doing the same thing, returning the tag you're searching for. What's different in each case is how that match is calculated.
To answer your first question, let's look at your code in a different way. Anytime you have a function, you can always replace the function call with the code of the function, with parameters properly replaced. For example:
function hello(text) {
alert('Hello ' + text);
}
hello('to you.');
is the equivalent to
alert('Hello to you.');
So let's do this with your recursive function:
if (el.nextSibling.tagName === tagName.toUpperCase())
return el.nextSibling;
else
if (el.nextSibling.nextSibling.tagName === tagName.toUpperCase())
return el.nextSibling.nextSibling;
else
if (el.nextSibling.nextSibling.nextSibling.tagName === tagName.toUpperCase())
return el.nextSibling.nextSibling.nextSibling;
else
etcetera...
From this you should be able to see how a recursive function hides a loop in itself. This also shows the danger of recursive functions - that they can keep calling themselves without end. Which leads me to wonder - what will happen to your function if el doesn't have a nextSibling?

I like to think as recursion as the mirror effect or the Droste effect, based on the following picture:
Where each level digs deeper into the next one until it reaches a limit. In your case, find the sibling with the desired tag name.
So essentially your base code is the first picture, and the first recursion is the first level. It will go a level deeper until it reaches its goal.

Can anyone help me visualize this a bit more? Any tips/tricks that you
may have used when first learning this concept?
Maybe thinking of an iterative version would help understand:
function findSibling(el, tagName) {
while (el = el.nextSibling) {
if (el.tagName == tagName.toUpperCase()) {
return el;
}
}
}
Also, the one thing I was stuck on for awhile was the second return
statement. Why can't I just call the function in the else? Why do I
need the return?
A recursive function as general rule will have a recursive call and an exit condition. The definition itself is recursive. In your code the exit condition is finding the tagName.
Explaining recursion is hard if you don't understand recursion. The Wikipedia has a good explanation with visualization. http://en.wikipedia.org/wiki/Recursion
Edit: See this question too https://softwareengineering.stackexchange.com/questions/25052/in-plain-english-what-is-recursion

You call a function findSibling to find 'tag'
But it doesnt find tag so it calls function findSibling to find 'tag'
But it doesnt find tag so it calls function findSibling to find 'tag'
But it doesnt find tag so it calls function findSibling to find 'tag'
It retrns tag to its caller
It returns tag to its caller
It returns tag to its caller
It returns tag to its you.
You have tag.
I think the best you can understand is the correct answer to this particular problem. To find something in a nested DOM is only slightly more complex to think about but its the same concept... and almost the same code.

Related

Vanilla JS - hoisting a querySelected variable to Window

I've been researching and trying different solutions to this literally all day.
** EDIT: regarding duplicate post: As I wrote below, a set timeout function has been attempted and successful around the function call. Please, before you close my question, atleast ask that what you’re describing as a duplicate hasn’t already been attempted… or in this case. INCLUDED in my original post. I’m not looking for cred, I’m looking for help. **
I have a reusable function takes in 3 params:
What to wrap,
wrap in what type of element
and the id of the new wrapping element (so I can control access it later.)
Here's a codeSandbox version to help you help me! https://codesandbox.io/s/queryselector-to-globe-jbffk0?file=/index.html
Goal: I'd like to include a querySelector within the function that takes in the id to eliminate the extra step, to ensure the selector is defined after the item is created, and to keep a cleaner code-base. The problem is I keep fighting between a function that's surrounded by parens...
Var wrap = (function(params){...})(window); to potentially give global scope to the queryselector(object ref) I'm trying to create, and a standard es6 function I'm more familiar with... Var wrap = (params) => {...};
import "./styles.css";
const item = document.querySelector(".item");
var wrap = (function (toWrap, wrapper, id) {
wrapper = wrapper || document.createElement("div");
wrapper.setAttribute("id", `${id}`);
toWrap.parentNode.appendChild(wrapper);
// Non-working auto id something to
// window.id = document.querySelector(`${id}`);
return wrapper.appendChild(toWrap);
})(window);
// How can I "store the window.id" just as if it were manually written right here in global scope?
wrap(item, "div", "itemadded");
Note: the window thing I read at: http://markdalgleish.com/2011/03/self-executing-anonymous-functions/
Like I said, I can provide more working code/attempts to show I've made a ton of effort if anyone is wondering.
PS, I'll definitely mark the answer and give upvotes for help.
Thanks in advance!
If your still reading, I've tried simplifying even further, adding a timeout function to ensure that the function takes in toWrap correctly... idk what else to try... :(

jQuery performance: Chain appends or append multiple arguments?

I was wondering which of those is more performant, and more importantly why?
$('a').append(
$('<b></b>').text('1'),
$('<b></b>').text('2'),
$('<b></b>').text('3'));
and
$('a')
.append($('<b></b>').text('1'))
.append($('<b></b>').text('2'))
.append($('<b></b>').text('3'));
where a and <b> are an arbitrary selector and tag. As far as I was able to tell from trying them out, they both function in exactly the same manner.
Relevant question: What is the best way to add options to a select from as a JS object with jQuery?
Edit:
The reason I was asking this question was to know whether I should structure appends as:
texts = ['1','2','3','4','5'];
$a = $('a');
$a.append(...texts.map(function(o){ return $('<b></b>').text(o); }));
or as:
texts = ['1','2','3','4','5'];
$a = $('a');
for (o in texts) {
$a.append($('<b></b>').text(o));
}
The latter one is more intuitive, and I believe most programmers would choose to write it, when the earlier one performs better.
You can use a benchmarking tool to test this. I used JSPerf: https://jsperf.com/so-question
The results show that the first case is much faster. I believe it is because that jQuery does a lot of initialization, checking, and compatibility work every time you call a function that works with an element, that is, .append().
The first case only calls .append() once, whereas the second case calls .append() three times. In general, the less function calls you make, the faster your code will be.

JavaScript not starting function

I have search for some sort of way to do this, but i can't make it work. It may seem simple but i don't know much about javascript so I have found very little information on the problem and I don't know were is the error at so i'll just briefly explain what it's supposed to do. First here is my code:
function showI(a){
$("#show").fadeIn();
$("#show .load").fadeIn();
$("#show").load("ajax/showi.php?id="+a,$("#show .load").fadeOut("fast"))
}
$('.List-item').on('click touchstart',function(){
var id=$(this).attr("id").split("list-").join();
showI(id);
})
So there's like a button with the class list-item which when click should open a new window with the showI function, but it doesn't(I used before the attribute onClick, but it didn't work on mobile so I changed it to .on(click touchstart))
Any help would be appreciate. (Don't know if this is replicated because i can't find a word to describe the problem)
This line:
$("#show").load("ajax/showi.php?id="+a,$("#show .load").fadeOut("fast"))
calls $("#show .load").fadeOut("fast") and then passes its return value into load as a second argument, exactly the way foo(bar()) calls bar and then passes its return value into foo.
If you're looking for a completion callback, you need to wrap that in a function:
$("#show").load("ajax/showi.php?id="+a, function() {
$("#show .load").fadeOut("fast");
});

Refactoring jQuery to use $(this) and add/remove classes from another element

so I have this idea I'm working on over on Codepen. I've got it working as it is but before I go and add more clickable areas I've realized a massive need to refactor and DRY things up. So far it works but it's ugly as hell and would involve a massive amount of repeated code.
So I'm trying to replace the many $(.class).click(function() { ... }); functions with a switch statement that uses $(this) to populate a single .click function instead. But I'm lost.
You can see everything here and edit it also: http://codepen.io/lukewatts/pen/ubtmI
I feel like I'm close but I've hit a wall. The top commented out part is the DRY attempt while what is uncommented for now is the working version. Click the min, off, max words or the LEDs to see it work.
Thank you very much in advance for any advise on this. PHP is my main language to be honest.
P.S. I had leds.click(function() { ... }) and I replaced it with leds.on(function() { ... }) but still nothing.
I understand what you are trying to do, but that not how the jQuery object works. In order to check for the object to match a selector, you will have to use .is().
As such, you will not be able to use a switch, but you will have to use a serie of chained ifs to achieve the goal the way you are trying, such as
if ( $this.is('.led[data-level="one"]') )
var led = $('p.min a');
var level = "one";
I have updated your CodePen example to work in this way: Codepen
As I mentioned in my comment to the question, though, I am not making any code review here, just fixing what didn't work for you. I am not sure this is actually a better approach than your messy original one, to be entirely honest.
The refactored version looks good for me. If you don't like to use addClass and removeClass you may directly change the class property of the element:
indicator.attr("class", "one on");
The reason your switch statement doesn't work is because every time you create a jQuery object, it gets an Id, so when the switch tries to compare $this to a selector like $(p.min a), they won't be equal. However, if you used multiple if statements with $.is, you could compare:
$this = $(this)
if($this.is('p.min a')) {
// do work
} else if($this.is('p.max a')) {
// do work
}
I wouldn't, however, recommend this approach. For more complex pages, I'd recommend a binding framework like Knockout.js. For something small, you're adding a lot of complexity. For clarity: If this becomes part of a larger control set or system, a binding framework would be useful. For the control as-is, both a binding framework and the OP's current approach are overkill.
You may want to look at event delegation, I find it very helpful to keeping things DRY. Clicks will bubble up the DOM tree to higher elements, and you can register your handler on an ancestral element. This is actually ideal, as you only bind a single handler to a single element, instead of binding to multiple elements and thus you realize a performance benefit in addition to cleaner code.
First thing, wrap all your .led elements in a <div id="leds">:
<div id="leds">
</div>
Now create your handler:
$('#leds').bind('click', function(e){
var target = e.target;
var $target = $(target);
//do interesting stuff
if (target.nodeName === 'A') {
var level = $target.data('level');
if(level = 'one'){
//do more interesting stuff
}
}
}
});

javascript leaving an empty if statement

I would like to know if leaving an empty if statement for certain situations as:
else if(typeof console === 'undefined'){}
Just to have the code bypass the rest of the function It is an accepted and safe way to work or there are other recommendation practices for these cases?. Thank you.
It's fine and safe to leave if branches empty, the only thing I would add is a comment:
else if(typeof console === 'undefined')
{
//explanation why nothing has to go here
}
Without seeing the rest of the code I'm unsure how you're using this to "bypass the rest of the function", there may be a better way to do this.
From what information you've provided me, I can glean that the answer is "no". It will work, but it's bad style. If you would like to bypass the rest of the function, why not return; or put most of the logic in the if statement that pertains to it so that there is no bypassing at all?
I just had a case in which I chose to use an empty if-statement (professional context). I must agree though, there definitely is a technically cleaner solution. Still, since in a professional context time is important too, I chose to use the empty if-statement in my case, so I wanted to share my train of thought with you.
In my case I'm patching existing code with a variable that is used to skip already existing nested if-statements. The main function keeps running before and after the statement.
Original Code:
if(bValidateA){
}elseif(bValidateB){
}elseif(bValidateC){
}
// ... code continues with variables set inside the statements.
Now we want to add a global Parameter to not validate anything. What are my options and why do they suck?
Solution A sucks because much work and less easy to read:
if(!bValidateNothing && bValidateA){
}elseif(!bValidateNothing && bValidateB){
}elseif(!bValidateNothing && bValidateC){
}
Solution B sucks because empty if-statement:
if(bValidateNothing){
// empty
}elseif(bValidateA){
}elseif(bValidateB){
}elseif(bValidateC){
}
Solution C sucks because it becomes too nested (in my case there have been some additional ifs in the original code):
if(!bValidateNothing){
if(bValidateA){
if(xx){
}elseif(xy){}
}elseif(bValidateB){
}elseif(bValidateC){
}
}
Solution D, the technically cleanest solution by adding additional functions, sucks because you need to split your code, which needs a lot of time, and may result in new errors.
(no pseudocode)
So, to answer the question "accepted and safe": it works, it's readable, safe and quick. Sometimes that has to be enough, considering the alternatives. If you have the time to avoid using it, I'd probably still recommend that instead.
Funny enough, the time I saved by using this quick way to implement my logic, has now been successfully spent adding my cents to this ten year old already answered question.
Just don't write a block for a case you don't want to handle.
If you only want to do something when console exists, then do that:
if(typeof console !== 'undefined'){
// your code
}
// else if(typeof console === 'undefined'){}
// you don't need that second part
Or maybe I didn't quite get your issue?
Same as Pioul's answer, but I'd add that imo checking existence in javascript looks much tidier with the !! (notnot) operator.
if(!!console){
// your code
}
// else if(!console){}
// you don't need that second part
Sometimes it is useful to have debugging information printed out:-
if(typeof console !== 'undefined'){
console.log("debug info");
}
Then, before releasing the code, simply comment out all the console.log's
// console.log("debug info");
This can be done with a macro.
It will leave an empty if statement. But this is not a compilation error so that's OK.
Note, that if you're going to comment out the line it is important that braces are used. Otherwise you'd have the next line dependent on the if statement which would be a bleeding shame.
Using an empty if statement can be a valid and accepted practice in certain situations.
For example, when working with a try-catch block, you may use an empty if statement to handle specific errors without disrupting the rest of the function. Additionally, it can be used for performance optimization by short-circuiting the evaluation of certain conditions.
Make sure that when using an empty if statement, it is properly commented to provide context and explanation for its use.
Example:
try {
// code that may throw an error
} catch (error) {
if(error instanceof SpecificError) {
// handle specific error without disrupting the rest of the function
}
}
Another example:
if(isFirstConditionTrue && isSecondConditionTrue && isThirdConditionTrue) {
// Do something
} else if(isFirstConditionTrue && isSecondConditionTrue) {
// Do nothing, because third condition is false
} else {
// handle other conditions
}
It's always a good practice to add comments explaining the purpose of each empty if statement and why you chose to use it in a certain scenario. It's not generally considered bad style as long as it serves a specific purpose and is well documented.

Categories

Resources