Skip to main content

Take Action with Events and Functions

Learning Objectives

After completing this unit, you’ll be able to:

  • Define functions
  • Distinguish between function declaration and function expressions
  • Invoke functions
  • Pass and assign functions
  • Describe uses of functions and events in Lightning Web Components

When it comes to making stuff happen in JavaScript it’s all about events and functions.

Remember our model of the runtime? Let’s extend that a bit. 

With JavaScript in the browser, events are all over the place. Parts of the DOM emit events that correspond to what that DOM object does. Buttons have click events, input and select controls have change events, and virtually every part of the visible DOM has events for when the mouse cursor interacts with it (like passing over it). The window object even has event handlers for handling device events (like detecting the motion of mobile devices). 

To make something happen in a web page, functions get assigned to these events as event handlers. 

To reiterate, DOM events and other events related to the browser environment are not actually part of the core JavaScript language, rather they are APIs that are implemented for JavaScript in the browser. 

When an event is emitted, a message is created in the engine. It is these messages that are placed in the event queue we talked about earlier. 

Messages in the queue and frames in the stack.Once the stack is free, the event handler is invoked. This creates what’s referred to as a frame on the call stack. Each time one function invokes another, a new frame is added to the stack, and when complete, it is popped off the stack, until finally the frame for the actual event handler is popped, the stack is empty, and we start all over again. 

Defining and Assigning Functions

In JavaScript functions are essentially special objects. As objects, they are first-class members of JavaScript. They can be assigned as the values of variables, passed into other functions as parameters, and returned from functions. 

There are two essential phases in the life of a function: definition and invocation.  

When function is declared, its definition is loaded into memory. A pointer is then assigned to it in the form of a variable name, parameter name, or an object property. It should be no surprise, however, that there are several different syntaxes to do this.

Function Declaration

A declaration is a statement that uses the function keyword to create a function. In fact, we’ve seen it already when we were looking at our object constructor. That constructor is a function. But constructor functions are a bit special, so let’s step back and talk about plain old functions and see how they work: 

// declare function

function calculateGearRatio(driverGear, drivenGear){
return (driverGear / drivenGear);
}
// call function
let gearRatio = calculateGearRatio(42, 30);
console.log(gearRatio); // 1.4

In this code sample, function is followed by the name of the function, with parameters enclosed in the parentheses. 

This works fine, but there’s some implicit stuff happening. First of all, the function name becomes the variable name. It also implicitly assigns the variable to the enclosing context. Finally, you can call this function before it is declared, such as below where calculateGearRatio is invoked the line before the declaration. 

// call function

let gearRatio = calculateGearRatio(42, 30);
// function is declared after the line it is called
// this is allowed in function declaration
function calculateGearRatio(driverGear, drivenGear){
return (driverGear / drivenGear);
}
console.log(gearRatio); // 1.4

Function Expressions

Function expressions accomplish the same thing as declarations more explicitly. 

const calculateGearRatio = function(driverGear, drivenGear){

return (driverGear / drivenGear);
}
// the rest works the same
let gearRatio = calculateGearRatio(42, 30);
console.log(gearRatio); // 1.4

In this instance we have an explicitly assigned variable. Since we’ve named the pointer, we can drop the function name after the function keyword. The only catch here is that the function must be declared prior to it being invoked. 

Notably, function expressions are also used to assign functions as members of objects. Recall when we assigned the changeGear function to Bike.prototype?  

Bike.prototype.changeGear = function(direction, changeBy) {

if (direction === 'up') {
this.currentGear += changeBy;
} else {
this.currentGear -= changeBy;
}
}

Returning a Function

Since functions are first-class objects, another way to declare a function is when a function returns another function. This pattern is often referred to as a factory function. 

// when invoked, this function will assign a function

function gearFactory(){
return function(driverGear, drivenGear){
return (driverGear / drivenGear);
}
}
// calculateGearRatio can now be invoked as a function
const calculateGearRatio = gearFactory();
// and all the rest

While the above example is simplistic, it’s valid. Factory functions are useful for one-off reusable functions, especially to capture variable references in a closure. We talk about closures in a later unit. 

Anonymous Functions

There are many APIs in JavaScript that require you to pass a function for them to work. Say, for instance, you have an array, and you want to create a new array derived from the values of that array. In this case you would probably use the Array.map function.

let myArray = [1, 5, 11, 17];

let newArray = myArray.map( function(item){ return item / 2 } );
console.log(newArray); // [0.5, 2.5, 5.5, 8.5]

In this snippet, myArray.map takes in a single parameter: a function that is executed once per item in myArray

This function is never reused. It is declared as an argument (with no name...thus “anonymous”) passed into the function, and is executed in the internals of the implementation of the map function. Anonymous functions (also called lambdas in some languages) are commonplace in JavaScript. 

Function Invocation

Once you’ve declared your function, you’ll probably want to get around to invoking it. When a function is invoked, a few things happen. 

Remember, the first thing is a new frame is pushed onto the stack. Then an object containing its variables and arguments is created in memory. The this pointer is then bound to the object along with a few other special objects. Values passed into arguments are then assigned, and finally the runtime begins to execute the statements in the body of the function. 

The binding of this has one important exception which we’ll revisit in the unit on Asynchronous JavaScript. 

Invocation Versus Assignment

When working with functions, one potential source of confusion to those new to JavaScript is whether you are assigning/passing a function or invoking it. It all comes down to whether you use the ()

Consider our bike object’s calculateGearRatio function. 

let bike = {

...,
calculateGearRatio: function() {
let front = this.transmission.frontGearTeeth[this.frontGearIndex],
rear = this.transmission.rearGearTeeth[this.rearGearIndex];
return (front / rear);
},
...
}

Now consider these two different ways to access the calculateGearRatio function.  

// invoke function and assign value to ratioResult

let ratioResult = bike.calculateGearRatio();
// assign calculateGearRatio function to a new pointer
const ratioFunction = bike.calculateGearRatio;

In the first instance, calculateGearRatio is invoked with the result returned from the function being assigned (in this instance as a primitive value) to the ratioResult variable. On the other hand ratioFunction has simply been assigned or pointed to the calculateGearRatio function. You could then turn around and invoke it as ratioFunction()

There are reasons to assign a function to another pointer, particularly as a parameter of another function like with the Array.map() function. But any function using a this reference risks breaking, since this can point to different things at different times. More on that later. 

Functions as Event Handlers

If you want a function to fire as the result of an event, it needs to be wired up to that event. Doing so makes that function an event handler. If you need access to the context of the invoking event, the function definition needs to include a single argument to serve as a reference to the event that fired it. This argument is optional.

var handleClick = function(event) {

}

Each event has properties that tell you what you need to know about that event to deal with it. For example, for click you can detect data about the click (the event type, what element fired it, coordinates of the click, and so on).  

var handleClick = function(event) {

console.log(event.type);  // click
console.log(event.currentTarget); // the thing you clicked
console.log(event.screenX); // screen X coordinate
console.log(event.screenY); // screen Y coordinate
}

Assigning Event Handlers via DOM APIs

In simple web pages you may occasionally see explicit assigned event handlers in the HTML. 

<button onclick="handleClick(event)">

Click to Go
</button>

However modern web applications rarely use event binding in HTML. Instead, the DOM API is preferred, specifically the JavaScript Element.addEventListener() function. 

First you need a reference to the HTML element. Below we’ve added an id attribute to our button and removed the onclick attribute. 

Now we reach into the DOM, get the reference to the button, and assign the event listener, handleClick, by passing it in as a value (note, no parentheses). 

let button = document.getElementById("clicker");

button.addEventListener("click", handleClick);

Using the DOM API gives the developer flexibility to make the UI highly interactive and responsive to what the user is doing. The developer can also remove an event listener if functionality needs to be turned off. 

button.removeEventListener("click", handleClick);

You will also see anonymous functions added as event listeners. 

button.addEventListener("click", function(event){

//...anonymous function body...
});

Bear in mind, anonymous functions can’t be removed using removeEventListener, as there is no pointer to pass in to identify the function. 

Events and Functions in Lightning Web Components

The key code artifacts of a Lightning web component are a JavaScript module, an HTML template, and a CSS file. The only one of these that is required is the JavaScript module (mind the .xml file which is also required is not code, but metadata about the component). 

Functions in Lightning web components most often come in the form of methods that are members of the class exported by the component’s JavaScript module. These functions can be event handlers or other functions invoked downstream from there. 

The HTML template references handler functions in a way that resembles static HTML binding, but in fact it's different. Because the template is compiled into a JavaScript artifact, the static-looking binding is in fact just a syntactic convention the framework uses for invoking addEventListener sometime in the lifecycle of your component. 

This markup in the template shows event handler binding.

<lightning-input onchange={handleChange} label="Input Text" value={text}>

</lightning-input>

This is the event handler. 

    handleChange(event){

        this.text = event.target.value;
    }
Note

There are a number of more advanced features of events in Lightning Web Components. To explore these features, complete the Lightning Web Components Basics module, or dive into one of the sample apps.

Resources

Keep learning for
free!
Sign up for an account to continue.
What’s in it for you?
  • Get personalized recommendations for your career goals
  • Practice your skills with hands-on challenges and quizzes
  • Track and share your progress with employers
  • Connect to mentorship and career opportunities