Chapter 2: Surveying JS
The best way to learn JS is to start writing JS. To do that, you need to know how the language works, and that’s what we’ll focus on here. Even if you’ve programmed in other languages before, take your time getting comfortable with JS, and make sure to practice each piece.This chapter is not an exhaustive reference on every bit of syntax of the JS language. It’s also not intended to be a complete “intro to JS” primer. Instead, we’re going to survey some of the major topic areas of the language.
Each File is a Program
Almost every website (web application) you use is comprised of many different JS files (typically with the .js file extension). It’s tempting to think of the whole thing (the application) as one program. But JS sees it differently. In JS, each standalone file is its own separate program. The reason this matters is primarily around error handling. Since JS treats files as programs, one file may fail (during parse/compile or execution) and that will not necessarily prevent the next file from being processed. Obviously, if your application depends on five .js files, and one of them fails, the overall application will probably only partially operate, at best. It may surprise you to consider separate .js files as separate JS programs. From the perspective of your usage of an application, it sure seems like one big program. That’s because the execution of the application allows these individual programs to cooperate and act as one program.Many projects use build process tools that end up combining separate files from the project into a single file to be delivered to a web page. When this happens, JS treats this single combined file as the entire program.
import statement or a <script type=module> tag, all its code is treated as a single module.
Though you wouldn’t typically think about a module—a collection of state and publicly exposed methods to operate on that state—as a standalone program, JS does in fact still treat each module separately. Similar to how “global scope” allows standalone files to mix together at runtime, importing a module into another allows runtime interoperation between them.
Regardless of which code organization pattern (and loading mechanism) is used for a file (standalone or module), you should still think of each file as its own (mini) program, which may then cooperate with other (mini) programs to perform the functions of your overall application.
Values
The most fundamental unit of information in a program is a value. Values are data. They’re how the program maintains state. Values come in two forms in JS: primitive and object. Values are embedded in programs using literals:"My name is Kyle." is a primitive string literal; strings are ordered collections of characters, usually used to represent words and sentences.
I used the double-quote " character to delimit (surround, separate, define) the string value. But I could have used the single-quote ' character as well. The choice of which quote character is entirely stylistic. The important thing, for the sake of code readability and maintainability, is to pick one and to use it consistently throughout the program.
String Interpolation
Another option to delimit a string literal is to use the back-tick` character. However, this choice is not merely stylistic; there’s a behavioral difference as well. Consider:
firstName with the string value "Kyle", the `-delimited string then resolves the variable expression (indicated with ${ .. }) to its current value. This is called interpolation.
Other Primitives
Other than strings, JS programs often contain other primitive literal values such as booleans and numbers:while represents a loop type, a way to repeat operations while its condition is true.
In this case, the loop will never run (and nothing will be printed), because we used the false boolean value as the loop conditional. true would have resulted in a loop that keeps going forever, so be careful!
The number 3.141592 is, as you may know, an approximation of mathematical PI to the first six digits. Rather than embed such a value, however, you would typically use the predefined Math.PI value for that purpose. Another variation on numbers is the bigint (big-integer) primitive type, which is used for storing arbitrarily large numbers.
Numbers are most often used in programs for counting steps, such as loop iterations, and accessing information in numeric positions (i.e., an array index). For example:
1 for the element in the second position, instead of 2, because like in most programming languages, JS array indices are 0-based (0 is the first position).
Null and Undefined
In addition to strings, numbers, and booleans, two other primitive values in JS programs arenull and undefined. While there are differences between them (some historic and some contemporary), for the most part both values serve the purpose of indicating emptiness (or absence) of a value.
Many developers prefer to treat them both consistently in this fashion, which is to say that the values are assumed to be indistinguishable. If care is taken, this is often possible. However, it’s safest and best to use only undefined as the single empty value, even though null seems attractive in that it’s shorter to type!
Symbols
The final primitive value to be aware of is a symbol, which is a special-purpose value that behaves as a hidden unguessable value. Symbols are almost exclusively used as special keys on objects:Arrays And Objects
Besides primitives, the other value type in JS is an object value. As mentioned earlier, arrays are a special type of object that’s comprised of an ordered and numerically indexed list of data:JS arrays can hold any value type, either primitive or object (including other arrays). Functions are values that can be held in arrays or objects.
me represents an object, and first represents the name of a location of information in that object (value collection). Another syntax option that accesses information in an object by its property/key uses the square-brackets [ ], such as me["first"].
Value Type Determination
For distinguishing values, thetypeof operator tells you its built-in type, if primitive, or "object" otherwise:
Declaring and Using Variables
To be explicit about something that may not have been obvious in the previous section: in JS programs, values can either appear as literal values (as many of the preceding examples illustrate), or they can be held in variables; think of variables as just containers for values. Variables have to be declared (created) to be used. There are various syntax forms that declare variables (aka, “identifiers”), and each form has different implied behaviors.var, let, and const
For example, consider thevar statement:
var keyword declares a variable to be used in that part of the program, and optionally allows an initial assignment of a value.
Another similar keyword is let:
let keyword has some differences to var, with the most obvious being that let allows a more limited access to the variable than var. This is called “block scoping” as opposed to regular or function scoping.
Consider:
age outside of the if statement results in an error, because age was block-scoped to the if, whereas myName was not.
It’s very common to suggest that
var should be avoided in favor of let (or const!), generally because of perceived confusion over how the scoping behavior of var has worked since the beginning of JS. I believe this to be overly restrictive advice and ultimately unhelpful. Both declaration forms can be appropriate in any given part of a program, depending on the circumstances.const. It’s like let but has an additional limitation that it must be given a value at the moment it’s declared, and cannot be re-assigned a different value later.
Consider:
myBirthday constant is not allowed to be re-assigned.
const declared variables are not “unchangeable”, they just cannot be re-assigned. It’s ill-advised to use const with object values, because those values can still be changed even though the variable can’t be re-assigned. This leads to potential confusion down the line:
Other Declaration Forms
Besidesvar / let / const, there are other syntactic forms that declare identifiers (variables) in various scopes. For example:
hello is created in the outer scope, and it’s also automatically associated so that it references the function. But the named parameter myName is created only inside the function, and thus is only accessible inside that function’s scope. hello and myName generally behave as var-declared.
Another syntax that declares a variable is a catch clause:
err is a block-scoped variable that exists only inside the catch clause, as if it had been declared with let.
Functions
The word “function” has a variety of meanings in programming. For example, in the world of Functional Programming, “function” has a precise mathematical definition and implies a strict set of rules to abide by. In JS, we should consider “function” to take the broader meaning of another related term: “procedure.” A procedure is a collection of statements that can be invoked one or more times, may be provided some inputs, and may give back one or more outputs.Function Declaration
From the early days of JS, function definition looked like:awesomeFunction and the function value happens during the compile phase of the code, before that code is executed.
Function Expression
In contrast to a function declaration statement, a function expression can be defined and assigned like this:awesomeFunction. Different from the function declaration form, a function expression is not associated with its identifier until that statement during runtime.
It’s extremely important to note that in JS, functions are values that can be assigned and passed around. In fact, JS functions are a special type of the object value type. Not all languages treat functions as values, but it’s essential for a language to support the functional programming pattern, as JS does.
Parameters and Return Values
JS functions can receive parameter input:myName is called a parameter, which acts as a local variable inside the function. Functions can be defined to receive any number of parameters, from none upward, as you see fit. Each parameter is assigned the argument value that you pass in that position ("Kyle", here) of the call.
Functions also can return values using the return keyword:
return a single value, but if you have more values to return, you can wrap them up into a single object/array.
Functions as Object Properties
Since functions are values, they can be assigned as properties on objects:greeting(), question(), and answer()) are included in the object held by whatToSay. Each function can be called by accessing the property to retrieve the function reference value.
Compare this straightforward style of defining functions on an object to the more sophisticated
class syntax discussed later in this chapter.Comparisons
Making decisions in programs requires comparing values to determine their identity and relationship to each other. JS has several mechanisms to enable value comparison, so let’s take a closer look at them.Equal…ish
The most common comparison in JS programs asks the question, “Is this X value the same as that Y value?” What exactly does “the same as” really mean to JS, though? For ergonomic and historical reasons, the meaning is more complicated than the obvious exact identity sort of matching. Sometimes an equality comparison intends exact matching, but other times the desired comparison is a bit broader, allowing closely similar or interchangeable matching. In other words, we must be aware of the nuanced differences between an equality comparison and an equivalence comparison. If you’ve spent any time working with and reading about JS, you’ve certainly seen the so-called “triple-equals”=== operator, also described as the “strict equality” operator. That seems rather straightforward, right? Surely, “strict” means strict, as in narrow and exact.
Not exactly.
Yes, most values participating in an === equality comparison will fit with that exact same intuition. Consider some examples:
Another way
===’s equality comparison is often described is, “checking both the value and the type”. In several of the examples we’ve looked at so far, like 42 === "42", the type of both values (number, string, etc.) does seem to be the distinguishing factor. There’s more to it than that, though. All value comparisons in JS consider the type of the values being compared, not just the === operator.=== operator does have some nuance to it, a fact many JS developers gloss over, to their detriment. The === operator is designed to lie in two cases of special values: NaN and -0. Consider:
NaN, the === operator lies and says that an occurrence of NaN is not equal to another NaN. In the case of -0 (yes, this is a real, distinct value you can use intentionally in your programs!), the === operator lies and says it’s equal to the regular 0 value.
Object Equality
The story gets even more complicated when we consider comparisons of object values (non-primitives). Consider:42 === 42 considers the actual 42 value and compares it. But when it comes to objects, a content-aware comparison is generally referred to as “structural equality.”
JS does not define === as structural equality for object values. Instead, === uses identity equality for object values.
In JS, all object values are held by reference, are assigned and passed by reference-copy, and to our current discussion, are compared by reference (identity) equality. Consider:
y === x is true because both variables hold a reference to the same initial array. But the === [1,2,3] comparisons both fail because y and x, respectively, are being compared to new different arrays [1,2,3]. The array structure and contents don’t matter in this comparison, only the reference identity.
Coercive Comparisons
Coercion means a value of one type being converted to its respective representation in another type (to whatever extent possible). As we’ll discuss in Chapter 4, coercion is a core pillar of the JS language, not some optional feature that can reasonably be avoided. But where coercion meets comparison operators (like equality), confusion and frustration unfortunately crop up more often than not. Few JS features draw more ire in the broader JS community than the== operator, generally referred to as the “loose equality” operator. The majority of all writing and public discourse on JS condemns this operator as poorly designed and dangerous/bug-ridden when used in JS programs.
From what I can tell, most of this frustration comes from a pretty short list of confusing corner cases, but a deeper problem is the extremely widespread misconception that it performs its comparisons without considering the types of its compared values.
The == operator performs an equality comparison similarly to how the === performs it. In fact, both operators consider the type of the values being compared. And if the comparison is between the same value type, both == and === do exactly the same thing, no difference whatsoever.
If the value types being compared are different, the == differs from === in that it allows coercion before the comparison. In other words, they both want to compare values of like types, but == allows type conversions first, and once the types have been converted to be the same on both sides, then == does the same thing as ===. Instead of “loose equality,” the == operator should be described as “coercive equality.”
Consider:
== causes the non-number values ("42" and true) to be converted to numbers (42 and 1, respectively) before the comparisons are made.
You may be thinking, “Oh, well, I will just always avoid any coercive equality comparison (using === instead) to avoid those corner cases”! Eh, sorry, that’s not quite as likely as you would hope.
There’s a pretty good chance that you’ll use relational comparison operators like <, > (and even <= and >=).
Just like ==, these operators will perform as if they’re “strict” if the types being relationally compared already match, but they’ll allow coercion first (generally, to numbers) if the types differ.
Consider:
i < arr.length comparison is “safe” from coercion because i and arr.length are always numbers. The arr[i] < 500 invokes coercion, though, because the arr[i] values are all strings. Those comparisons thus become 1 < 500, 10 < 500, 100 < 500, and 1000 < 500. Since that fourth one is false, the loop stops after its third iteration.
These relational operators typically use numeric comparisons, except in the case where both values being compared are already strings; in this case, they use alphabetical (dictionary-like) comparison of the strings:
if, etc.), which we’ll revisit in Appendix A, “Coercive Conditional Comparison.”
How We Organize in JS
Two major patterns for organizing code (data and behavior) are used broadly across the JS ecosystem: classes and modules. These patterns are not mutually exclusive; many programs can and do use both. Other programs will stick with just one pattern, or even neither! In some respects, these patterns are very different. But interestingly, in other ways, they’re just different sides of the same coin. Being proficient in JS requires understanding both patterns and where they are appropriate (and not!).Classes
The terms “object-oriented,” “class-oriented,” and “classes” are all very loaded full of detail and nuance; they’re not universal in definition. We will use a common and somewhat traditional definition here, the one most likely familiar to those with backgrounds in “object-oriented” languages like C++ and Java. A class in a program is a definition of a “type” of custom data structure that includes both data and behaviors that operate on that data. Classes define how such a data structure works, but classes are not themselves concrete values. To get a concrete value that you can use in the program, a class must be instantiated (with thenew keyword) one or more times.
Consider:
Page class, the data is a string of text stored in a this.text member property. The behavior is print(), a method that dumps the text to the console.
For the Notebook class, the data is an array of Page instances. The behavior is addPage(..), a method that instantiates new Page pages and adds them to the list, as well as print() (which prints out all the pages in the notebook).
The statement mathNotes = new Notebook() creates an instance of the Notebook class, and page = new Page(text) is where instances of the Page class are created.
Behavior (methods) can only be called on instances (not the classes themselves), such as
mathNotes.addPage(..) and page.print().class mechanism allows packaging data (text and pages) to be organized together with their behaviors (e.g., addPage(..) and print()). The same program could have been built without any class definitions, but it would likely have been much less organized, harder to read and reason about, and more susceptible to bugs and subpar maintenance.
Class Inheritance
Another aspect inherent to traditional “class-oriented” design, though a bit less commonly used in JS, is “inheritance” (and “polymorphism”). Consider:Publication class defines a set of common behavior that any publication might need.
Now let’s consider more specific types of publication, like Book and BlogPost:
Book and BlogPost use the extends clause to extend the general definition of Publication to include additional behavior. The super(..) call in each constructor delegates to the parent Publication class’s constructor for its initialization work, and then they do more specific things according to their respective publication type (aka, “sub-class” or “child class”).
Now consider using these child classes:
print() method, which was an override of the inherited print() method from the parent Publication class. Each of those overridden child class print() methods call super.print() to invoke the inherited version of the print() method.
The fact that both the inherited and overridden methods can have the same name and co-exist is called polymorphism.
Modules
The module pattern has essentially the same goal as the class pattern, which is to group data and behavior together into logical units. Also like classes, modules can “include” or “access” the data and behaviors of other modules, for cooperation’s sake. But modules have some important differences from classes. Most notably, the syntax is entirely different.Classic Modules
ES6 added a module syntax form to native JS syntax, which we’ll look at in a moment. But from the early days of JS, modules was an important and common pattern that was leveraged in countless JS programs, even without a dedicated syntax. The key hallmarks of a classic module are an outer function (that runs at least once), which returns an “instance” of the module with one or more functions exposed that can operate on the module instance’s internal (hidden) data. Because a module of this form is just a function, and calling it produces an “instance” of the module, another description for these functions is “module factories”. Consider the classic module form of the earlierPublication, Book, and BlogPost classes:
class forms, there are more similarities than differences.
The class form stores methods and data on an object instance, which must be accessed with the this. prefix. With modules, the methods and data are accessed as identifier variables in scope, without any this. prefix.
With class, the “API” of an instance is implicit in the class definition—also, all data and methods are public. With the module factory function, you explicitly create and return an object with any publicly exposed methods, and any data or other unreferenced methods remain private inside the factory function.
There are other variations to this factory function form that are quite common across JS, even in 2020; you may run across these forms in different JS programs: AMD (Asynchronous Module Definition), UMD (Universal Module Definition), and CommonJS (classic Node.js-style modules). The variations are minor (not quite compatible). However, all of these forms rely on the same basic principles.
new, calling the module factories as normal functions.
ES Modules
ES modules (ESM), introduced to the JS language in ES6, are meant to serve much the same spirit and purpose as the existing classic modules just described, especially taking into account important variations and use cases from AMD, UMD, and CommonJS. The implementation approach does, however, differ significantly. First, there’s no wrapping function to define a module. The wrapping context is a file. ESMs are always file-based; one file, one module. Second, you don’t interact with a module’s “API” explicitly, but rather use theexport keyword to add a variable or method to its public API definition. If something is defined in a module but not exported, then it stays hidden (just as with classic modules).
Third, and maybe most noticeably different from previously discussed patterns, you don’t “instantiate” an ES module, you just import it to use its single instance. ESMs are, in effect, “singletons,” in that there’s only one instance ever created, at first import in your program, and all other imports just receive a reference to that same single instance. If your module needs to support multiple instantiations, you have to provide a classic module-style factory function on your ESM definition for that purpose.
In our running example, we do assume multiple-instantiation, so these following snippets will mix both ESM and classic modules.
Consider the file publication.js:
blogpost.js:
main.js:
The
as newBlogPost clause in the import statement is optional; if omitted, a top-level function just named create(..) would be imported. In this case, I’m renaming it for readability’s sake; its more generic factory name of create(..) becomes more semantically descriptive of its purpose as newBlogPost(..).class from our module instead of a create(..) factory function, with generally the same outcome. However, since you’re already using ESM at that point, I’d recommend sticking with classic modules instead of class.
If your module only needs a single instance, you can skip the extra layers of complexity: export its public methods directly.

