Learn JavaScript Core Concepts
Learning Objectives
After completing this unit, you'll be able to:
- Describe the nature of the JavaScript runtime environment.
- Distinguish between the JavaScript engine and the language.
- Avoid key pitfalls when learning JavaScript.
- Describe some important JavaScript best practices.
The What, Why, and How of JavaScript
In the early 2000s, the software world was exploding with the idea that you could build applications that were hosted and delivered entirely over the Internet. Although we take web applications for granted now, at the time they were as mind-blowing as seeing a robot ride a bike.
Browsers were simple HTML renderers, and HTML and JavaScript standards were fragmented. To create a web page with even mildly complicated functionality, you had to force users to use only one browser, usually Internet Explorer. Even today there are apps that force this because of design decisions made in this era.
To work around some of these limitations, server-side UI frameworks evolved to allow software developers to dynamically create web pages on servers. Complex logic executed where the computing power was: on the server. Rendered HTML was then served to the feeble browser. In this world, JavaScript was primarily a way to make pages somewhat interactive, and to execute rudimentary logic without a server trip. Server-side frameworks were extremely popular, including the Salesforce framework Visualforce. As testimony to this popularity, there are millions of Visualforce pages live in Salesforce today.
Fast forward to the present and the world is a different place. Browsers are powerful applications that optimize web page viewing. JavaScript is well standardized around the ECMAScript standard, and top browser manufacturers are generally good about adopting new features. Web applications today have rich user interfaces running modern JavaScript in the browser. Instead of server-side frameworks, modern web applications tend to be client-side rendered. In Salesforce, this is done with the Lightning Component Framework. But writing JavaScript is still new for many developers. If you’ve worked mostly with Visualforce pages and Apex controllers, you might need a leg up to really get how JavaScript works so you can better understand your components.
Time to level up your JavaScript skills.
The JavaScript Runtime
The JavaScript runtime is an engine that interprets JavaScript code. It can be part of a browser, or other runtime environment like a server. Modern JavaScript engines are sophisticated and powerful to optimize execution and designed to conform to the ECMAScript standard.
The defining feature of the JavaScript engine is a single-threaded runtime represented by the stack below. Work being done in the stack owns the thread and must complete its synchronous logic before it hands back control of the thread.
The runtime is a busy place. New work can come in at any time from a number of origins, including the user (UI events) and web APIs (such as geolocation or device motion). Since there’s only one thread, there is a queue where work awaits its turn to use the thread.
When the stack is empty, the event loop takes work that is waiting to be done from the queue and moves it into the stack.
This is, of course, a simplification, but illustrates the basic model of how the JavaScript engine gets its work done. The JavaScript language works the way it does in practice because of this architecture.
JavaScript the Language
As a language, JavaScript is frequently misunderstood. If you’ve picked it up for any length of time, you’ve undoubtedly asked yourself questions. Is it a scripting language? What does it have to do with Java? Is it a real programming language? Why does it do that?
Let’s set some context around JavaScript as a language to help you answer some of those questions.
JavaScript Is Changing
Because JavaScript is built according to the ECMAScript standard, it’s constantly changing. An update to the standard describing new features is published every year, then JavaScript engine projects (browser and runtime manufacturers) put them in place.
Updates can include more modern language features as JavaScript matures. In other cases, features are added to implement cleaner syntax for existing functionality (these are called syntactic sugar).
Adoption of APIs Is Not Universal
This statement may seem scary, but the vast majority of JavaScript APIs work in the most common browser platforms. Still, implementers don’t adopt every language feature or API at the same rate. Some never adopt certain features (although this is rare). In general if you want to use a newer feature of the language, you should understand how well it will work in your target browser(s) using a resource like caniuse.com.
If a new feature isn’t implemented natively, there’s often a bit of code written to temporarily serve the purpose of that missing feature. This temporary fill-in code is called a polyfill. In fact, the Lightning Component Framework uses a curated list of polyfills that are applied before other code is run to automatically improve browser compatibility.
Indispensable Things to Know
It’s time to look at some code. Let’s start with some things every JavaScript developer should know to make their lives easier.
Remember Case Sensitive
JavaScript is case sensitive. This is tricky for many Salesforce developers who are used to both Apex and SOQL being case insensitive. Remember anytime you write JavaScript code in Salesforce, be mindful of case sensitivity.
Declaring Variables
Variable declaration is done using one of three operators: var
, let
, and const
. In general use let
and const
. The table below summarizes the functional difference between these three keywords.
Keyword |
Scope |
Mutable Assignment |
---|---|---|
var |
function |
yes |
let |
block |
yes |
const |
block |
no |
Scope is a topic that we discuss later, so let’s address what mutability means.
All variables are pointers. Assignment is the act of pointing that variable to something in memory. Mutability is whether or not a variable can be reassigned once it has initially been assigned something. Using var
or let
creates mutable pointers, whereas const
is immutable. As is often the case, this is best understood by demonstration.
//primitive assignments
var myBike = "Mountain Bike";
let currentGear = 5;
const numberOfGears = 12;
//reassignment
myBike = "Penny Farthing"; // this works
currentGear = 1; // so does this
numberOfGears = 1; // error
Above, myBike
and currentGear
had no problem when being reassigned values. But when attempting to do so to numberOfGears
, there is an error.
When working with objects (rather than primitives), remember that const
only prevents reassigning your variable to a different object. The object itself (its properties, functions, and so on) can still be changed as seen below.
// call constructor, new object, assign it to bike
const bike = new Bike();
//Change internal state by calling a function
bike.changeGear("front", "Up");
// add a new member to bike that did not exist before
bike.type = "Penny Farthing";
// check for success
console.log(bike.calculateGearRatio()); // 4.0909...
console.log(bike.type); // "Penny Farthing"
// attempt to point bike to new instance of Bike
bike = new Bike(1,2); // error
Here, we create an object from the Bike
constructor and assign it to the bike
variable (remember, case sensitive). We can then change anything we want about it such as calling functions that change its state and even adding new members. But the moment we attempt to reassign bike to something else, in this case by calling the constructor again, there’s an error.
Implicit Type Coercion
When most JavaScript operators encounter an invalid type, they attempt to convert the value to a valid type. This process of implicitly converting a type is called implicit type coercion. Consider the following:
let num1 = 9 * "3";
console.log(num1); // 27 (a number)
let num2 = 9 + "3";
console.log(num2); // "93" (a string)
In the first example the *
operator can only be used for math operations, coercing the string "3"
to a number. The outcome is 27. In the second, the +
operator sees the string "3"
making it a unary operator (concatenate). This coerces 9 to the string "9"
with an outcome of the string "93"
.
At first glance this can seem handy, but in practice can lead to confusing results.
Don’t Use Implicit Type Coercion
Many instances of implicit type coercion are confusing. For instance Boolean comparisons. The ==
and !=
comparison operator common to C-family languages will attempt to convert anything to Boolean. There are deterministic rules, but they are too complex to be practical. Here are some fun examples.
false == ""; // true
false == "0"; // true
"" == "0"; // false
[0] == 0; // true
“Why did that do that?”
Exactly.
For Boolean comparison the best practice is to use ===
and !==
. With these operators, primitive types are only equivalent when both type and value match, and object comparisons are only true when their respective pointers are pointing to the same memory address. Trying the same comparisons as above:
false === ""; // false
false === "0"; // false
"" === "0"; // false
[0] === 0; // false
Truthy and Falsy
You know that rule we just laid out warning against implicit type coercion? Well, here’s the exception.
When an expression expects a Boolean value, the following values are always treated as false.
-
false
(of course)
-
0
(the number zero)
-
""
or''
(an empty string)
-
null
-
undefined
-
NaN
(the result of failed mathematical operations)
false
is false
(of course!). The rest of these values are coerced to false
. As a group they are referred to as falsy.
There’s no question that true
is just that—true
. But any other value of any type is coerced to true
. We call these values truthy.
This has a handy side effect. Let’s say you want to test for several types of invalid data by simply passing a variable into an if expression.
const myRecord = response.getReturnValue();
if (myRecord) {
//now process the record
}
If for some reason the above assignment fails and we end up with an uninitialized variable (undefined
), an empty string, or 0
, we’ve covered all those bases just by doing the conditional check on the myRecord
variable. This is a widely accepted practice in JavaScript.
this
Is Tricky
This module has an entire section about how to use the this
pointer. It has well-defined rules, but what this
points to can change even within the same function. For instance, in an Apex class you might see:
public class MyApexClass {
private String subject;
public MyApexClass(String subject) {
this.subject = subject;
}
}
In this example Apex class, this
only ever refers to the current instance of MyApexClass
. In JavaScript, what this
points to is not determined by where a function is defined, but rather where that function is called. More on this
later.
Functions Are Values
In JavaScript, everything is an object. This goes for functions too. And like other objects, functions can be assigned to variables, passed into parameters of other functions, and used the same way you can any other object.
This can be a bit shocking if you haven’t worked in a language where functions are first-class citizens, or that uses lambdas. We spend a whole unit just on functions later on.
More on that—and so many other things—later.
For now, you have a good basic introduction to JavaScript concepts. Next, we look at how JavaScript works in the browser.