• We are delighted to introduce GiftBot 2.0, the next generation of our popular gifting feature. To celebrate, we'll be giving away some incredible prizes over the coming weeks in one big Giveaway Extravaganza!

Programming |OT| Functional, Imperative, OOP, Cargo-Cult,... all are welcome!

metaprogram

Member
Oct 28, 2017
965
I have a question related to design patterns (I'm using C#).

Let's assume that I have the following structure:
Building HAS (one or more) Floor HAS (one or more) Window

Currently each Building contains a list of Floor and each Floor contains a reference to its Building parent. The same logic applies to Window and Floor and their relationship.

I find it cumbersome having to reference the creator in each class constructor (e.g., myBuilding.AddFloor(myBuilding, newFloor))... but I need the children to be explicitly linked to their parent (in both directions... top-down and bottom-up).

Is there a better way of dealing with such a scenario?

Thanks!
Just say myBuilding.AddFloor(newFloor). Then in the implementation of AddFloor say floors.Append(new Floor(this))
 
Oct 26, 2017
284
Just say myBuilding.AddFloor(newFloor). Then in the implementation of AddFloor say floors.Append(new Floor(this))
Done! ...I should have thought of that... Thanks!

I’d also make a note that any references to a parent be captured with a weak reference to allow the memory to be reclaimed properly since it’s a circular dependency.
I had actually never heard of WeakReference... Thank you!
In my case I'm not sure I'll need it though, as I've made it impossible that children be orphaned (i.e., removing a Floor from a Building deletes all Window objects, which aren't referenced anywhere else). A Window without a Floor or a Floor without a Building wouldn't make sense!
 
Oct 25, 2017
2,418
Outside of a few of the simpler ones, most modern garbage collectors can handle cyclic references just fine.
The problem with all GC implementations is not on the GC itself ;). Circular reference designs have a tendency of allowing someone to inadvertently hold a reference to a child object keeping all the objects connected as reachable. The problem is basically a developer problem. If at all possible, circular references should be avoided. Of course, not all can. Node based structures such as linked lists, trees, and graphs cannot avoid such a situation. With those however, you simply avoid leaking out the node object. Theoretically, you can use an internal Impl class and only expose an immutable proxy though.
 

Pokemaniac

Member
Oct 25, 2017
2,083
The problem with all GC implementations is not on the GC itself ;). Circular reference designs have a tendency of allowing someone to inadvertently hold a reference to a child object keeping all the objects connected as reachable. The problem is basically a developer problem. If at all possible, circular references should be avoided. Of course, not all can. Node based structures such as linked lists, trees, and graphs cannot avoid such a situation. With those however, you simply avoid leaking out the node object. Theoretically, you can use an internal Impl class and only expose an immutable facade though.
I mean sure, that can happen, but the worst possible outcome is a memory leak. You were talking like it could actually cause issues in the GC itself.
 
Oct 25, 2017
2,418
I mean sure, that can happen, but the worst possible outcome is a memory leak. You were talking like it could actually cause issues in the GC itself.
Well, memory leaks can cause issues with the GC itself. In the case memory spiked quite a bit and the holdout causing the memory leak finally was no longer referenced, the GC pause would be quite long.

To be fair though, this is not specific to GCs. If you had a reference counter, the same and worse problems would happen.
 

metaprogram

Member
Oct 28, 2017
965
No it can trigger a lock condition. You can also avoid it by abstracting away the parent as an interface. Direct circular references cause the same problem no matter the GC implementation.
That doesn’t make any sense. Cyclic references are a solved problem in modern GC implementations, if one can’t handle it it’s because the implementation is poor. But C# *does*. You can test this yourself. Make a function f where you instantiate A and B with references to each other, then return from the function and force a collection. Put some code in the Finalize() method that prints something and you can observe that it gets printed
 
Oct 25, 2017
2,418
That doesn’t make any sense. Cyclic references are a solved problem in modern GC implementations, if one can’t handle it it’s because the implementation is poor. But C# *does*. You can test this yourself. Make a function f where you instantiate A and B with references to each other, then return from the function and force a collection. Put some code in the Finalize() method that prints something and you can observe that it gets printed
You’re assuming no one will keep a reference to a floor which keeps the parent building and all its floors along with all of each floor’s windows in memory.
 

Zevenberge

Member
Oct 27, 2017
351
You’re assuming no one will keep a reference to a floor which keeps the parent building and all its floors along with all of each floor’s windows in memory.
I might be missing something, but isn't this why you want circular reference in the first place? To be able to say
C#:
public int CountWindows(Floor floor)
{
    return floor.Windows.Count + CountWindows(floor.NextFloor());
}
Also, thanks to this discussion, I might look into my own datastructures tomorrow (if I have time for playing around, because it's my last day at work before going on holiday). Friday my tests segfaulted during a GC :s Didn't really manage to find out more. I came across the whole Tolkien stack already (elf, dwarf and trampoline).
 

metaprogram

Member
Oct 28, 2017
965
But you said this
I’d also make a note that any references to a parent be captured with a weak reference to allow the memory to be reclaimed properly since it’s a circular dependency.
No it can trigger a lock condition. You can also avoid it by abstracting away the parent as an interface. Direct circular references cause the same problem no matter the GC implementation.
Which just isn’t true. Nothing will deadlock, and everything will be reclaimed properly.

But sure, if you have a circular reference then 2 things have to become orphaned before the GC can reclaim it rather than 1. That sounds very different than what we’ve been talking about though, but sure weak references can occasionally be helpful here
 

MotiD

Member
Oct 26, 2017
785
Here is another way to think about the problem that emphasizes recursion and backtracking. You are attempting to determine whether there are lists [a_0,...,a_n] of bits such that some number x can be written sum[i=0,n](a_i * 3 ^ i). To the best of my knowledge, this representation is not unique; you might as well solve the more general problem of finding all such lists (your algorithm would terminate when it finds one such list).

Imagine that you wanted to construct all such lists. You have a predicate test(num, bits) that determines whether num is the power sum represented by the coefficients in bits. You know there is a maximum power that determines the maximum list length (and can be computed using logarithms). You know various other things about the boundary conditions of the recursion, so we can focus attention on the recursive case.

In the recursive case, you have a partial solution bits and whatever other pieces of information you are tracking to determine whether to continue searching along the current branch of the recursion tree. If test(num, bits) succeeds, you should perform whatever found action is appropriate and return success (depending on the exact problem, you would either continue searching for solutions or return). If it fails, there are two possibilities to consider.

If no more solutions are possible, you should return failure. If more solutions are possible, you need to consider two cases: in the first case, the next power does not contribute to the sum (i.e., the coefficient is zero); in the second, the next power does contribute to the sum (i.e., the coefficient is one). In either case, you would recursively call your function with the appropriately updated power and solutions, and report whether either recursive call succeeds.

In this explanation, I am constructing solutions and recording them on successful branches of the recursion tree. When a potential solution fails, the algorithm backtracks to the point at which more solutions could be found, and resumes searching from there. There are, of course, many possible optimizations you could make to the algorithm (for example, counting down instead of constructing solutions), but hopefully this helps you understand the structure of the recursion.
Thanks for this handy post.
Gotta say that ever since I started going over binary trees, recursion and backtracking have become easier since I can visualize it all now.
Fwiw, I’m a little confused about the requirement that you can’t arrive at a solution via math.

The answer I gave is still a perfectly acceptable answer. It solves the problem exactly, and the math is just there to explain why it works, much like you would need to use some math to explain why the other answer you posted works.

Sure you can’t use math to avoid using recursion, but it’s still using recursion.
I'm honestly not 100% sure whatever it would be acceptable or not since that guideline is a bit iffy, but it exists
 

quickly

Member
Mar 8, 2018
936
Gotta say that ever since I started going over binary trees, recursion and backtracking have become easier since I can visualize it all now.
I agree. The whole problem can be reduced to a depth-first search over a binary tree (whose nodes represent 0 or 1 decisions on the list coefficients).
 

phisheep

Member
Oct 26, 2017
743
Further adventures in Forth! I've now got this thing up to the ANS core standard (barring any bugs to be found), and it's neat.



Here's a sample session, including a view of the dictionary structure:



I'm particularly pleased with the runtime trace, which has been super-useful in working out what is going on - the dots to the left of each line each represent a value on the return stack (the actual values aren't any real use in debugging), and serve to indent the trace so you can see what is nested inside what; and the stuff in brackets to the right is the contents of the parameter stack.



... and the entire code of the inner interpreter looks, after about 4 painful iterations, like this:

C:
void inner_interpreter () {

    mem.ip.cell_p = NULL;
    cell *saved_rsp = mem.rsp;

    for (;;) {

        if (mem.trace.n == -1)  trace_word();

        switch (mem.w->interpreter) {

            case DOCOLON:       rpush(mem.ip);
                                mem.ip.p = &(mem.w->data_field);
                                break;
            case DODOES:        rpush(mem.ip);
                                mem.ip.p = mem.w->code_field.p;
                                push((cell)(void *)&(mem.w->data_field));
                                break;
            case C_FUNCTION:    mem.w->code_field.f();
                                break;
            case C_VAR:         push((cell)mem.w->code_field.p);
                                break;
            case DOCON:         push((cell)mem.w->data_field[0]);
                                break;
            case DOVAR:         push((cell)(void *)&(mem.w->data_field));
                                break;
            case BRANCH:        mem.ip = (cell)*mem.ip.cell_p;
                                break;
            case ZBRANCH:       {  if (pop().n)
                                       mem.ip.cell_p += 1;
                                   else
                                       mem.ip = (cell)*mem.ip.cell_p;
                                }
                                break;
            case LIT:           push((cell)*mem.ip.cell_p);
                                mem.ip.cell_p++;
                                break;
            case SLIT:          {  int len = *(char*)mem.ip.p;
                                   push((cell)(mem.ip.p+1));
                                   push((cell)len);
                                   mem.ip.p += 1 + len;
                                   mem.ip.u = (mem.ip.u + (4-1)) & -4;
                                }
                                break;
            case EXIT:          mem.ip = rpop();
                                break;
            default:            error("Interpreted function not found");
                                break;
        }

        if (mem.trace.n == -1)  trace_stack();

        if (saved_rsp == mem.rsp)
            break;

        mem.w = *mem.ip.ip;     // NEXT
        mem.ip.cell_p++;
    }
}
It *could* be a lot shorter than that. We only really need the cases for DOCON, DODOES and C_FUNCTION, but I find my head hurts less if I gather all the code that does weird things with the instruction pointer and the code field in one place!

Today's conundrum, though, is a real corker. At the moment, the outer interpreter - that handles all the console i/o and text parsing and so on - is coded in C, and everything gets driven from it. Now I'm recoding that in Forth which leaves me with some puzzles:

1) There will be no termination condition on the inner interpreter loop, because it will never end
2) ... so how the hell do we *start* it?
3) how to code the word EXECUTE - which currently just calls the inner interpreter to execute a word, it'll need to be incorporated in there somehow without interfering with the instruction pointer
4) since the code for the outer interpreter is in the file boot.fth, how do I bootstrap into it?
5) i don't really want to TRACE every action of the outer interpreter, so will need a different way of handling that

It's a pretty little bootstrapping problem, and I'm looking forward to a productive and inevitably frustrating day!
 

Ambitious

Member
Oct 26, 2017
319
Work is so exhausting at the moment.

After some changes, our team now consists of 5 juniors (one of them is fresh from school and has literally zero experience with our stack), and 3 seniors, including me. One of the other seniors is working only part-time and considering leaving the project, and the other one - the team lead - will leave the project for sure by the end of the year. The one junior with the most work/programming experience will also leave the company next month. It's not yet set in stone, but it's possible that they won't be replaced.

The juniors aren't bad coders, but they lack experience. They're young and most of them are still studying. Every day I see countless merge requests with code that isn't bad, but has a lot of potential for improvement. I try to comment as many suggestions for improvements as I can, but I can't do this all day. I have my own tickets to work on. And many times, they disagree with me and start to argue. Now that's fine, of course - after all, I'm just making suggestions; I'm not telling them what to do. But if I offer several ideas that would definitely improve the code and then I have to discuss with several coworkers in several MRs and try to make them understand my point, it's just so exhausting.

Last week on Thursday/Friday, it was particularly bad. The team lead was on vacation and the other senior wasn't working (as I mentioned, he's part-time). So whenever any of the other people had a problem and needed advice, they came to me. Had I been able to focus, I would have finished my ticket in half a day. Instead, it took me two full days, because I kept getting distracted by their questions and by my own urge to review their merge requests.

I'm worried about the project. The software we're working on right now will be used by thousands of users every day. It needs to be polished as fuck, and that's not the case right now. The code is complex, redundant, inconsistent and hard to read. I try to clean up and refactor whenever I can, and I try to get them to do the same, but really, I'm just so weary.

When the other two seniors are gone, I will become the new team lead. Seriously, if they won't be replaced by other, more experienced coworkers.. if they're either replaced by even more juniors, or if they're not replaced at all, then I'm out. I can't run this thing on my own.
 

Lionheart

Member
Oct 26, 2017
667
I was considering becoming a software developer, and studied for about 7 months but reading this chat is pure gibberish, I'm out.
 

metaprogram

Member
Oct 28, 2017
965
Giving up a potentially high paying career because of some posts on an internet forum seems a little short-sighted. No career is all peaches and rainbows, you have good days and bad days, good jobs and bad jobs. Just like everywhere else.
 

sNtd

Member
Oct 24, 2018
226
I was considering becoming a software developer, and studied for about 7 months but reading this chat is pure gibberish, I'm out.
Even with a PhD in Informatics I do not fully understand every topic discussed in this thread. Programming is a highly technical activity so you need the proper background and to concentrate on a problem discussed to truly grasp it. It takes years and years to become a good programmer.
 

Lionheart

Member
Oct 26, 2017
667
Yeah I'm being facetious but there's truth in there. I don't know if I could do it tbh. It's something I need to decide if I want to commit so much of my time to learn at this stage in my life (33 yo). Incredibly daunting.
 

sNtd

Member
Oct 24, 2018
226
Yeah I'm being facetious but there's truth in there. I don't know if I could do it tbh. It's something I need to decide if I want to commit so much of my time to learn at this stage in my life (33 yo). Incredibly daunting.
I would say having some experience in life can be a benefit, as most software developers fresh out of college have very little domain knowledge. For example, how to replace paperwork with electronic forms is one of the easier things to learn (just learn PHP, SQL and HTML) and can land you a decently paying job. I was doing that as a hobby before I even started college and I found a side job doing something relatively similar after a year and a half in college. Of course, if you want a high paying job in any field you need to put in the time to learn. Some people land high paying jobs at a FAANG company (Facebook, Apple, Amazon, Netflix, Google) after three years of college, but I guess they are typically highly talented and could probably already program before entering.

(you can replace college with self-study, consider there are many self-learned programmers)
 

Jokab

Member
Oct 28, 2017
665
Work is so exhausting at the moment.

After some changes, our team now consists of 5 juniors (one of them is fresh from school and has literally zero experience with our stack), and 3 seniors, including me. One of the other seniors is working only part-time and considering leaving the project, and the other one - the team lead - will leave the project for sure by the end of the year. The one junior with the most work/programming experience will also leave the company next month. It's not yet set in stone, but it's possible that they won't be replaced.

The juniors aren't bad coders, but they lack experience. They're young and most of them are still studying. Every day I see countless merge requests with code that isn't bad, but has a lot of potential for improvement. I try to comment as many suggestions for improvements as I can, but I can't do this all day. I have my own tickets to work on. And many times, they disagree with me and start to argue. Now that's fine, of course - after all, I'm just making suggestions; I'm not telling them what to do. But if I offer several ideas that would definitely improve the code and then I have to discuss with several coworkers in several MRs and try to make them understand my point, it's just so exhausting.

Last week on Thursday/Friday, it was particularly bad. The team lead was on vacation and the other senior wasn't working (as I mentioned, he's part-time). So whenever any of the other people had a problem and needed advice, they came to me. Had I been able to focus, I would have finished my ticket in half a day. Instead, it took me two full days, because I kept getting distracted by their questions and by my own urge to review their merge requests.

I'm worried about the project. The software we're working on right now will be used by thousands of users every day. It needs to be polished as fuck, and that's not the case right now. The code is complex, redundant, inconsistent and hard to read. I try to clean up and refactor whenever I can, and I try to get them to do the same, but really, I'm just so weary.

When the other two seniors are gone, I will become the new team lead. Seriously, if they won't be replaced by other, more experienced coworkers.. if they're either replaced by even more juniors, or if they're not replaced at all, then I'm out. I can't run this thing on my own.
You need to talk to your boss and explain that you spend way too much time tutoring and too little time coding. If your boss tells you to keep up the same level of tutoring then you have your answer and can get out.
 

Cyborg009

Member
Oct 28, 2017
308
Would anyone here mind looking over a resume of mines. I’m looking to try to get my first software engineer job. My only experience is help desk work unfortunately.
 

NetMapel

Member
Oct 25, 2017
285
A general OOP question for y'all, but what is the purpose of a static method? You have your regular method which inherits the instance and class method which inherits the class. Static method doesn't inherit any of that and basically works like a function. So what's the usage of a static method when you could just have a regular function outside of a class that does the same thing?
 

quickly

Member
Mar 8, 2018
936
A general OOP question for y'all, but what is the purpose of a static method? You have your regular method which inherits the instance and class method which inherits the class. Static method doesn't inherit any of that and basically works like a function. So what's the usage of a static method when you could just have a regular function outside of a class that does the same thing?
In general, static methods are appropriate when the method behavior should be uniform across every class instance. In some languages, only static methods can modify static class variables; and static methods and classes are used to implement general purpose or "pure" functions (e.g., Java).
 
Last edited:

metaprogram

Member
Oct 28, 2017
965
A general OOP question for y'all, but what is the purpose of a static method? You have your regular method which inherits the instance and class method which inherits the class. Static method doesn't inherit any of that and basically works like a function. So what's the usage of a static method when you could just have a regular function outside of a class that does the same thing?
Fundamentally there’s zero difference between a static method and a global function. It’s purely an organizational thing. If your global method is called getMaxFileSize, and you have a File class, then maybe it could be File::getMaxSize. This helps people find it, but otherwise is really no different
 

deprecated

Member
Apr 15, 2019
120
Fundamentally there’s zero difference between a static method and a global function. It’s purely an organizational thing. If your global method is called getMaxFileSize, and you have a File class, then maybe it could be File::getMaxSize. This helps people find it, but otherwise is really no different
I wouldn't say zero difference (although I don't know what language you're using). In C++ a static method still can't be called from outside the object if it's private or protected. Static methods still have to follow visibility rules

Also, hi all!
 

metaprogram

Member
Oct 28, 2017
965
I wouldn't say zero difference (although I don't know what language you're using). In C++ a static method still can't be called from outside the object if it's private or protected. Static methods still have to follow visibility rules

Also, hi all!
I mean even more fundamental than that! The code generated by the compiler for a static method should be byte for byte identical to a global function. They’re literally interchangeable from the machine’s perspective.

Also I’m mostly referring to C++, in other languages you have things like Reflection metadata that changes, and you may not even have global functions to begin with.
 

deprecated

Member
Apr 15, 2019
120
I mean even more fundamental than that! The code generated by the compiler for a static method should be byte for byte identical to a global function. They’re literally interchangeable from the machine’s perspective.

Also I’m mostly referring to C++, in other languages you have things like Reflection metadata that changes, and you may not even have global functions to begin with.
I guess it depends on the level of abstraction NetMapel cares about. But yes, the compiler should generate the same function between the two.
 

Pokemaniac

Member
Oct 25, 2017
2,083
I mean even more fundamental than that! The code generated by the compiler for a static method should be byte for byte identical to a global function. They’re literally interchangeable from the machine’s perspective.

Also I’m mostly referring to C++, in other languages you have things like Reflection metadata that changes, and you may not even have global functions to begin with.
From a machine code perspective there really isn't a huge difference between static and non-static functions either. The primary difference is whether or not there's an implict "this" argument. All the complicated bits for non-static functions are in the dynamic dispatch that happens before you get to the function itself.
 

metaprogram

Member
Oct 28, 2017
965
From a machine code perspective there really isn't a huge difference between static and non-static functions either. The primary difference is whether or not there's an implict "this" argument. All the complicated bits for non-static functions are in the dynamic dispatch that happens before you get to the function itself.
Non-static functions might go through a vtable (as well as a virtual base table), and could also use a different calling convention (thiscall on x86). But sure, at this point we're just saying "they're all functions, so they're similar in that regard".
 

Gravy Boat

Member
Oct 27, 2017
3,835
Hey, sorry if this isn't the best thread for this but I couldn't find a more suitable one. Would anyone be able to tell me why an API may return an asynchronous decision? I feel like I have a very basic understanding, but this is a question I got asked for a job interview and I'm curious to know if there's more to it than I thought.
 
Oct 25, 2017
2,418
Hey, sorry if this isn't the best thread for this but I couldn't find a more suitable one. Would anyone be able to tell me why an API may return an asynchronous decision? I feel like I have a very basic understanding, but this is a question I got asked for a job interview and I'm curious to know if there's more to it than I thought.
Asynchronous usage is always tied to improving perceived latency. Rather than block on waiting for an answer, we do other things to ensure the user stays engaged with our application.
 

BreakyBoy

Member
Oct 27, 2017
163
I've been working with Go quite a bit, but I wouldn't consider myself an expert or anything. It was just a natural thing to try out since I'm in the DevOps/kubernetes space where the language has a lot of traction.

I like it quite a bit, but there are some quirks you need to get used to. The big one for me was not having real generics, which meant that I had to re-think my solutions and/or make use of workarounds like code generators or the use of interface{} objects.

All in all though, I like it. I need to go back and finish last year's Advent of Code challenges with it.
 

MegaRockEXE

Member
Oct 29, 2017
1,164
Can we still get help here? I'm working on a C# WinForms application and I want to add command line support to it. But I want to write things to the console window that is needed to run it. My code works, but I can't write anything to the console. And a snippet of code I found makes a window show up at a specific point in the code, but it only asks for user input, which is not quite what I want. Anyone ever done anything like this before?
 

phisheep

Member
Oct 26, 2017
743
Can we still get help here? I'm working on a C# WinForms application and I want to add command line support to it. But I want to write things to the console window that is needed to run it. My code works, but I can't write anything to the console. And a snippet of code I found makes a window show up at a specific point in the code, but it only asks for user input, which is not quite what I want. Anyone ever done anything like this before?
I haven't, but this looks like it might be the sort of thing to address the problem.
 

MotiD

Member
Oct 26, 2017
785
I need some clarification about something that confused me with inheritance and Polymorphism in Java

Say that I have a class A and a class B that extends A
Java:
public class A{...}

public class B extends A {

// equals 1
    public boolean equals(B other) {
        System.out.print("B");
        return true;
    }
// equals 2
    public boolean equals (Object other){
        System.out.println("Object B");
        return true;
    }
// equals 3
    public boolean equals(A other){
        System.out.println("A B");
        return true;
    }


}
A does not have an equals method.
Why is it that when I call a4.equals(a2), equals 2 ends up being used? How I thought it worked was the compiler will check A to see if there's an equals method there it could use, but one doesn't exists so it will use the equals method in Object, but the fact that an equals method that receives a reference variable for Object in B changes that somehow.
If I delete equals 2 from B it indeed uses the equals method in Object, but I'm not clear on why having equals 2 in B changes things.

Edit: So it seems once the compiler sees there's no equals method in A and decides to use the Object equals method, it will still use a method that overrides it? I think that's the part that confused me.
I knew about Polymorphism and overriding and how the compiler chooses what method to use, but I didn't realize the compiler will still use the override method in this case.
 
Last edited:

Post Reply

Member
Aug 1, 2018
1,934
What is the best way to learn Javascript and React? I'm trying to get a job as a front end developer and need to learn it. I know html, css, sql, bootstrap and seo. Any suggestions are greatly appreciated.
I'm not a web-dev ... all the work I do revolves around .NET, but I've been spending this year trying to learn more languages outside of .NET languages and Java and when I was spending time trying to learn JavaScript, I read this book:

https://www.amazon.com/Eloquent-JavaScript-3rd-Introduction-Programming/dp/1593279507

I think it was a really good book. It won't help you with React, but it does a good job of just teaching vanilla JavaScript and showing how to do a lot of stuff using pre-ES6 syntax and ES6+ syntax. It also randomly has the best explanation and subsequently nicest reference material on regular expressions that I've come across.

I only ever dealt with JavaScript once when I was in college and I hated it at the time, but this book has made me appreciate it more than I did (still vastly prefer TypeScript), but I was able to gain a practical level of proficiency by the end of it.
 
I'm not a web-dev ... all the work I do revolves around .NET, but I've been spending this year trying to learn more languages outside of .NET languages and Java and when I was spending time trying to learn JavaScript, I read this book:

https://www.amazon.com/Eloquent-JavaScript-3rd-Introduction-Programming/dp/1593279507

I think it was a really good book. It won't help you with React, but it does a good job of just teaching vanilla JavaScript and showing how to do a lot of stuff using pre-ES6 syntax and ES6+ syntax. It also randomly has the best explanation and subsequently nicest reference material on regular expressions that I've come across.

I only ever dealt with JavaScript once when I was in college and I hated it at the time, but this book has made me appreciate it more than I did (still vastly prefer TypeScript), but I was able to gain a practical level of proficiency by the end of it.
I will definitely check this book out. Thanks a lot.