The Modern JavaScript Tutorial Series; Part 1
xxpertHacker (780)

Author's note:

This tutorial series should explain everything that I have learned in my time using JavaScript.

I've known the language for less than a whole year.

Part one was written in less than 12 hours, and may contain plenty of intricate text, or may otherwise need to be fixed; please leave a comment on what sections should be improved or elaborated upon.

Lastly, part one is already incredibly long, despite having been written in such a short period of time and containing so little content, any suggestions as to what should be moved out of part 1 would be appreciated.

Please upvote to encourage the release of the next parts!


The Modern JavaScript Tutorial

Part 1: background & basics

This tutorial series should thoroughly explain the following:

  • What JavaScript is
  • What you can do with JavaScript
  • How you should actually structure/write your code
  • JavaScript best practice
  • How JavaScript is executed
  • Tips on programming/scripting in general, that apply to almost any language and/or situation

Why am I making this tutorial series?

There are well over a thousand tutorials that attempt to teach JavaScript, so why waste my time doing it myself?

This is intended to teach modern JavaScript, not how they did it 15 years ago, not how they will use it 15 years in the future, but how it should be written today, in the modern-day and age.

If you want to learn how to write ancient code, just search up "JavaScript tutorial" on any browser. They're all outdated, after discussing this with someone, I realized that the only way to break the chain was to do myself, leading us here.

I will not explain everything, because there is a whole lot of stuff that should've never been known in the first place, or even added.

Now, let's get back to what we all came here for: JavaScript.


What is JavaScript?

Developed in 1995, JavaScript is a simple weakly and dynamically typed, multi-paradigm scripting language.

JavaScript is an imperative language, meaning that authors define both, how something is done, and what is done. This is in contrast to declarative programming, where one simply gives instructions on what must be done, leaving room for how that goal should be reached.

A massive amount of JavaScript control flow revolves around an event loop, meaning that JavaScript is an event-driven language.
While running on a single thread, an implementation's event loop should buffer events to which JavaScript may choose to respond to.
This execution is concurrent, but not parallel. Parallel execution requires threading.

By default, JavaScript often runs on a single thread, but modern JavaScript is not limited to only using that thread.

What makes the language so unique is that JavaScript, and JavaScript alone, has for the longest time, been the only language that allows developers to dynamically manipulate stuff on the web.
No other language is allowed to fire HTTP requests from within a browser, besides the browser's native code.
No webpage can create interaction between a user.
This has essentially forced developers for years to learn JavaScript.

This has finally changed, as another language is on the web nowadays, and it's gaining popularity, but we're here to focus on JavaScript. I'll create a post for that language another time.

Finally, browsers are supposed to be able to execute JavaScript that was written 15 years ago and today's code. This means that you can write a page today, and it'll still work years from now.

This is both, one of its greatest strengths... and its greatest downfall.
Because backward-incompatible changes essentially can't be made, JavaScript has accumulated a large amount of less than desirable features.

Miscellaneous:

  • JavaScript is abbreviated "JS"
  • The file extension for JavaScript since 2015 has been ".mjs" (the older one was .js)

Who makes JavaScript?

JavaScript was originally written by Brendan Eich, a technologist who had co-founded Mozilla and is currently CEO of Brave Software.

But JavaScript isn't maintained by a single man.
All JavaScript implementations are strongly encouraged to conform to the ECMAScript specification (ES262). The ECMAScript committee is the group responsible for developing the core language.

Implementations that don't conform to the specification are considered to simply not support JavaScript properly.

(That link above points to the actual ES262 specification. It's massive, over a few MB, and it reaches over 20k lines of HTML alone, as of this writing; opening it may be slow)

Usually, the terms ECMAScript and JavaScript are interchangeable, but JavaScript is far more prevalent.


The syntax + language

How can you learn a language, without seeing the actual code‽

Here's a "hello world" script, written in JavaScript:

console.log("Hello, world!");

One line. Simple.

What does this show you, right off the bat?

JavaScript is a C-family language, it takes inspiration from C, Java, and other C-family languages.

Notice the dot in between the words console and log, this is a property lookup, on an object. JavaScript is an object-orientated language.

Notice that there is nothing else, no importing the console object, no main function where the control flow starts, it's just top to bottom, and some stuff is already defined by the runtime.

Thus, we can infer that JavaScript is, by default, unstructured. It was originally intended for amateurs who know little about computer science. This is especially problematic, considering how many developers are only given two options: JavaScript or nothing.

JavaScript is intended as a simple language to understand. A beginner does not need an understanding of formal computer science, nor do they need an understanding of the low-level details about hardware in order to grasp JavaScript. The language is generally considered to be easier to learn than some other popular languages, such as Java, C#, C++, Ruby, PHP, Go, R, Swift, although, it may be harder to learn than some languages, such as Python, or some command line shell languages.

Because JavaScript is dynamic, weakly typed, and so unstructured, generally, it becomes exponentially harder to maintain any large scale project that is written in the language. It can happen, it's just unnecessarily hard and unmaintainable, or it's incredibly poor in performance.

If you write 50 lines of terse, simple code, it's not that bad.
But, since many users actually are amateurs, trying to debug even a relatively small project that reaches even 400 lines can become difficult.

These problems have caused a whole plethora of languages and pseudo-languages to be created that transpile to JavaScript.
Some of the languages, take TypeScript for example, simple build upon JavaScript, attempting to make it more structured and sensical.
Others, for example, Dart, are whole languages, with their own runtime semantics and structure, that just happen to also have language-level support for transpilation to JavaScript.

In reality, these languages aren't just transpiling to JavaScript, they are misusing JavaScript as if it were a compilation target.


Datatypes

This part gets interesting, considering how JavaScript is a weakly and dynamically typed language.

JavaScript has types, and variables can be reassigned with a new value of a different type.

But just because a variable can be reassigned with a different type doesn't mean that you should change the type.

There are plenty of types that can be represented in JavaScript.
There is a major division that needs to be made: objects and primitives.


Datatypes: Primitives

Primitives are simply raw values. They generally don't have properties, as those are unique to objects, and they are immutable.

Here's an incomplete list of JavaScript primitives and their subtypes:

  • Numbers
    • Floating-point numbers
      • 32-bit float
      • 64-bit float
      • NaN (Not A Number)
      • Infinity
        (There is a separate NaN and Infinity for both, 32 bits, and 64 bits, but you'll never be able to distinguish between them)
    • Integers
      • 8-bit (Signed and unsigned)
      • 16-bit (Signed and unsigned)
      • 32-bit (Signed and unsigned)
      • BigInts (arbitrarily long integers)
        • 64-bit (Signed and unsigned)
  • Strings
    • Template strings
  • Booleans
  • Undefined
  • Symbols
  • Records
  • Tuples

Numbers are generally merged into one category, as there is little to distinguish between the sizes and types of numbers.
For example, performing division between two integers may result in a floating-point result. Using 5 and 2 as an example: 5 / 2 = 2.5.

There are operations to truncate or floor a floating-point number, removing the decimal place, resulting in an integer.

Once one has exceeded the "safe" precision capacity of a number, precision is lost, results are heavily rounded, and arithmetic becomes lossy.

Generally, the only real distinctions that need to be made are whether the value is a number or what is known as a BigInt.

BigInts are integers that are designed for maintaining precision when working with massive values.
For example, the division between two BigInts exceeding over one thousand digits should have perfect precision.

In contrast to numbers, which are represented as their literal digits, BigInts are similar, yet they end with the character 'n'.

BigInts can only be integers, meaning that operations performed involving two BigInts have any would-be decimals truncated.
Once again, using 5 and 2 as an example, we would add the character 'n' as a suffix to every value, and get a BigInt as a result: 5n / 2n = 2n.

The reason for the distinction between "plain" numbers and BigInts is that mixing them will always result in a runtime type error being thrown.

Strings are similar to arrays of characters, and like objects, they have properties, namely, the .length property, and indexes corresponding to each of their values.

There is no "character" data type in JavaScript, indexing into a string simply yields another string, with a length of one.

Strings are generally viewed as one type, yet a distinction can be made between literal strings: strings and template strings. Template strings will be explained in more depth later.

String literals are denoted as their contents, beginning and ending with one of the three different JavaScript quotes. Note, the ending and beginning must be the same character.

The three characters that a string may start and end with are the apostrophe ', the quote ", and the grave accent mark `.

The three different characters are often called the "single quote," "double quote," and "back-tick
" or "backquote," respectively.
Examples:

"That's the novel that had made me reconsider what I was doing";

'The criminal broke into the store and said, "hand over the money!"';

`And the reader groaned, "couldn't this tutorial have been shorter?"`;

Note that template strings use the ` character, whereas the other two strings are just plain strings.

Booleans represent conditions. They are denoted by the literal tokens true or false in JavaScript.
Examples:

true;

false;

On their own, they are relatively useless. But when operated on, in more complex pieces of code, they can be incredibly valuable. Oftentimes they are involved in statements that result in conditional execution of code.

Undefined is JavaScript's representation of a non-existent value.
For example, accessing out of bounds memory in JavaScript generally results in undefined, as opposed to a runtime error, which is common in many other languages.

It may be denoted by the built-in property undefined, or any operation that may yield the value.

An example of accessing the global property:

undefined;

Symbols are unique values that are only equivalent to the original value.
They take a role similar to enums in other C-family languages.
They have no literal syntax, instead, they must be constructed from the Symbol constructor.

Symbol("This is a unique value") === Symbol("This is a unique value");

The result of the above expression would be false because although the constructor was called with the same value, the symbol that was returned is different.

Symbols are primarily used with objects.


Datatypes: Objects

Objects, in contrast to primitives, are mutable.
Similar to Symbols, the result of comparing two objects results in true only when comparing an object to the original object.

Objects have properties, when a property is a function, that when called, operates on the object that holds the function, the function is not called a property, but a method.

Objects are used to represent more complex structures than what the primitive data types can.

They can represent just about anything, here are a few:

  • Null
  • Objects
    • Arrays
    • Functions
      • Functions sub types (listed later)
    • Maps
    • Sets
    • ArrayBuffers (raw memory)
    • Exceptions
    • Structs
    • Files
    • Directories
    • Dates (Time)
    • Events
    • GUI Elements
    • References (to other objects)
      ...

(If you haven't caught on, the list could go on forever)

Null is separate from the rest of the objects.
Sometimes it is considered a primitive, other times it is considered to be an object.
JavaScript's typeof operator claims that null is an object, while some say that this is simply a known mistake.

JavaScript uses prototypical inheritance, instead of the common class-based inheritance common to other C-family languages.

Null is often used in the same ways that undefined is used, that is, to represent a missing value.

Null is denoted by the literal token null.

null;

All objects inherit from null, via what is known as the "prototype chain."

All objects must be constructed, that is, to call a function, save for two specific types of objects, simple objects, and simple arrays.

Object literals start with the character {, and end with the character }.

Take, for example, a point on a graph. A point is two dimensional, and cannot be represented using a primitive.

({
    x: 4.0,
    y: 9.0
});

Array literals consist of a list of values, separated by a comma, wrapped on both ends with a bracket.

[ "This", "is", "an", "array" ];

Objects will be covered in a later tutorial.


Variables

Variables are defined using one of two keywords, let or const, followed by a valid JavaScript name.

Here's a simplified pattern to define a valid JavaScript name: a chain of characters that does not include an operator, any whitespace, and that may not start with a numerical digit.
(character refers to any character in the UTF-8 encoding)

const creates an immutable binding to a value.

let creates a mutable binding to a value.

const x = 0;
const y = 1;
const z = x + y;

The value stored in z should be the numerical 1.
Now, if we were to attempt to change the value stored in the variable z, like so:

z = 3;

We would get a runtime error, as JavaScript is interpreted, whereas many other languages would give a compile-time error.

The other way to define a variable is to use let.

let name = "John Doe";

Now, if we reassign the variable, like so:

name = "Jane Doe";

It should run perfectly fine!


Functions

Functions are sections of executable code, exposed to JavaScript as objects.

Functions are can be categorized into plenty of different categories, here are a few common ones:

  • Named functions
  • Anonymous functions
  • Arrow functions
  • Generator functions
  • JavaScript functions
  • Non-JavaScript functions

If you think that's a lot, some of these may overlap, and it's not uncommon for them to overlap.
And I'm confident that there might be more added later.

Take a close look at the last two, recall that functions are only

exposed to JavaScript as objects

A function that is called from JavaScript may not necessarily be executing JavaScript code.

Some examples of non-JavaScript functions come from foreign function interfacing.

If you want to, you could look into Mozilla's old XPCOM language bindings, Node.js' C++ addons, or Deno's plugin system for some examples of JavaScript FFIs.

Generally, non-JavaScript functions are written in native code, and as a result, are much more performant, as they are compiled to efficient, direct machine instructions.
In contrast, JavaScript is interpreted and may be compiled, but even compiled JavaScript has quite some runtime overhead holding it back.

Functions in JavaScript are defined using one of three primary ways, with slight variations for different function types:

  • Naming a function using the function keyword
  • Assigning a variable with a function defined using the function keyword
  • Assigning a variable with a function defined using the arrow character combination

Examples:

function f(x, y) {
	// ...
}

const g = function (x, y) {
	// ...
}

const h = (x, y) => {
	// ...
};

The third function here is called an "arrow function," whereas the former two are often simply referred to as functions. Arrow functions are often more common than plain functions, depending on a script's author.

Take note, although these are how you declare a function, you main obtain a function through other means. For example, calling a function could return another function.

Functions are reusable sections of executable instructions, they are generally used to compute a value based on given values and return it to the caller, just like the mathematical sense of a function. Some functions are called purely for their side-effects. The difference will be covered in a later tutorial.

Functions may be declared almost anywhere in code, even in other functions, and they may access any variables that were declared in the scope that they were declared in, creating what is known as a "closure."

Once you realize that you've written a similar piece of code more than once, use a function.


Operators

Every JavaScript operator, as of 2020, using x and y as placeholders for potential values:

  • Arithmetic operators
    • x + y Addition / concatenation
    • x - y Subtraction
    • -x Unary numerical negation
    • x * y Multiplication
    • x / y Division
    • x % y Modulo
    • x ** y Exponentiation
  • Conditional operators
    • !x Boolean negation
    • x < y Less than
    • x > y Greater than
    • x === y Equality
    • x !== y Inequality
    • x <= y Less than or equal to
    • x >= y Greater than or equal to
    • x || y Or
    • x && y And
  • Bitwise operators
    • x ^ y Exclusive or
    • x & y Bitwise and
    • x | y Bitwise inclusive or
    • ~x Bitwise negation (x & 1)
    • x << y Signed left shift
    • x >> y Signed right shift
    • x >>> y Unsigned right shift
  • Miscellaneous operators
    • x = y Assignment
    • x, y Comma
    • Property lookups
      • Unconditional
        • x.y Dot
        • x[y] Bracket access
      • Conditional
        • x?.y Dot
        • x?.[y] Bracket access
    • Function calls
      • x(y) Call
      • x?.(y) Conditional call
    • ?? Nullish coalescing
    • new x Construction
    • x ? y : z Conditional
    • await x "await"
    • typeof x "typeof"
    • delete x.y deletion

Whew, that's quite a few operators, and I intentionally left some out.

The operators under "arithmetic" all yield a number when operating on two numbers, or a BigInt when given two BitInts. If both operands are strings, the addition operator (+), will yield a new string, which is the result of merging both of the operands.
The five basic mathematical operations (+, -, *, /, **) all operate on two floating-point numbers, without care as to whether or not an input was an integer or a floating-pointer number. Operations that would be considered mathematically undefined, for example, division by zero, will oftentimes simply return "infinity" or "nan."

The operator denoted by ** is equivalent to the mathematical exponentiation, usually written as a number, with another number as a superscript.

The modulo operator (%) computes the remainder of the division between their operands.
The mathematical function for computing the same would be the following:

f(x, y) = y - x * ⌊ x / y ⌋

Or, in JavaScript:

const modulo = (x, y) => y - x * Math.floor( x / y );

All operators listed under "Conditional" are operations that return boolean values, which are represented as true or false.
Some of them may cast their operands to boolean values, namely !, ||, and &&.
Values are considered "truthy" or "falsy," meaning that they aren't the literal boolean true or false defined by the core of the language, but they can be used equivalently, or similarly.

All of the bitwise operators, with the sole exception of the unsigned right shift (>>>), all specifically operate on 32-bit signed integers. When used on a floating-point number, they will truncate the decimal.

All of the bitwise operators, with the same exception being the unsigned right shift, are usable on BitInts, if, and only if, both operands are BigInts. (BigInts will be covered in the next tutorial)

The unsigned right shift requires an understanding of computer science and hardware representation of numbers.
It will convert its operands to unsigned 32-bit integers, removing decimals, as 32-bit conversions do, but also reinterpreting the operand as unsigned, meaning that negative values become positive.

Each of the operators under "miscellaneous" are oddballs that deserve separate explanations.

Every operator listed above will be gone over in-depth in the next tutorial.


What you can do with JavaScript

The core language of JavaScript isn't very useful, but instead, it's the fact that JavaScript is given so many functions and objects that make it useful. Essentially, JavaScript is very bloated.

Every built-in object has a method or property for almost anything that you could want to do with it.

If you want to re-encode a string from, say a Chinese encoding, to say UTF-8, there's a built-in object dedicated to just that.

Want to get the current time? There's an object dedicated to it. Another is being proposed as of this writing!

Want to send an HTTP request to get a resource? There are an object and a function both dedicated to it.

What to modify a web site's document? There's the entire document object provided in every browser for doing just that.

Want to work with raw binary data? JavaScript has you covered, there is a standard interface for doing just that.

Are you trying to perform some mathematical transformations involving trigonometry? There's a standard object quite literally called "Math," dedicated to just that.

You can probably get where I am going with this, most algorithmic and low-level details are already taken care of for you.

Generally, there will be a proper tool for the job.

Examples of actual JavaScript code will be given in later tutorials.

How you should actually structure/write your code

Recall that JavaScript is a dynamically-typed language, in reality, its dynamic typing is rarely, if ever, helpful to developers.

Being dynamically-typed, variables and functions are not explicitly annotated with type meta information, which reduces code size, but also reduces readability, as one cannot read over someone else's code and easily determine what a function is intended to accept and output.

Oftentimes, comments and documentation are used to convey this information to other developers, removing whatever tradeoff in code size that there was.

JavaScript has type errors that can still occur from misusage of types.

And, to top it off, most implementations will be able to JIT compile statically typed code, whereas duck-typed code will be interpreted.

Try to write somewhat statically-typed code, that is clear in its intention, and with a few comments to explain modules of code.

Don't use a loop or a large structure, if there is already a built-in function dedicated to a purpose, oftentimes it is easier to recognize a well-named function when compared to a few lines of code intended to compute a value or execute an operation.

You are viewing a single comment. View All
CodeLongAndPros (1579)

@xxpertHacker

weakly and dynamically typed, multi-paradigm scripting language.