(for the full Gertrude Stein quote on art having no function and not being necessary, see : https://www.scottedelman.com/2012/04/26/art-has-no-function-it-is-not-necessary/)
Introduction
In the first part of this occasional series on JavaScript for Apex
Programmers, we looked at my chequered history with JavaScript, then the
difference between Apex and JavaScript types. In this instalment we'll look at
methods and functions.
Definitions
A method is associated with an object. It's either part of an object instance
(non-static) or part of the class that objects are created from (static
method). A function is an independent collection of code that can be called by
name from anywhere. These are often used interchangeably (by me especially!)
but the difference is important when considering JavaScript and Apex.
Apex
Apex only has methods. All Apex code called by its name is either a method
being invoked on an object instance (that you or the platform created) or a
static method in a class.
...
Almost all Apex code.
...
Apart from triggers.
...
Here's an account trigger with a named collection of code that I can call
later in the trigger:
trigger AccountMethod on Account (before insert)
{
void logMessage(String message)
{
System.debug('Message : ' + message);
}
logMessage('In account trigger');
}
And if I execute anonymous and insert a trigger, I see the message appear as
expected:
While this might look like a function, it's actually a static method on the
trigger, as I found out by changing the use to
this.logMessage('In account trigger');
It's not a useful static method though, as it can't be called from outside of
this trigger. I suppose it could be used to organise code in the trigger to be
more readable, but code shouldn't live in triggers so you'd do far better to
put it in a utility or service class.
That interesting digression over with, as far as we are concerned Apex has
methods in objects or classes, and I'll proceed on that basis.
If you want to pass some Apex code to be executed by another method, you have
to pass the object to the method. An example of this that we come across
pretty often is scheduled Apex:
MySchedulable mySch = new MySchedulable();
String cronStr = '21 00 9 9 1 ?';
String jobID = System.schedule('My Job', cronStr, mySch);
The platform will call the
execute() method on the
mySch instance of the
MySchedulable class that I pass to
it.
JavaScript
JavaScript, as I find so often the case, is a lot more powerful and a lot more
confusing. JavaScript has both methods and functions, but under the hood methods are
functions stored as a property of an object.
Functions are also actually object instances - every function you create is
actually an instance of the Function object. Which means they can have
properties and methods like other objects. And those methods are actually
functions stored as properties. And so on.
The good news is that you don't need to care about most of this when using
JavaScript in Salesforce. In my experience, what you really need know is:
Functions are First Class Citizens
In JavaScript, functions can be assigned to variables:
let log=function(message) {
console.log(message);
}
log('Hello');
Hellopassed as parameters to other functions:
let consoleLogger=function(message) {
console.log(message);
}
let log=function(logger, message) {
logger(message);
}
log(consoleLogger, 'Message to console');
Message to consoleand returned as the result of a function;
function getConsoleLogger() {
return function(message) {
console.log(message);
}
}
let consoleLogger=getConsoleLogger();
consoleLogger('Message for console');
Message for console
Functions can be Anonymous
When you create callback functions in JavaScript for very simple, often one-off use, they quickly start to proliferate and become difficult to distinguish
from each other. Anonymous functions are defined where they are needed/used
and don't become a reusable part of the application. Using a very simplistic
example, for some reason I want to process an array of numbers and multiply
each entry by itself each entry. I'm going to use the map() method from the
Array object, which creates a new array by executing a function I supply on
every element in the source array. If I do this with named functions:
function multiply(value) {
return value*value;
}
let numbers=[1, 2, 3, 4];
let squared=numbers.map(multiply);
console.log(squared);
[1, 4, 9, 16]
If I don't need the multiple function anywhere else, it's being exposed for no
good reason, so I can replace it with an anonymous function that I define when
invoking the map method:
let numbers=[2, 4, 6, 8];
let squared=numbers.map(function(value){return value * value});
console.log(squared);
[4, 16, 36, 64]
My anonymous function has no name and cannot be used anywhere else. It's also
really hard to debug if you have a bunch of anonymous functions in your stack,
so exercise a little caution when using them.
Arrow Functions improve on Anonymous
Especially for simple functions. Arrow functions (sometimes called fat arrow
functions) give you a more succinct way to create anonymous functions.
numbers.map(function(value){return value * value});
I can lose a lot of the boilerplate text and just write:
numbers.map(value=>value*value);
Breaking this down:
- I don't need the function keyword - I replace it with =>
-
I don't need parenthesis around my parameter, I just put it to the left of
=>
Note that if I have no parameters, or more than one, I do need
parenthesis
- I don't need the braces, as long as the code fits onto a single line
-
I don't need the return statement, again as long as the code fits onto a
single line. The result of my expression on the right hand side of => is
implicitly returned
Thus arrow functions can look pretty different to regular functions:
let multiply=function(value) {
return value * value;
}
let arrowMultiply=value=>value*value;
or quite similar
let addAndLog=function(first, second) {
let result=first + second;
console.log('Result = ' + result);
return result;
}
let arrowAddAndLog=(first, second)=>{
let result=first + second;
console.log('Result = ' + result);
return result;
}
Arrow functions have a few gotchas too - the major one is 'this' always refers to the Window object, regardless of how you might try to change it.
Functions have a Context
There's quite a bit to this (pun intended!) and as I mentioned in the first instalment, this isn't intended to be a JavaScript tutorial, so I can't see any point in replicating the Mozilla Developer Network content. Instead I'll just point you at it. The key thing to remember is 'this' depends on how the function is called, not where it is declared, so if you pass an object method as a callback function, when it is invoked 'this' won't refer to the original object, but whatever object is now invoking it. I'd recommend spending some time getting to grips with the context, otherwise you'll likely spend a lot more time trying to figure out why things don't work.
Related Posts
JavaScript for Apex Programmers Part 1 - Typing