A JavaScript Crash Course from a Apex Dev's Point of View - Part 3
At TrailheaDX ‘19, I was fortunate enough to do a talk on learning JavaScript fundamentals from an Apex developer’s point of view. This series covers the topics from that talk
We focused on first class functions in the last post, which should help set the stage for demystifying the this
keyword.
What’s this?
In Apex, the this
keyword is fairly straightforward: it refers to the current instance of an Object. Take this Person
class as an example:
public class Person {
String name;
public String getName() {
return this.name;
}
}
When you instantiate a Person
object, the method getName
uses this.name
to return that instance’s value for the property name
.
If you treat the this
keyword in JavaScript similarly you will quickly run into some unexpected behavior. Remember, functions in JavaScript are first class. So while a function may be the value of a property in an object, it doesn’t necessarily belong to that object. For example, consider this object:
let person = {
name : 'Sean',
getName : function() {
return this.name;
}
}
The person
JavaScript object has a property called getName
and it would seem that if you called that function, it should return the name
property on that object (i.e. the value Sean
). Depending on how the function was called, that would be true. But what if you assigned the value of getName
to another variable, like so?
let nameGetter = person.getName;
Now the variable nameGetter
has been essentially been removed from the object person
. What does the this
keyword in nameGetter
refer to now? If you think of JavaScript like Apex, you would be tempted to say the this
keyword still refers to the person
object, but that’s not the case.
Generally, this
keyword is often said to be bound to the “call site”, meaning that its values depends on the context in which the function is used. Just by looking at a function that contains the this
keyword is not enough to tell you what it is bound to. It is not necessarily the object it is used in, but it can be!
This really frustrated me, because I wanted the this
keyword to work like it does in Apex. But if you can let go of that notion of what you think it should be, you can than embrace it for what it can do. To do that, let’s walk through the rules of how the this
keyword is bound.
(Credit for the this
hierarchy that follows goes to You Don’t Know JS, an amazing book that really helped me understand JavaScript more!)
Default Binding
Let’s start with “default binding”, which is basically what this
falls back on when all of the other rules that follow don’t apply. In short, the default binding is the global scope. For example:
function identify() {
console.log(this.name);
}
var name = 'Sean'
identify(); //Outputs 'Sean'
The variable name
is in the global scope. So when you call the identify
function, none of the other rules for binding the this
keyword apply (you’ll understand why later as you read on), so it is bound to the global scope and thus the log the string Sean
.
However, when strict mode
is in use, which it will be in LWC, then the global scope is not eligible to be bound to this
, so you will get an error instead.
function identify() {
'use strict`
console.log(this.name);
}
var name = 'Sean'
identify(); //TypeError: Cannot read property 'name' of undefined
“new” Binding
Let’s start at the top of the this
binding hierarchy. When you call a function with the new
keyword, you are invoking a constructor call of that function. When you call a function with the new
keyword, that function behaves differently:
- The function creates an empty object
- Any references to the
this
keyword in the function will now refer to that object during it’s execution - At the end of the function, unless something is explicitly returned by the function, that newly created object will be returned (even if you don’t have a return statement in your function)
Consider the following function named Person
. It has a capital “P”, but that does not affect it’s behavior as a constructor. Instead, it typically signals that it is a constructor function that can and should be called with the new
keyword, but using a lowercase p
would work as well.
function Person(name) {
this.name = name;
}
When we call this function with the new keyword, we can assign the object it creates to a variable.
let programmerSean = new Person('Sean');
console.log(programmerSean.name) //Outputs 'Sean'
Because we used the new
keyword to invoke the Person
function, the variable programmerSean
is an object with a property name
, that contains the string value Sean
. What if we don’t use the new
keyword?
let programmerJoe = new Person('Joe');
console.log(programmerJoe.name) //TypeError: Cannot read property 'name' of undefined.
console.log(name) //Outputs 'Joe' because the `this` keyword fell back on the global scope!
Without the new
keyword the Person
function did NOT return an Object, so the variable programmerJoe
is actually undefined. Also the this
keyword could not use new binding, so it defaulted to the global scope. As a result, there is now a name
property on the global scope that holds the string value Joe
!
Explicit Binding
In JavaScript, you can also explicitly set what you want the this
keyword to be bound to using the functions call
, apply
and bind
.
Consider the following function and object:
function greet(greetings) {
console.log(greetings, this.name);
}
let sean = { name : 'Sean' }
The call
function can be called on a function and accepts one to many parameters. The first parameter is the object that you want this
to be bound to. And rest of the parameters are the parameters that you want to pass the function you are invoking. Using call
on a function immediately invokes it. For example:
greet.call(sean, 'Hello', 'My Friend') //Outputs 'Hello My Friend Sean'
The apply
function works the same, but accepts only 2 parameters. The first parameter is the same, it’s the object you want this
to be bound to. The second parameter, however, is an array that holds all of the parameters you want to pass the function you are invoking. For example:
greet.call(sean, ['So Long', 'Farewell']) //Outputs 'So Long Farewell Sean'
The bind
function works differently in that it does not invoke the function. When you call bind
on a function, you pass it one parameter, which will be the object you want the this
keyword bound to. bind
then returns that function with this
already bound, which can you assign to a variable. For example:
let boundFunction = greet.bind(sean);
boundFunction('Good Morning'); //Outputs 'Good Morning Sean'
In short, use call
and apply
when you want to invoke a function with a specified this
keyword and use bind
when you want to specify the this
keyword, but don’t want to invoke the function until later.
Implicit Binding
When a object has a function as a property, when you call that function from that object, the this
keyword is bound to that bound. This is known as implicit binding. Here is the person
object that we looked at earlier that contained a function as a property:
let person = {
name : 'Sean',
getName : function() {
return this.name;
}
}
When you call person.getName()
, due to implicit binding the this
keyword refers to the person
object and thus returns the string value Sean
. As we illustrated earlier, you can also lose this implicit binding if you call the function outside of the context of the object.
let nameGetter = person.getName;
nameGetter(); //returns nothing
You will often run into this in LWC. Consider this example component:
import { LightningElement, track } from 'lwc'
export default class JobTracker extends LightningElement {
@track
jobRunning = false;
handleClick() {
this.jobRunning = true
}
}
At first class, it looks like JavaScript class are treating the this
keyword similarly to how it works in Apex. It looks like this
is mean to represent that instance of the class, which is why the handleClick
function can access the jobRunning
property. In actuality, this only works because of implicit binding. Let’s rewrite this component to illustrate how the this
keyword does not necessarily refer to the class.
import { LightningElement, track } from 'lwc'
export default class JobTracker extends LightningElement {
@track
jobRunning = false;
handleClick() {
markJobRunning();
function markJobRunning() {
this.jobRunning = true
}
}
}
We have now created a nested function called markJobRunning
within the handleClick
function. markJobRunning
is not called with the new
keyword, so new binding is not in effect. Explicit binding is also not being used and we can tell that implicit binding is not being used because markJobRunning
is not being called from an object. So that means that default binding is in effect. Of course, since LWC runs in strict mode, that means this
is going to undefined, so we’ll get an error saying Cannot read property jobRunning of undefined
.
Binding Hierarchy
To review, use the following hierarchy to determine what this
is bound to.
- Is the function being called with
new
?- The
this
keyword refers to the “new” object constructed by the function
- The
- If not, is the function being called with
call
orapply
, or instantiated withbind
?- The
this
keyword refers to the specified object
- The
- If not, is the function being called off of an object?
- The
this
keyword refers to the object the function was called off of
- The
- If not, default binding is in effect
- The
this
keyword refers to the global scope, or is undefined if strict mode is enforce.
- The
Arrow functions
There is of course an exception to all of this! Arrow functions are not just shorthand for writing functions. The this
keyword actually behaves differently when you use an arrow function. For those who are unfamiliar here is the same function written with the function
keyword and as arrow function:
let oldFunction = function(x) { console.log(x) }
let arrowFunction = x => console.log(x)
I love the shortened syntax, but if you have the this
keyword in a function that means that two different syntaxes are not interchangeable. To illustrate, let’s look at an example that demonstrates that problem that arrow functions attempt to solve. Consider our Person
constructor function again.
function Person(name) {
this.name = name;
this.delayedGreet = function() {
setTimeout(
function() {
console.log("Hi, my name is ', this.name);
},
100
)
}
}
let sean = new Person('Sean');
sean.delayedGreet() // Eventually outputs "Hi, my name is undefined"
So our Person
function constructs an object with a name
property and a delayedGreet
property that calls setTimeout
with a callback function that logs out a greeting using the name
property of the this
keyword. However, after calling the Person
constructor, when we call delayedGreet
, it outputs Hi, my name is undefined
instead of the expected Hi, my name is Sean
. What’s happening here? What does the this
keyword in this.name
refer to?
At first glance you might think that new binding should be in effect. However when the delayedGreet
property is created, the callback passed to the setTimeout
function is not being invoked yet, so new binding IS NOT in effect. When that callback is eventually invoked, the this
keyword is bound to however the setTimeout
function binds it, so this.name
ends up being undefined.
Arrow functions attempt to fix this confusion. Here’s the same method written with an arrow function:
function Person(name) {
this.name = name;
this.delayedGreet = function() {
setTimeout(
() => console.log("Hi, my name is ', this.name),
100
)
}
}
let sean = new Person('Sean');
sean.delayedGreet() // Eventually outputs "Hi, my name is Sean"
The same function but written with an arrow function and now it works! What’s happening here? Well, arrow functions use “lexical scope” for defining the this
keyword. In other words, whatever this
is bound to when the function was instantiated is what this
will be bound to. In our example, the this
keyword will use new binding so that it is bound to the same object that was created during the constructor.
Are you confused? You should be! In my opinion, this “fix” just creates more mental overhead. It’s another exception you have to keep track of when you already have to deal with a somewhat confusing hierarchy. It’s important to know what’s going on here because some developers do like this “solution” that arrow functions provide. I, however, instead recommend using explicit binding.
function Person(name) {
this.name = name;
this.delayedGreet = function() {
setTimeout(
function() {
console.log("Hi, my name is ', this.name);
}.bind(this), //Explicitly bind this to the "new" binding"
100
)
}
}
let sean = new Person('Sean');
sean.delayedGreet() // Eventually outputs "Hi, my name is Sean"
By using the bind
method, I am explicitly setting what I want the this
keyword to be so that it is very clear as to what I intend. My advice? If you have to refer to the this
keyword, avoid using an arrow function.
Hopefully you now have a better understanding of how the this
keyword works, which can make reading JavaScript much less intimidating. In our next post, we’ll wrap up with some debugging options that you have in JavaScript that you previously did not have in Apex.
A JavaScript Crash Course from a Apex Dev's Point of View - Part 2
At TrailheaDX ‘19, I was fortunate enough to do a talk on learning JavaScript fundamentals from an Apex developer’s point of view. This series covers the topics from that talk
In the last post, we covered some basic differences in JavaScript and Apex, focusing on dynamic typing and scope. In this post, we’ll focus on first class functions in JavaScript, a feature that I believe is a fundamental difference in how you write code compared to Apex.
First Class Functions
Unlike Apex, functions are “first class” in JavaScript. What does that mean? It means that functions can be:
- Passed as arguments to other functions, which are generally referred to as “callbacks”
- Used as return values from functions
- Assigned to variables
- Stored as attributes in data structures.
In Apex, methods can only be passed around when encapsulated in an object, and then called from that object.
Here is an example of first class functions in action:
function fetchAction() {
return function() { console.log('you called?') }
}
function doTheThing(f) {
f();
}
let anAction = fetchAction();
doTheThing(anAction); //Outputs: "you called?"
The first function fetchAction
returns a simple function that logs the string you called?
. The second function doTheThing
accepts a callback and executes it. So we can call fetchAction
, assign the return value to the variable anAction
, and then pass that to doTheThing
, which executes that action, logging the string you called?
. As you can see, function values do NOT have the parenthesis. Adding the parenthesis to a function invokes the function.
You will often see this in LWC with imperative Apex, which imported as Promises. You can chain Promises with the then
and catch
functions, both of which accept a callback as parameters. When you call an Apex method in LWC, after the method completes, the then
function executes the callback passed to it. If an exception is thrown, the catch
method executes the callback passed to it.
For example:
import {LightningElement, track} from 'lwc';
import submitRecords from '@salesforce/apex/MyController.submitRecords';
export default class MyComponent extends LightningElement {
@track allRecords
submit() {
submitRecords({ records: allRecords })
.then(function(){
alert('Success')
})
.catch(function(){
alert('Something went wrong...')
})
}
}
When the submit
function is called in our component, that executes the imperative Apex method submitRecords
. If that method resolves successfully, the then
function executes a callback function that alerts the string Success
. If an exception is thrown, the catch
function executes a callback function that alerts the string Something went wrong
. Since functions are first class, we are able to declare the callbacks parameters as inline functions. But you could also declare the callbacks outside of the functions like so:
import {LightningElement, track} from 'lwc';
import submitRecords from '@salesforce/apex/MyController.submitRecords';
export default class MyComponent extends LightningElement {
@track allRecords
submit() {
submitRecords({ records: allRecords })
.then(handleSuccess)
.catch(handleError)
function handleSuccess(){
alert('Success')
}
function handleError(){
alert('Something went wrong...')
}
}
}
In this example, the behavior is the same, but this time the functions are declared outside of the parameters and then we pass handleSuccess
and handleError
to then
and catch
respectively. Remember, when referring to functions as values, do not add the parenthesis. handleSuccess
is the function itself while handleSuccess()
will call the function and pass the value of whatever is returned by that function.
Let’s walk through a more practical example to help illustrate how this affects the way we write code. Given an array of contacts with their names and the group they each belong to, I want to get a concatenated string of all the contacts that belong the group “Avengers” so that I can print them on a document. Here is the list:
let contacts = [
{ groupName : 'Avengers', name : 'Steve Rogers' },
{ groupName : 'Avengers', name : 'Tony Stark' },
{ groupName : 'Avengers', name : 'Natasha Romanov' },
{ groupName : 'Justice League', name : 'Diana Prince' },
{ groupName : 'Justice League', name : 'Bruce Wayne' },
{ groupName : 'X-Men', name : 'Charles Xavier' },
]
If I were to build this in JavaScript with an Apex mindset, you might do something like this:
- Initialize a blank string
- Iterate through all the contacts
- If the contact belongs to a desired group, concatenate their name to the string
function concat(contacts) {
let concatenatedNames = '';
for (let i = 0; contacts.length; i++){
if (contacts[i].groupName === 'Avengers') {
concatenatedNames = concatenatedNames + '|' + contacts[i].nae;
}
}
return concatenatedNames
}
There’s nothing inherently wrong with this code; it does the job. But you typically wouldn’t see it written like this. The Array
object has functions that allow you to iterate over the items in an array while taking advantage fo first class functions.
filter
- iterates over each value in a collection and passes each item to a callback for processing- If the callback returns a “truthy” value, then the item will be added to a new array
- Otherwise it does not add the item to the new array.
- When complete, the new filtered array is returned
map
- iterates over each value in a collection and passes each item to a callback for processing.- Whatever is returned by that callback is added to new array
- When complete, the new array is returned
reduce
- iterates over each value in a collection and passes each item and an aggregator to a callback for processing.- Whatever is returned by the callback becomes the “aggregator” object. This aggregator is passed to the next iteration of the callback
- When complete, the aggregator is returned
- Essentially, you are “reducing” an array into an object
Let’s do the same concatenation, but this time leveraging these functions:
function concat(contacts) {
return contacts
.filter( function(contact) { return contact.groupName === 'Avengers' } )
.map( function(contact) { return contact.Name }, [] )
.reduce (function(concatenatedNames, name) { return concatenatedNames + '|' + name; }, '')
}
filter
builds an array of contacts that are in the “Avengers”. Since the return value of filter
is an array, we can chain the map
function to it, which will build an array of just the name strings from the contacts. map
also returns an array, so we chain reduce
to it, which will builds our concatenated string. To be more succinct, here’s same method with arrow functions.
function concat(contacts) {
return contacts
.filter( c => c.groupName === 'Avengers' )
.map( c => c.Name )
.reduce( (concatenatedNames, n) => concatenatedNames + '|' + n, '')
}
This is a common pattern you’ll see in JavaScript: call some function to get some data and use callbacks to process that data. Because functions are first class in JavaScript, execution tends to favor this pattern of passing around utility functions that are chained to produce an end result as opposed to using instance methods that manage an object’s state.
I actually don’t know if a for
loop is faster or slower than using filter
, map
and reduce
, but I do know that using filter
, map
and reduce
is more prevalent. Remember, part of writing code is making it more readable to other developers. As the saying goes, when in JavaScript, do as the JavaScriptors do!
That covers the basics of first class functions and how they can change your approach to writing code in JavaScript vs. Apex. In the next post, we’ll focus on demystifying the this
keyword identifier and how it is drastically different from the this
keyword in Apex.
A JavaScript Crash Course from a Apex Dev's Point of View - Part 1
At TrailheaDX ‘19, I was fortunate enough to do a talk on learning JavaScript fundamentals from an Apex developer’s point of view. This series covers the topics from that talk
I’ve been a developer for about 8 years now and while most of that time has been focused on working on the Salesforce platform, I’ve also had the opportunity to work on Golang, Ruby, and of course, JavaScript. Just like the languages we use to speak with each other, part of successfully using a programming language is to embrace the nuances that are commonly followed by other users of that language. In other words:
Stop developing in JavaScript as if you were writing Apex.
During this series, we’ll cover some topics that I believe provide a strong foundation to getting started with JavaScript by comparing its features to analogous features from Apex.
JavaScript is a Dynamically Typed Language, Apex is Static
In a statically typed language like Apex, variables must be declared with their type and thus those variables can only reference their declared type. Here’s a simple Apex method for example:
Integer increment(Integer x) {
return x + 1;
}
Because of static typing, the method increment
can only accept an Integer as a parameter and can only return an Integer. This provides a level of protection at compile time as there is a stricter contract as to what your method will accept and return, leaving you with less surprises at runtime.
In a dynamically typed language like JavaScript, variable types are defined at runtime. Here’s the same method as a JavaScript function:
function increment(x) {
return x + 1;
}
The parameter x
can be anything! A string, an integer, or some kind of arbitrary object. You also do not necessarily know what it will return just by looking it. You lose that compile time protection, but you also get more flexibility. For instance, dynamic typing makes defining arbitrary data easier. Here’s a function that returns an arbitrary data structure that is declared inline:
function returnData() {
return {
a : 'some',
b : 'random',
c : {
d : 'nested data
}
}
}
I can’t count the number of times while writing Apex where I just need to process some data and return it in some random structure. Here is the equivalent of that data structure in Apex:
class RandomData {
String a;
String b;
NestedData c;
}
class NestedData {
String d;
}
class RandomData returnData() {
RandomData rand = new RandomData();
NestedData nest = new NestedData();
rand.a = 'some';
rand.b = 'random';
rand.c = nest;
nest.d = 'nested data';
return rand;
}
Because Apex is statically typed, you would need to define objects that follow the data structure you want. You get some type safety here but if this was just some private helper method in a class, that type safety is probably outweighed by the extra code that you have to write (and thus maintain!) This is a pretty contrived example, but you have probably seen this when making wrapper classes for SObjects when you wanted to encapsulate an SObject in addition to other data that does not necessarily exist as a field on that SObject. For example:
class OpportunityWrapper() {
Opportunity opp;
Boolean shouldDisplay;
}
Sure that’s only four lines, but where should those four lines live? In the controller it is used in? In its own file? What if you have to add more properties? What if those properties aren’t necessarily related? You can see how you can start to spiral out of control, all over some data structure.
The increased flexibility of dynamic typing, however, can lead to some unexpected results. Consider this concatenate
function that accepts two parameters and logs their concatenation as a string.
function concatenate(a, b){
console.log(a + b);
}
If you called function with the strings “hello” and “world”, you’ll see the expected output “helloworld” with no spaces. If you called function with the integers “1” and “2”, you would expect the output “12”, but it would actually output “3”. The function just adds the parameters together and adding two integers together behaves differently from adding two strings together. To fix this, add an empty string to the concatenation so the parameters are coerced into strings. Here’s the fixed version of function:
function concatenate(a, b){
console.log(a + '' + b);
}
Now passing the integers “1” and “2” to the function will output the expected “12”. So when you are writing JavaScript, make sure to be more defensive when handling variable types to prevent unexpected results.
Scoping
In short, scope refers to visibility of variables within your code. JavaScript and Apex have some differences when handling scope.
Apex is pretty straightforward with block level scope. You can think of a block as anything between a pair of curly braces. Scope flows outward in Apex. When a variable is used in a block, the compiler searches within that block for its definition. If the variable is not defined in there, the compiler searches outside of that block to see if it is a static or instance property in that class. If it still can find it, then it checks the global scope (which contains the definition for tokens like Test and Schema). If that fail, then you get a compilation error.
This outward flow of scope also explains why in Apex you are able to have some level of duplicate variable names without issue. For example, in this Apex method, the variable i
is defined twice: once within the blockScope
method and once as an instance variable. The blockScope
method uses the i
that is declared within the block of the method as opposed to the instance variable defined outside of the scope.
private Integer i = 0;
void blockScope() {
Integer i = 10;
System.debug(i);
}
blockScope(); //outputs 10;
In JavaScript, scope depends on how you declare your variables. You are probably most familiar with using the var
keyword to declare variables, which provide function level scope.
For example, consider this function where the variable greeting
is defined within an if
block. The greeting
variable is still visible outside of the block due to the function level scoping.
function greet() {
if(true) {
var greeting = 'hello!';
}
console.log(greeting) //Outputs 'hello!'
}
If you forget to use the var
keyword, JavaScript will actually put that variable on global scope (i.e. the window
object).
function greet() {
if(true) {
greeting = 'hello!';
}
console.log(greeting) //Outputs 'hello!'
}
Fortunately, if you write you code with use strict
, then strict mode will prevent this from happening and will throw an error
function greet() {
'use strict'
if(true) {
greeting = 'hello!'; //Throws "ReferenceError: greeting is not defined in scope"
}
console.log(greeting)
}
Lightning Locker actually enforces strict mode everywhere so you don’t have to specify use strict
, but it is important to understand the mechanism behind this scoping.
ES6 introduced the two new keywords let
and const
for variable declaration which provide block level scope like in Apex. let
is used for variables that you want to be able to reassign values to while const
variable values cannot be reassigned. In the following function, the variables greeting1
and greeting2
are not accessible outside of the if
block;
function greet() {
'use strict'
if(true) {
let greeting1 = 'hello';
const greeting2 = 'world!';
}
console.log(greeting1) //Throws "Uncaught ReferenceError: greeting1 is not defined"
console.log(greeting2) //Throws "Uncaught ReferenceError: greeting2 is not defined"
}
In general, I tend to default to const
if the variable won’t be reassigned, then I use let
if I need to change the variable and only using var
when I need function level scope, though I don’t run into many scenarios where that is necessary.
These are some very basic differences between JavaScript and Apex. In the next part, we’ll dive deeper by covering first class functions and how that changes the way you approach writing code in JavaScript compared to Apex.