JavaScript Code Style
h
Coder100 (12643)

JavaScript, unlike many languages, has a very diverse code style. This is probably a combination of not only ASI (Automatic Semicolon Insertion) code style, but also if quotes should be used or not, and type safety.

So without further ado, lets talk about my favorite code style!

Coder100's code style

Semicolons

Semicolons are always required, but beware of ASI.

console.log("hey :smirk:"); // <-- good!
console.log("hey :smirk:") // <-- avoid!

ASI

ASI automatically inserts a semicolon if the program errors. You can read up here

Quotes

Due to how often we're using single quotes, it is very recommended to use double quotes. Also in lower level languages, double quotes signify a string, and I believe it is wise to adapt that into your coding as well.

console.log("Let's go outside!");  // <-- good!
console.log('Let\'s go outside!') // <-- avoid!

However, the exception is when you require double quotes:

console.log("\"That's what she said\""); // <-- avoid!
console.log('"I am Coder100"'); // <-- good!
console.log('"That\'s what she said"'); // <-- edge case
console.log(`"That's what she said"`); // <-- good!

Try to minimize escaping, and if there is no need for escaping, just use ".

Spacing

I have always used two spaces, but sometimes I use 4, and sometimes I use tabs. So which one should we use? I flipped a coin between two spaces and tabs and got two spaces, so that's what we are using.

function hi() {
  return 5; // <-- good!
}

function hi() {
	return 5; // <-- avoid!
}

Resolving Paths

Use path.join, not only is it going to make your life easier, it also allows you to make more complex paths.

const path = require("path");

path.join(__dirname, "./index.html"); // <-- good!
__dirname + "/index.html"; // <-- avoid!

Destructuring

I haven't found much use for destructuring, but just make sure you add spaces:

const {log} = console; // <-- avoid!
const { log } = console; // <-- good!
const { log, debug, dir, table, error, warn } = console; // <-- good!
const { log,
        debug,
        dir,
        table,
        error,
        warn } = console; // <-- consider using no wrap (avoid)

Spacing

This code snippet will tell you everything.

if (true) {
  // this is what we want
} else {
  console.log("POG UR COMPUTER IS BROKE");
}

function poggers(a) {
  return 5;
}

let myArr = [1, 2, 3];
myArr.push(10);

poggers(10);

Arrow functions

I only use arrow functions as lambdas. You should too. It allows you to define functions anywhere!

const bad = () => console.log("bad"); // <-- avoid!

hi();

function hi() { // <-- good!
  console.log("goodbye");
}

[1, 2, 3].map(n => n ** 2); // <-- good!
[1, 2, 3].map(function square(n) {
  return n ** 2;
}); // <-- uh ew (avoid)

Also anonymous functions should always be converted to arrow functions, except for when binding.

const x = {
  key: "value",
  func: () => this.key // good!
};
x.func(); // => undefined

const y = {
  key: "value",
  func: function () { return this.key; } // good!
};
y.func(); // => "value"

Function Definition

I normally call functions before defining them,

a();

function a() {
  console.log("hi");
}

however, what you want is really up to you. However, never define functions within loops.

for (let i = 0; i < 10; i++) {
  function a() { return i; }
  console.log(a);
}

Mutability

Like rust, make sure to define everything as constant. Only when you need to change mutability should you change the const keyword to let.

const a = 5; // <-- good!
const b = 10; // <-- good!
let g = 10; // <-- good!
g++;

let j = 3; // <-- avoid!

Objects and Arrays

These can be prefixed with let if you are going to change them. const isn't only for the interpreter, it is also for programmers.

const a = {
  b: 6
};

a.b = 6; // <-- avoid!

If you want objects to be constant, consider also adding Object.freeze()

const a = Object.freeze({
  b: 6
});

a.b = 10; // <-- will silently fail

Same goes with arrays.

Member Access

The final code style is with member access. Try to use . as much as you can, but use [] for variables and numbers.

let a = { b: 6 };
a.b = 10; // <-- good!
a[3] = 10; // <-- good!
a["b"] = 10; // <-- bad!

Conclusion

Hopefully you like this code style. Finally I have peaked in good coding practices!
In the next tutorial, I will be showing you how to use eslint to enforce this code style :))

See you then!

Credits

Special thanks to @fuzzyastrocat and @Baconman321 for pointing out some edge cases

You are viewing a single comment. View All
Baconman321 (560)

Cool, this is pretty much the same for me (well, the const part no, because a lot of stuff I have to change sooner or later). You forgot hoisting tho. Since JavaScript renders functions first, you don't have to worry too much for functions (and sometimes it's actually very useful), but I recommend staying away from hoisting as much as possible. Also, I recommend putting all the global variables and functions (unless otherwise needed somewhere else) at the top of the program. Makes code cleaner. Also, if you have a function (like a loop) that gets called over and over again, and generate new instances a whole lot, I recommend after you're done with the function to assign it to null, so the garbage collector will collect it.

Coder100 (12643)

@Baconman321 ah yes, I did forget a lot of things lol

Coder100 (12643)

@Baconman321 so um I don't really care for hoisting as I normally split functions into many files, and otherwise they don't get called over and over again.

I rarely put functions inside loops, as I usually use things like .map and .foreach

thanks for your advice!

Baconman321 (560)

@Coder100 Typically you would do looping if you do game design or anything like that because you have to constantly update the canvas (or whatever you are using).

Coder100 (12643)

true, typically you would use recursion to achieve that @Baconman321

fuzzyastrocat (1471)

@Coder100 @Baconman321

typically you would use recursion to achieve that

What?!?! You'd quickly blow the JS call stack if you used recursion for your main draw loop! Typically, you'd use requestAnimationFrame.

Baconman321 (560)

@fuzzyastrocat Yeah. Or it goes really really fast (depends on how you implement it). I think coder100 meant that.

fuzzyastrocat (1471)

@Baconman321

depends on how you implement it

No matter how you implement recursion in JS, it's going to blow the call stack if it's unbounded. There is no tail call optimization in almost all standard JS implementations (barring Safari), so if it doesn't blow the stack then it's not recursion.

Baconman321 (560)

@fuzzyastrocat I have been able to requestAnimationFrame with the function call, so IDK why then. Yeah, the call stack is a big frustration, but quite easy to avoid.

fuzzyastrocat (1471)

@Baconman321 requestAnimationFrame is not recursion, which is why it doesn't blow the stack. (If this isn't what you meant then please tell me.)

Coder100 (12643)

wait is requestAnimationFrame not just recursion but without stack overflows? @fuzzyastrocat

fuzzyastrocat (1471)

@Coder100 No, it's not, since with recursion a function call can return but requestAnimationFrame cannot. (Now, they may be implemented in a similar way, but that's neither here nor there. requestAnimationFrame itself is not recursion.)

Coder100 (12643)

@fuzzyastrocat hm alright
ill read up on its implementation xD

Baconman321 (560)

@fuzzyastrocat I tested.

function e(){
console.log("e");
e();
}
e();

does not return a stack error. It just gets called.... really fast. I don't know if that's recursive, but that's what I've heard.

fuzzyastrocat (1471)

@Baconman321 A few things here. One, try removing the console.log and see if you get a stack overflow. It could be that your console.log's buffer is slow to write and so therefore it's just prolonging the stack overflow for quite a while.

If not, what browser are you using? If you're using Safari, as I mentioned before then that would make sense. Safari is an anomaly in that it performs tail-call optimization — standard JS implementations do not, and therefore that code will not work on most browsers. For example, I tried this on Chrome (removing the console.log so that it will run quicker), and it quickly runs into a stack overflow.

Coder100 (12643)

be patient, the threshold is like 10k or smth @Baconman321

Baconman321 (560)

@fuzzyastrocat I'm using chrome. Still, it goes too fast for me, so I prefer to use requestanimationframe.

fuzzyastrocat (1471)

@Baconman321 Let's not just skip away from this issue though, I'm interested. Do you get a callstack error when you remove the console.log? If not, what version of chrome do you use?

EpicRaisin (232)

@fuzzyastrocat @Coder100
lol I've always just done setInterval('draw()', 1)

Baconman321 (560)

@EpicRaisin That has problems, as it really depends on CPU usage (if the CPU usage is higher, it goes slower because of lag). It's better to use requestAnimationFrame(draw). If not, use setTimeout recursively, as that can be used as a better setInterval.

fuzzyastrocat (1471)

@Baconman321 @EpicRaisin Keep in mind that a recursive setTimeout cannot be relied upon to run forever. See this article for more. So therefore, it is not a better setInterval for the purpose @EpicRaisin described.

Just use requestAnimationFrame and everything will be good :D

(Also @Baconman321 did you see my above comment?)

Baconman321 (560)

@fuzzyastrocat I believe that's TCI (tail call optimization). Yes, I do get a callstack error. Any code whatsoever inside a recursive loop like that prevents a callstack error (according to stackoverflow).

Baconman321 (560)

@fuzzyastrocat What above comment? Also, you can implement a clearTimeout to prevent the callstack from growing too large. Also, I see you like looking into the very depths of programming languages (like how they actually execute code and stuff), I would love to do that too... but it's pretty complex right now :(

fuzzyastrocat (1471)

@Baconman321

Any code whatsoever inside a recursive loop like that prevents a callstack error (according to stackoverflow)

Hmm, I'm confused here. First of all I'm not sure what you mean by "recursive loop", that's kinda an oxymoron, but I assume you mean a recursive call which functions the same as a loop.

Now, if you had tail call optimization you would not get a callstack error. So I'm not sure what your point is there... perhaps you have it backwards, since you say that recursive loops prevent callstack errors when they are really the cause of them?

Also, the above comment was my one asking about continuing the issue. ("Let's not jump away from this though...")

Also, you can implement a clearTimeout to prevent the callstack from growing too large.

But then the loop stops. So now you have to implement some kind of restarting mechanism, and that's just way too complex.

Baconman321 (560)

@fuzzyastrocat Yeah, but it would work. Also, settimeout doesn't work well over 1 hour for some reason (or not at all). Uhm, I think TCI is not in use, but basically JavaScript throws a callstack error because you are calling something over and over without doing anything (which is like TCI, otherwise IDK from what I heard it's basically something that prevents the call stack from going oof... ok yea I saw TCI doesn't throw an error nvm). I mean recursive loop as in recursive function (sorry). Also, is there a resource that you use to learn this? I would like to read up about this myself...
IDK why anything inside a recursive function prevents a callstack error either...

fuzzyastrocat (1471)

@Baconman321 Okay, your understanding of TCO (not TCI) is a little incorrect. Let me try to clear things up:

All that tail call optimization (TCO) does is convert a recursive call into a loop. That's it. So if you have this code:

function x() {
  // do things
  x();
}
x(); // this is the call

TCO will convert that x(); at the end (the one by the // this is the call comment) into this:

while(true){
  // do things
}

This is how TCO prevents the call stack from being overflowed — it converts the function call into a standard loop, which doesn't need a function call and therefore doesn't overflow the call stack.

Uhm, I think TCI is not in use, but basically JavaScript throws a callstack error because you are calling something over and over without doing anything

IDK why anything inside a recursive function prevents a callstack error either

This has nothing to do with it. If you waited long enough, your original example (with a console.log in it) would throw a callstack error. It's just that it takes wayyyyy longer to run because it's having to actually do something (rather than just an empty loop), so it doesn't hit that callstack error as quickly. Because writing to a buffer is incredibly slow, console.loging every loop means it would take a long time. But here's an example that will hit a callstack error quickly, and it does do something in the loop:

let i = 0;
function x(){
  if(i % 100 === 0) console.log("e");
  i ++;
  x();
}
x();

This simply logs e every 100th iteration, instead of every single iteration, so it doesn't do as much buffer writing and therefore isn't as slow. But, it still gets a callstack error.

As for a resource, I can't really give you one since I'm not sure. These are some very fundamental misunderstandings of how function calls work, so I'm not really sure of a good resource to clear it up. Really, if you're determined enough and you have access to google you'll be able to find anything. (It's how I've done it.)

So, just remember: a function call is not "linked" to what's inside a function. If you recursively call a function over and over, it will run into a call stack overflow. It just depends how long it will take.

However, if TCO is being used, then your function call will actually be converted into a loop and therefore it won't run into a callstack error (loops don't use the callstack). Note, however, that not all functions can be converted into a loop: the function call must be at the very end of the function ("tail call") to be actually made into a loop. If you're wondering why, I'd suggest reading up on tail call optimization. A google search should suffice.

Baconman321 (560)

@fuzzyastrocat IDK what you mean by writing to a buffer, why does console.log need a buffer? Could it be that it needs to wait a bit before accessing the i/o stream (to the console, or is I/O stream not in a console)? I'm confused...

fuzzyastrocat (1471)

@Baconman321 The "buffer" I speak of is "the place where console.log outputs everything". No, it does not need to wait before writing (that would be waiting for no reason). It's just that console.log is a very slow operation compared to, say, math (console.log("e") will run very slowly compared to 1 + 2 * 3 - 4 / 5 + 6 * 7 - 8). As for exactly why it's slow, that's getting very deep down and I won't explain it here because I think it would just make things more confusing.

Now, everything is relative — this "slowly" is only on the scale of milliseconds (or even microseconds), so you won't notice it if you just console.log once. But since you're console.loging hundreds and thousands of times in your recursive example, the slowness compounds and becomes extreme.

Baconman321 (560)

@fuzzyastrocat Oh ok. I can't find anything explaining what a buffer is used for in console.log... so IDK how I'm gonna know that.

fuzzyastrocat (1471)

@Baconman321 The terminology "buffer" might not be used specifically, but "buffer" itself means "a temporary area of memory to transfer or process data". So while that might not have been the perfect terminology to describe it, I think you can probably get the idea from it: the buffer I'm talking about is where all the things that console.log outputs are stored.

That's something that I think might help a lot — don't necessarily look for things exactly as they are, think about how the connect. For instance, console.log outputs things from your code. But where does it output? Well, there must be some kind of temporary memory that stores the things which console.log outputs. This could be called a buffer, it could be called a stream — it depends on the implementation, but both can convey the same meaning.

Coder100 (12643)

i've never seen buffer outside of C and C++ that's a first for me lol

I know that a buffer can be used for higher level things, but I never considered the dom console a real 'terminal' lol @fuzzyastrocat

Baconman321 (560)

@fuzzyastrocat Yeah. I know chrome has a "preserve-log", so it must be something with more memory than something meant to be very "temporarily" reserved...

Baconman321 (560)

@Coder100 I highly doubt buffer will only be in low (or lower) level languages. In fact, there is Buffer() in nodejs (I believe). A lot of times, you can make your own buffer (in concept).

fuzzyastrocat (1471)

@Coder100 Eh I mean, with modern browsers it's basically a real terminal. Like I said though, depends on the implementation.

fuzzyastrocat (1471)

@Baconman321 Yeah, depends on the browser/implementation. Also Chrome might use a buffer, and then move that memory into more permanent memory (the other purpose of buffers).