/ 

Ref/control structures!

How to Code: The Fundamentals, pt 1

URL copied to clipboard
By AstroMacGuffin dated  last updated 

Writing code is a lot like having cyber minions As moderator and volunteer on the JavaScript Mastery discord, I've noticed a trend. The people who are in the most distress when they come for help, are the people who jumped head-first into a tutorial that was far too advanced for their skill level. They don't even know the fundamentals of JavaScript and they're trying to build a TikTok clone or a medical-grade messaging app.

Some of them don't even want to learn, and are eager to say so.

For the rest of you: welcome back to basics. There's a lot of fundamental knowledge you may have missed. If you're not really sure what variables are; if you have no idea how to create a function; and if you've never heard of the terms "control flow" or "scope" before, everything you do will make more sense after you grok this information.

If someone (such as myself) sends you this link, congratulations! That person thinks you'll be a better programmer with just a little bit of high-density reading!



People who don't write code might think it's magic, but it's just language, and logic. Anyone can pick it up, because our species excels at both language and logic. So let's do this!

If you feel like I skipped some crucial information, maybe try part 0 of "How to Code: Fundamentals".

This Lesson

First you'll learn about control flow and scope. Then you'll learn about variables, a little bit about data types, and then functions.

Control flow is a programming concept that means you are in control of which instructions get done in what order. In this lesson we'll mostly be focused on the simplest control flow scheme: line 1 happens, then line 2 happens, then line 3, etc. until the program is complete. I'll use the if statement to demonstrate a common control flow feature of programming.

Variables are nametags for pieces of data. There are many types of data. Variables are not "typed" in JavaScript, meaning a variable can point to data of one type one moment, and another type of data the next.

Functions are nametags for code, but they're also more. Functions take input and send output, and can be any level of complexity, making them entire mini-programs within your program.

Control Flow

"Control flow" refers to things you can do in programming that control which instructions get sent to the CPU in what order. Take your simplest code sample, a traditional Hello World in browser-side JavaScript:

const text = 'Hello World';
alert(text);

It has two lines of code:

  1. The first line of code creates a const -- a "constant", which is a type of variable. The const named text will hold the text, "Hello World".
  2. The second line of code calls a function that's built into JavaScript, alert. It passes input to that function by placing the variable name text between the parentheses.

alert is a well-known function that pops up a simple message box on the web browser. What should the message say? That's the input you provide when you call the alert function. More on functions later. The upshot for now is that, when you run this code, a pop-up, saying "Hello World", should appear.

But how does the alert function know anything about a variable named text? That's thanks to control flow, and scope, a second feature under your control, which will be explained soon. Suffice to say for now: control flow goes from top to bottom, line by line. Since the definition of text came before the call to alert, text existed when alert was called.

To explain scope better I'll introduce the if statement. This code contains a common mistake:

let color = 'blue';
if (color === 'blue') {
  const text = 'Hello World';
}
alert(text);

What's happening here? First, let me explain operators. A single = equals sign is known as the "assignment operator". It assigns a value on the right hand side, to a variable on the left. So on the first line of code above, a variable called color is being assigned the value "blue".

But on the next line of code, we have three equals signs === back to back. This is one of the many comparison operators. The === operator means "is exactly equal to".

And of course, on that second line of code above, we've met our first if statement. If the conditions in the parentheses evaluate to true, then the code in the curly braces will be executed. This is a control flow feature. If the code above was run with no modifications, the following line of code would always be executed because color will always be exactly equal to "blue" (since we just defined it as such in the line above):

  const text = 'Hello World';

But since that's all the code in the curly braces after the if conditional… what happens next? After any control flow structure in your code has met its end, control flow always resumes its previous state, meaning in this case, we go back to doing things line-by-line, top to bottom…

Scope

…Which would be great if not for the bug. It's a very common bug: a variable is being used "out of scope".

robo-mouse-clickSmall.jpg

Let's look at the whole thing again:

let color = 'blue';
if (color === 'blue') {
  const text = 'Hello World';
}
alert(text);

If you ran it in a browser, no pop-up box would appear, and you'd be told Uncaught ReferenceError: text is not defined in the Developer Tools console. (To open the console, right click almost anywhere in the page, and choose Inspect -- e.g. Inspect element.)

So why isn't text defined? It's because it fell out of scope. Basically, each set of curly braces creates a context one level "inner" compared to the context of where you typed those new curly braces. Using our example, the const text = 'Hello World'; line is in a context one level "inner" vs everything outside those curly braces. Each of these contexts has its own scope.

Variables from "inner" levels of scope can't be used in "outer" levels of scope. Variables from "outer" levels of scope can be used in the "inner" scope code. Just remember, in any given situation, the so-called "outer scope" is often an "inner scope" compared to the overall global scope, that is, the outermost part of the control flow. The variable will fall out of scope when the block that created it, ends. So long as that doesn't happen before control flow reaches the part of the code where you want to use the variable -- the variable will be available.

In the example code, the if block ended with the closing curly brace } and so the const text no longer existed when alert was called. That's scope.

Variables & Data Types

Variables (aka vars) are nametags for data. This alone is a powerful feature. Combining variables with other variables, and combining variables with functions, represents almost 100% of programming, so this section is critical.

Someone recently asked in the JavaScript Mastery discord why variables were important, so here's a bullet list of reasons off-the-cuff:

  • Configuration (the easiest config system for any piece of software is a list of config vars at the top)
  • Software Speed (when the same value is referenced multiple times, a variable is faster)
  • Function Arguments (without which functions would be far less powerful)
  • Readability (even if you're the sole maintainer of a program, you'll thank yourself later if your code is readable)
  • Debugging Steps (easier to find the mistake in your code if that mistake leaves a named imprint you can output)

First thing you need to know is the three types of variable, identified by the keyword used to declare them: const, let, and var.

  • const - The variable must be defined. Can't be changed. Normal scope rules apply.
  • let - The variable need not be defined. Can be changed. Normal scope rules apply.
  • var - The variable need not be defined. Can be changed. Special scope rules apply.

Declaring & Defining Variables

In JavaScript, as with most languages, you have to declare a variable before you can use it. You declare a variable by issuing one of the above three keywords, plus the variable's name.

let foo;
var bar;

You can go one step further in the same line, defining the variable by using the assignment operator and some value on the right of that.

let foo = 2;
var bar = 'The quick brown fox jumped over the lazy dog.';

Variables made with const must always be declared and defined at the same time.

const foobar = 'Two quick brown foxes jumped over three lazy dogs.';

Why var is Useless

So what are these "special scope rules" attached to var? I'll borrow the example from MDN:

var x = 1;
if (x === 1) {
  var x = 2;
  console.log(x);
  // expected output: 2
}
console.log(x);
// expected output: 2

The special scope rule is that var does nothing special. You still have to declare the variable in the outside scope, and even the second var in the if block (var x = 2;) offers no special features. Compare the above, to the below:

let x = 1;
if (x === 1) {
  x = 2;
  console.log(x);
  // expected output: 2
}
console.log(x);
// expected output: 2

They're practically identical (the second example is shorter by one keyword) and they get the same result. For this reason, JavaScript coders tend to drift toward never using var. Just remember: const for constants that can't be changed, let for everything else.

In all cases, you need to remember to declare your variables in proper scope, so that the variable will be available when it's needed, and released from memory when it's not needed anymore.

Data Types

Variables in JavaScript both do, and don't, have a "type". Every piece of data has a "data type", and yet, variables are not "strongly typed" in JavaScript -- meaning any variable can point to any type of data -- no variable is locked into the type of data it was first assigned. Still, data types are important; eventually, you will find yourself often querying the data type of a variable as part of an if or switch statement, to make more powerful functions. Long story short, we need a quick word on data types.

For a beginner, here's what you need to know about types. There are two categories.

Category 1: "primitives"

  • Data type boolean stores either true or false
  • Data type string stores text of whatever length
  • Data type number will be reported for every number you'll ever use
  • null and undefined data types, explained in the next lesson

Category 2: "objects"

  • variables created using the let obj = new className() syntax
  • variables created using the let obj = { field: 'value', property: 1 } syntax
  • arrays, regular expressions, etc

Why care? Because category 2, the objects, are always handled "by reference". This means if you assign it to another variable, both variables point to the same object, as shown here:

let foo =  { field: 'value', property: 1 };
// foo.property is currently 1
const bar = foo;
bar.property = 2;
console.log(foo.property); // output is 2

Only the object types do this.

green_computer-gutsSmall.jpg

Functions

Functions give nametags to code, but also much more. Functions can take input, and can send output, like little mini-programs. Most of the best functions are small, single-task functions.

Let's take a look at a simple function that has both input and output:

function add(a, b) {
  return a + b;
}

The function is called add and it adds two numbers, that are given to it when it's called (the inputs). It adds the two numbers, returning (outputting) the result.

Let's break this down. Obviously function add creates a function called add. The (a, b) is where things get interesting. This is called the function's "signature". This is how you define the inputs for the function. In this example, we're defining two variables, a and b, that will be available inside the function. The values of a and b depend on whoever writes code "calling" this function. (You "call" a function by typing its name with a set of parentheses after it.) For example, if someone writes:

let c = add(6, 18);

…then, inside the function, a will equal 6 and b will equal 18. Notice that the person calling the function used the same number of inputs as the signature specified.

And the last thing to understand about the function definition for add, is the line starting with return. The return statement is a function's output. If the function is used thusly: let c = add(6, 18); …you might notice the let c = part and wonder, how does it know what c should equal? It knows because of the return statement inside the function. This example, our add function, doesn't even create a variable to return -- it just returns the direct result of a math operation. Outputting values of whatever complexity in this way is fine.

earth-hexgrid.jpg

Let's look at a slightly more complex example. Let's say you have a simple project for logging and storing log entries.

const ERR_LOGSTOR = 'Invalid storage medium'; // an error message
const logStorage = []; // an array where we store our logs
// function signature with a default value
function storeLog(logMessage='Message not specified') {
  if (Array.isArray(logStorage)) {
    logStorage.push(logMessage);
    return 0;
  }
  else {
    return ERR_LOGSTOR;
  }
function logMessage(logMessage=undefined) {
  if (logMessage === undefined) {
    console.log('An undefined message was not logged');
  }
  else {
    console.log(logMessage);
    const status = storeLog(logMessage);
    if (status === ERR_LOGSTOR) {
      console.warn('Log storage error: ' + status);
    }
  }
}
logMessage('System started');

Let's break that down:

const ERR_LOGSTOR = 'Invalid storage medium'; // an error message

Whenever you see two slashes // it means that the rest of that line is a "comment". This is how programmers put documentation into the code itself. Code can also be "commented out" by putting two slashes at the beginning of the line. Code that's been "commented out" is completely ignored when you run the program.

const logStorage = []; // an array where we store our logs

Two square brackets [] means "create a new, empty array". So when we begin this program, logStorage will be assigned an empty array.

function storeLog(logMessage='Message not specified') {

This is the function declaration and the beginning of its definition. The signature is one value this time, but the value has a default. Since the value has a default, you can skip that part of the signature when calling the function: it could be storeLog('I am a log message') or it could be storeLog() with nothing between the parentheses. If nothing is between the parentheses, the function will proceed as if you had typed storeLog('Message not specified').

  if (Array.isArray(logStorage)) {
    logStorage.push(logMessage);
    return 0;
  }
  else {
    return ERR_LOGSTOR;
  }

In the above snippet, first we do a sanity check -- meaning we check the code's situation to avoid producing an error. In this case, that means making sure the logStorage variable is an Array. If it is, we add the logMessage to the end of the logStorage array and output a 0 as our function's return value. If logStorage is not an Array, we do nothing except return the error message defined at the top of the script.

A quick additional note about return: using return also exits control flow from the function, in addition to sending output. Notice you can have multiple return statements in a function. Control flow will only arrive at one of them, but you can manage this with features like the if statement.

}

This ends the function. Every code block begins with { and ends with }. This applies to control flow structures such as the if statement, as well as functions, classes, etc.

function logMessage(logMessage=undefined) {

Once again we have a function declaration with a single parameter as its signature, and once again the parameter has a default value.

  if (logMessage === undefined) {
    console.log('An undefined message was not logged');
  }

Here we're doing some error trapping: we don't want to log nothing, but we do want to log the fact that nothing was logged. (Ah, business logic.)

  else {
    console.log(logMessage);

Whenever you use if, you can use else right after the if block ends. The if block executes if the condition in parentheses is true; the else block runs otherwise.

    const status = storeLog(logMessage);

Here we're using the previous function, storeLog. Yes, you can use functions within functions. If doing task A means also doing task B, you'll probably find yourself calling one function from within another.

    if (status === ERR_LOGSTOR) {
      console.warn('Log storage error: ' + status);
    }

When we ran the storeLog function we also grabbed its return value. Now we're checking it against our known error messages, and if there was a match, we're warning the console operator there was a problem.

  }
}

Here we're closing the else case and closing the function.

logMessage('System started');

Finally we set off the whole chain of events by calling logMessage to log a message that the system has started.

Move on when you're ready to part 2 of "How to Code: The Fundamentals".

🔍

Valid HTML!Valid CSS!Powered by Node.js!Powered by Express.js!Powered by MongoDB!