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
I flipped a coin between two spaces and tabs and got two spaces, so that's what we are using.
Bruh -_-
I flipped a coin between two spaces and tabs and got two spaces, so that's what we are using.
Ahh I see so for every line I flip and...
function main() {
console.log("prepare");
console.log("for");
console.log("the");
console.log("BEST");
console.log("CODE STYLE");
console.log("EVER");
console.log("AWWW YEAAHH");
}
function main () {
console.log("everyone" );
console .log( "should") ;
console.log ("use" );
console.log( "this" ) ;
console . log( "style!" ) ;
}
HAHA YESS XD
@fuzzyastrocat
console .log("indeed!" );
console .log("Sorry for the bad tutorial!" ) ;
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.
@Baconman321 ah yes, I did forget a lot of things lol
@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!
@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).
true, typically you would use recursion to achieve that @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
.
@fuzzyastrocat Yeah. Or it goes really really fast (depends on how you implement it). I think coder100 meant that.
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.
@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.
@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.)
wait is requestAnimationFrame
not just recursion but without stack overflows? @fuzzyastrocat
@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.)
@fuzzyastrocat hm alright
ill read up on its implementation xD
@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.
@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.
be patient, the threshold is like 10k or smth @Baconman321
@fuzzyastrocat I'm using chrome. Still, it goes too fast for me, so I prefer to use requestanimationframe.
@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?
@fuzzyastrocat @Coder100
lol I've always just done setInterval('draw()', 1)
@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
.
@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?)
@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).
@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 :(
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.
@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...
@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.log
ing 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.
@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...
@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.log
ing hundreds and thousands of times in your recursive example, the slowness compounds and becomes extreme.
@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.
@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.
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
@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...
@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).
@Coder100 Eh I mean, with modern browsers it's basically a real terminal. Like I said though, depends on the implementation.
@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).
I haven't found much use for destructuring
are u serious
// you can do this
let a=2;
let b=1;
[a, b] = [b, a];
console.log(a);// 1
console.log(b);// 2
that's enough for me
Scoping is mucked up in functions. Normal function use this in the scope of the caller, however arrow functions use this in the scope of the callee, a.k.a the sane way https://testmyspeed.onl/.
This will really help!
yay! Glad I helped you! @YashasShah
js is the best.
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.
http://slitherio.online/
function cool() {
console .log("this is a cool tutorial"); // <- i used a semi
console .log("keep up the gud work") // <- NOOOOOO!
}
cool!
i will try to do these suggestions! after i finish thinking about how stupid i am for using var
instead of const
and using tabs instead of 2 spaces :{
Edit: i use semis in python
@Coder100
Imho, this is gross:
const y = { key: "value", func: function () { return this.key; } // eww };
This is better:
const y = {
key: "value",
func() { return this.key; } // good!
};
And this is even worse:
const a = Object.freeze({ b: 6 }); a.b = 10; // <-- will silently fail
You know what, I think even ESLint has recommended this for the last decade:
"use strict";
const a = Object.freeze({
b: 6
});
a.b = 10; // <-- will throw a runtime exception
it throws an exception? did not know that lol
and yeah that first one is @xxpertHacker
@Coder100 Yeah, just like this does:
const x = 0;
x = 1;
Like, what are you doing with your life if you reassign a const like that, your entire script should crash.
here are my conventions: https://github.com/cursorsdottsx/my-code-conventions
why break it up? @CursorsDev
in strict mode trying to modify a frozen object will result in an error
Use path.join, not only is it going to make your life easier, it also allows you to make more complex paths.
er, the actual reason is for all platform support since linux and gnu uses /
while windows uses \
...
well yeah obviously, but you can also have more complex paths:
path.join("..", "whatev", "whatev");
Tabs > spaces
8 wide < 2 wide
@CodeLongAndPros ew but ok
i do use tabs for c++ @CodeLongAndPros
@Coder100 If you need more than 3 levels of indentation you should refactor anyways
@CodeLongAndPros why would you use spaces? Different editors interpret spaces the same but tabs differently. The only languages I know that care about whitespacfe are Python and Makefile. (also, are you still posting on your blog at https://codelongandprosper90.github.io/?)
lol yeah @CodeLongAndPros
wait no
not for c# smh @CodeLongAndPros
using System;
namespace myProg
{
class MyClass
{
public static void Main(string[] args)
{
// bruh
}
}
}
@Coder100 Why you always use arrow functions:
The function
keyword hoists, so don't use that unless in conjunction with let
or use strict;
,
and scoping is mucked up in functions. Normal function use this
in the scope of the caller, however arrow functions use this
in the scope of the callee, a.k.a the sane way.
@firefish I don't think it's always 100% sane though. I mean, put yourself in the shoes of a newbie JS programmer. What do you expect this to return:
const x = {
prop: 1,
func: () => this.prop
};
It seems like that should return 1, but no, it returns undefined
since there's no global prop
. However using a function() { return this.prop; }
will return 1 as expected, so I would argue that sometimes arrow functions can be more confusing.
When i look at this. Uhhh I dont understand anything. lol
I'm pretty sure it's better to use tabs because it requires less data-space.
yes, but usually it won't matter because if you were to go to production, your files would ideally be minimized @EIG520
Ur so lucky! My tutorials barely get recognition, and I've done a few on things like the file reader api for JavaScript, service workers, and even a PHP tutorial (which I think I'm going to heavily improve). The most upvotes I got was 7 (might have changed, but eh... I doubt it). ;(
Oh and also, you forgot the importance of comments...
@Baconman321 you don't have 10.000 cycles lol
@potatojs Doesn't matter. A 0 cycle person could make a tutorial better than a person with 100k cycles. It doesn't depend on cycles, it depends on skill.
@Baconman321 lol i didn't mean that your tutorials are bad, i'm sure they are good enough, but when people see a guy with 100k cycles they upvote immediately :)
@potatojs Sadly...
Nice tutorial!
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.
I always default to const
and never use let
with objects or arrays...
why not use let for objects and arrays? @programmeruser
@Coder100 no need
@programmeruser hmm any reason?
Pretty good tutorial! However:
That's not the case! They have two different roles and arrow functions will not work in some situations due to their
this
scoping. (Otherwise anonymous functions would have no purpose in the language at all.) Example:Also, I have to disagree with the first part on arrow functions. For the example:
For such a trivial example, the function
bad
is much more succinct. And, such a trivial function should not require a circular dependency, which is the only reason function hoisting is useful. So, I feel that simple one-liners (function = evaluate a single expression) should be expressed as arrow functions, while multiline functions (functions which would require() => { ... statements ... }
) should be expressed asfunction name () { ... }
.thanks! yeah, ig I didn't consider many edge cases in this tutorial, thanks for pointing those out, I'll add them @fuzzyastrocat
wait a second why doesn't
y
require binding? It's an anonymous function right? @fuzzyastrocat@Coder100 It works just fine like that, no binding needed. That's the point of (and my point with) the
function () { }
expression. (Try it yourself).@fuzzyastrocat amazing!
@Coder100 why did you make a tutorial on your own opinions -_-
cycle farmeralso for example:
defining functions in loops is fine if you want to programmatically define functions for some reason, just that it's not commonly needed
why is the avoid there, as you stated afterwards, for objects to be constant use Object.freeze()
const
!=Object.freeze()
at allpurely personal preference, using escape characters is arguably even better in most situations since it allows you to utilize all three
@realTronsi well the thing is this tutorial was inspired by the standard code style, which is absolutely trash
@Coder100 I just don't know what the purpose of this tutorial is lmao, like it doesn't really teach you anything??? Otherwise decently entertaining ig
@Coder100 @realTronsi Note that while systematically defining functions in loops is possible, it's very inefficient.
If it's absolutely trash, why did you make a tutorial on it? Shouldn't you make a tutorial on the best way to try to educate people? (Also if it was inspired by the standard code style then everyone's already doing this, so a tutorial isn't needed...)
@fuzzyastrocat I mean if you want to systematically mass define functions that's the way to go, you don't want to do something like
obviously systematically defining functions will be rarely useful though (I can't even think of a great example atm)
@realTronsi Yeah, definitely.