TL;DR: In the first part of the Glossary of Modern JS Concepts series, we learned about functional, reactive, and functional reactive programming. In Part 2, we'll gain an understanding of concepts like scope, closures, tree shaking, components, and more, as well as JavaScript application topics such as data flow and change detection.
Introduction
Modern JavaScript has experienced massive proliferation over recent years and shows no signs of slowing. Numerous concepts appearing in JS blogs and documentation are still unfamiliar to many front-end developers. In this post series, we'll learn intermediate and advanced concepts in the current front-end programming landscape and explore how they apply to modern JavaScript, JS frameworks, and JS applications.
Concepts
In this article, we'll address concepts that are crucial to understanding modern JavaScript and JS applications, including scope and closures, data flow, change detection, components, compilation, and more.
You can jump straight into each concept here, or continue reading to learn about them in order.
- Scope (Global, Local, Lexical) and Closures
- One-Way Data Flow and Two-Way Data Binding
- Change Detection in JS Frameworks: Dirty Checking, Accessors, Virtual DOM
- Web Components
- Smart and Dumb Components
- JIT (Just-In-Time) Compilation
- AOT (Ahead-of-Time) Compilation
- Tree Shaking
Scope (Global, Local, Lexical) and Closures
"Explain closures" is an infamous JavaScript technical interview question. The truth is that plenty of skilled JS developers have difficulty explaining closures, even if they conceptually understand (and even use) them. Let's back up and talk about the concepts necessary to explain a closure.
Scope
In order to grasp closures, we need to understand scope first. Scope is simply the context of our code: where variables and functions are accessible.
The following example demonstrates two scopes, global scope and local scope:
// Global scope
var globalVar = 'Hello, ';
console.log(localVar); // Uncaught ReferenceError: localVar is not defined
someFunction() {
// Local scope
var localVar = 'World!';
console.log(globalVar + localVar); // 'Hello, World!'
}
Everything has access to the global scope. If we open an empty .js
file and type var globalVar
, this variable is accessible to anything else we'll create. If we executed the file in a browser, globalVar
's function scope would be window
.
Note: If we declare a new variable without the
var
keyword, it will be placed in the global scope no matter where it is in the code. You may have encountered this before (perhaps accidentally).
The someFunction
function creates its own local scope. It also inherits access to the global scope. We can freely use globalVar
inside someFunction
. However, the global scope does not have access to nested contexts, such as someFunction
's local scope. If we try to log localVar
from the global scope, we will receive an error because localVar
is not defined in the global scope.
In a nutshell, nested functions have their own scope. Functions declared inside another function also have access to their parent functions' scopes. This is called the scope chain.
Lexical scope (or static scope) refers to the fact that every nested function can access the functions that contain it.
Consider this example:
// Lexical scope and scope chain
var a = 1;
function outerFunc() {
var b = 2;
console.log(a + b);
function middleFunc() {
var c = 3;
console.log(a + b + c);
function innerFunc() {
var d = 4;
console.log(a + b + c + d);
}
innerFunc(); // logs 10 (1 + 2 + 3 + 4)
}
middleFunc(); // logs 6 (1 + 2 + 3)
}
outerFunc(); // logs 3 (1 + 2)
This code is available to run at this JSFiddle: JS Scope (open your browser's console to see results). innerFunc
is the innermost function. It is declared inside middleFunc
, which is in turn declared in outerFunc
.
The innerFunc
function can access variables declared in all of its parent scopes. Its scope chain allows access to:
a
from the global scope,b
fromouterFunc
,c
frommiddleFunc
, andd
frominnerFunc
's own local scope.
This only works down the nested functions, not up. For instance, the locally scoped variable d
is declared in innerFunc
and is not accessible to middleFunc
, outerFunc
, or the global scope.
Closures
In the first part of the Glossary of Modern JavaScript Concepts, we learned about higher-order functions and functions as first-class objects. If this doesn't sound familiar, take a moment to review the section on Higher-order Functions.
Now let's revisit the following higher-order function example we saw in Part 1:
// Higher-order function
function whenMeetingJohn() {
return function() {
alert('Hi!');
}
}
var atLunchToday = whenMeetingJohn();
atLunchToday(); // alerts "Hi!"
This is a function that returns another function. Let's update this example to add an argument (salutation
) and a greeting
variable to whenMeetingJohn
's local scope. We'll also name the previously anonymous returned function alertGreeting
so we can refer to it more easily:
// Closures
function whenMeetingJohn(salutation) {
var greeting = salutation + ', John!';
function alertGreeting() {
alert(greeting);
}
return alertGreeting;
}
var atLunchToday = whenMeetingJohn('Hi');
atLunchToday(); // alerts "Hi, John!"
whenMeetingJohn('Whassup')(); // alerts "Whassup, John!"
This code is available to run at this JSFiddle: JS Closures.
A closure is formed when a function (alertGreeting
) declared inside an outer function (whenMeetingJohn
) references variables from the outer function's local scope (such as the greeting
variable).
The term "closure" refers to the function and the lexical environment (any local variables that were in scope when the closure was created) in which that function was declared.
When we execute atLunchToday()
, we receive an alert with the argument we passed during assignment ('Hi'
in this case) and the greeting
variable that was accessible in alertGreeting
's lexical environment.
Note: We can also call the returned function (
alertGreeting
) without assigning it. Doing so looks like this:whenMeetingJohn('Whassup')()
.
Hopefully you can see the value in closures when looking at this simple example. We can greet John with several different salutations. Each time, we create a closure with access to the particular salutation data in scope at the time of creation.
Another common example demonstrating closures uses a simple addition expression:
// Closures
function addCreator(x) {
return function(y) {
alert(x + y);
}
}
var add1 = addCreator(1);
var add5 = addCreator(5);
add1(2); // alerts 3
add5(2); // alerts 7
This code can be run at this JSFiddle: JS Closures - Adder.
Both add1
and add5
are closures with different lexical environments storing different values for the x
argument. These values are protected by the fact that they're "enclosed" in the lexical environment of each closure. We could use the addCreator(x)
factory function to create as many add_
functions as we needed.
Scope and Closures Takeaways
Scope is something that many JS developers learn early on, but may not have had to explain in words or with specific examples. Understanding scope is vital to writing good JavaScript.
Note: There is more to scope than we covered here. There are some great resources available to achieve greater understanding, especially with regard to the
this
keyword. See below for more links.
Closures associate data with a function utilizing the lexical environment the function was declared in.
To learn more about scope and closures (and this
), check out the following resources:
- Everything you wanted to know about JavaScript scope
- Explaining JavaScript Scope and Closures
- Scope in JavaScript
- What is lexical scope?
- MDN: Closures
- What is "this"?
- MDN: this
- Understand JavaScript's "this" With Clarity, and Master It
- JavaScript: How does the "this" keyword work?
One-Way Data Flow and Two-Way Data Binding
With the proliferation of JavaScript frameworks and Single Page Applications (SPAs), it's important for JS developers to understand concepts like data flow / binding, and how the tools we're using manage this.
One-Way Data Flow
An application or framework with one-way data flow uses the model as the single source of truth. React is a widely recognized example of one-way data flow (or one-way data binding). Messages are sent from the UI in the form of events to signal the model to update.
Take a look at the following React example:
// One-way data flow with React
class OneWay extends React.Component {
constructor() {
super();
this.handleChange = this.handleChange.bind(this);
// set initial this.state.text to an empty string
this.state = {
text: ''
};
}
handleChange(e) {
// get new input value from the event and update state
this.setState({
text: e.target.value
});
}
render() {
return (
<div>
<input type="text" onChange={this.handleChange} />
<p>Text: {this.state.text}</p>
</div>
);
}
}
This code is available to run at JSFiddle: React One-Way Data Flow.
We can see that the state
object model is established in the constructor
function. The initial value of this.state.text
is an empty string. In our render()
function, we add an onChange
handler to our <input>
element. We use this handler to setState()
, signalling the state
object model to update the text
property with the new value of the input field.
Data is only flowing in one direction: from the model down. The UI input does not have direct access to the model. If we want to update state in response to changes from the UI, the input must send a message carrying the payload. The only way the UI can influence the model is through this event and the setState()
method. The UI will never automagically update the model.
Note: In order to reflect changes from the model to the UI, React creates a new virtual DOM and diffs the old virtual DOM with the updated virtual DOM. Only the changes are then rendered in the real DOM. We'll talk more about this in the section on change detection.
Two-Way Data Binding
In two-way data binding, the data flows in both directions. This means that the JS can update the model and the UI can do so as well. A common example of two-way data binding is with AngularJS.
Note: In this article, AngularJS refers specifically to version 1.x of the framework while Angular refers to versions 2.x and up, as per the Branding Guidelines for Angular.
Let's implement the same example from above, but with AngularJS two-way data binding:
// AngularJS two-way data binding
// script.js
(function() {
angular
.module('myApp', [])
.controller('MyCtrl', function($scope) {
// set initial $scope.text to an empty string
$scope.text = '';
// watch $scope.text for changes
$scope.$watch('text', function(newVal, oldVal) {
console.log(`Old value: ${oldVal}. New value: ${newVal}`);
});
});
}());
<!-- AngularJS two-way data binding -->
<!-- index.html -->
<body ng-app="myApp">
<div ng-controller="MyCtrl">
<input type="text" ng-model="text" />
<p>Text: {{text}}</p>
</div>
</body>
This code is available to run at Plunker: AngularJS Two-Way Binding.
In our controller, we set up the $scope.text
model. In our template, we associate this model with the <input>
using ng-model="text"
. When we change the input value in the UI, the model will also be updated in the controller. We can see this in the $watch()
.
Note: Using
$watch()
in a controller is debateable practice. We've done it here for example purposes. In your own AngularJS apps, take into consideration that there are alternatives to using$watch()
in controllers (such as events), and if you do use$watch()
, always deregister your watches$onDestroy
.
This is two-way binding in AngularJS. As you can see, we didn't set up any events or handlers to explicitly signal the controller that the model was updated in the UI. The text
data binding in the template automatically uses a watcher to display changes to the model. We can also $watch()
the model. Watching should generally be done in services or directive link
functions, not in controllers.
Note: AngularJS uses what's called the digest cycle (dirty checking) to compare a value with the previous value. You can read more about dirty checking in AngularJS in the section on change detection.
Aside: Two-Way Data Binding in Angular
But wait! Angular (v2+) has the "banana-in-a-box" [(ngModel)]
, right? On the surface, this may look like persistence of automagical two-way data binding. However, that is not the case. Angular's two-way binding [()]
syntax simply shortcuts property and event binding in a template, and the ngModel
directive supplies an ngModelChange
event for you. To learn more about this, check out this article on two-way binding in Angular.
The following are functionally equivalent and demonstrate the ngModel
directive:
// ngModel directive: two-way binding syntax
<input [(ngModel)]="text" />
<p>{{text}}</p>
// ngModel property and event binding
<input [ngModel]="text" (ngModelChange)="text=$event" />
<p>{{text}}</p>
The Angular docs on two-way binding cover this syntax thoroughly.
Data Flow and Binding Takeaways
Many modern JavaScript frameworks and libraries utilize unidirectional data flow (React, Angular, Inferno, Redux, etc.). Why? One-way data flow encourages clean architecture with regard to how data moves through an application. Application state is also easier to manage, updates are more predictable, and performance can be better as well.
Although automagical two-way data binding was one of AngularJS's most popular demos back in 2009, Angular has left it behind. Some Angular developers lamented this at first, but ultimately, many found that performance gains and greater control outweighed automagic.
As we saw in the React example above, it's important to remember that one-way data flow does not mean that it's difficult to update the store from the UI. It only means that such updates are done deliberately, with specific instruction. It's less magical, but much more manageable.
Note: Generally when developers mention "implementing two-way data binding" in frameworks with one-way data flow (such as React), they are referring to the steps necessary to have UI changes notify the state that it should be updated. They are not looking for a way to implement automagical two-way binding.
To learn more about one-way data flow and two-way data binding, check out the following resources:
- Video: Introducing One-Way Data Flow
- Diagrams comparing one-way and two-way data binding
- Why does React emphasize unidirectional data flow and Flux architecture?
- Data binding code in 9 JavaScript frameworks (from 2015, but still worth a look)
- Two-way Data Binding in Angular (v2+)
- AngularJS Docs - Data Binding
- Thinking in React
Change Detection in JS Frameworks: Dirty Checking, Accessors, Virtual DOM
Change detection is important for any dynamic JavaScript Single Page Application (SPA). When the user updates something, the app must have a way to detect and react to that change appropriately. Some kind of change detection is therefore vital to SPA frameworks.
At a fairly high level, let's explore a few methods of change detection used in popular JavaScript frameworks today.
Dirty Checking
Although Angular was released, AngularJS still accounts for multitudes of apps in production or development right now. AngularJS uses what's known as the digest cycle to detect changes in an application. Under the hood, the digest cycle is dirty checking. What does this mean?
Dirty checking refers to a deep comparison that is run on all models in the view to check for a changed value. AngularJS's digest cycle adds a watcher for every property we add to the $scope
and bind in the UI. Another watcher is added when we want to watch values for changes using $scope.$watch()
.
"AngularJS remembers the value and compares it to a previous value. This is basic dirty-checking. If there is a change in value, then it fires the change event."—Miško Hevery, creator of AngularJS and Angular
The digest cycle is a loop. AngularJS runs through its list of watchers and checks to see if any of the watched $scope
variables have changed (aka, are "dirty"). If a variable has not changed, it moves on to the next watched variable. If it finds one that is dirty, it remembers its new value and re-enters the loop. When no new changes are detected in the entire watch list, the DOM is updated.
The major advantages of dirty checking are that it's simple and predictable: there is no extending of objects and there are no APIs involved. However, it's also inefficient. Whenever anything changes, the the digest cycle is triggered. Therefore, it's important that care is taken when creating watchers in AngularJS. Every time a $scope
property is bound to the UI, a watcher is added. Every time a $watch()
is implemented, another watcher is added. Many directives also add watchers, and so do scope variables, filters, and repeaters.
Though dirty checking is sufficiently fast in a simple app, we can easily see how this can get out of hand in a complex implementation. This has led to articles such as 11 Tips to Improve AngularJS Performance: 1. Minimize/Avoid Watchers and Speeding up AngularJS's $digest loop.
Note: Angular (v2+) no longer uses dirty checking.
Accessors
Ember and Backbone use data accessors (getters and setters) for change detection. Ember objects inherit from Ember's APIs and have get()
and set()
methods that must be used to update models with data binding. This enables the binding between the UI and the data model and Ember then knows exactly what changed. In turn, only the modified data triggers change events to update the app.
Note: In Backbone, this is done with Backbone models with
get()
andset()
methods.
This method is straightforward and enforces that the application author be very deliberate regarding their data bindings. However, on the flipside of the same coin, it can occasionally lead to confusion because Ember.Object
s are only used when data binding to templates. If there is no UI data binding, updates do not use Ember.Object
s. This mixed approach can result in the developer scratching their head when things aren't updating because of a forgotten setter or getter.
Virtual DOM
Virtual DOM is used by React (and Inferno.js) to implement change detection. React doesn't specifically detect each change. Instead, the virtual DOM is used to diff the previous state of the UI and the new state when a change occurs. React is notified of such changes by the use of the setState()
method, which triggers the render()
method to perform a diff.
Virtual DOM (occasionally known as V-DOM) is a JavaScript data model that represents the real DOM tree. When a virtual DOM is generated, nothing is rendered to the browser. The old model is compared to the new model and once React determines which parts of the virtual DOM have changed, only those parts are patched in the real DOM.
Change Detection Takeaways
There are many ways that JavaScript frameworks manage change detection, including more that weren't covered here. They each have strengths and weaknesses, but the modern trend is toward more deliberate and less automagical methods, many utilizing observer pattern under the hood.
To learn more about change detection in JS frameworks, check out the following resources:
- Change and its Detection in JavaScript Frameworks
- Change Detection Overview
- Dirty checking in AngularJS
- The Digest Loop and Apply
- Ember.Object Class
- Accessors vs. Dirty Checking in JavaScript Frameworks
- React.JS internals: Virtual DOM
- The Difference Between Virtual DOM and DOM
- React: Reconciliation
- virtual-dom
- VueJS: How Changes are Tracked
- Polymer 2.0: Observers and Computed Properties
- Understanding Angular 2 Change Detection
- Angular Change Detection Explained
Web Components
Web components are encapsulated, reusable widgets based on web platform APIs. They are composed of four standards:
Web components allow us to architect and import custom elements that automatically associate JS behavior with templates and can utilize shadow DOM to provide CSS scoping and DOM encapsulation.
Web components consist of a set of web platform APIs. There are libraries (such as Polymer) and polyfills (such as webcomponents.js) to bridge the gap between current browser support and future web API support.
Let's say we want to create a simple web component (my-component
) that shows some static text. We'd like to use HTML attributes to have the component change its text color and log something to the console. To display the <my-component>
custom element in our website or app, we might import and use it like so:
<!-- index.html -->
<html>
<head>
...
<script src="https://cdnjs.cloudflare.com/ajax/libs/webcomponentsjs/0.7.24/webcomponents.min.js"></script>
<link rel="import" href="my-web-cmpnt.html">
</head>
<body>
<my-web-cmpnt color="red" log="Hello"></my-web-cmpnt>
...
To create the my-component
web component utilizing shadow DOM, our my-component.html
might look something like this:
<!-- my-component.html -->
<template>
<style>
.my-component {
display: block;
padding: 20px;
}
</style>
<div class="my-component">
<p>This is a custom element!</p>
</div>
</template>
<script>
(function(window, document, undefined) {
var doc = document;
// my-component document
var self = (doc._currentScript || doc.currentScript).ownerDocument;
var template = self.querySelector('template').content;
// ShadowCSS shim, if needed
if (window.ShadowDOMPolyfill) {
WebComponents.ShadowCSS.shimStyling(template, 'my-component');
}
class MyComponent extends HTMLElement {
constructor() {
super();
}
connectedCallback() {
// get attributes
var color = this.getAttribute('color');
var log = this.getAttribute('log');
// utilize shadow DOM
var shadowRoot = this.attachShadow({mode:'open'});
var clone = doc.importNode(template, true);
var myComponent;
shadowRoot.appendChild(clone);
myComponent = shadowRoot.querySelector('.my-component');
// style with color and output log
myComponent.style.color = color;
console.log(log);
}
}
window.customElements.define('my-component', MyComponent);
}(window, document));
</script>
This code is available to run at Plunker: Web components.
The <template>
defines the element's CSS styling and HTML markup. Then, to take advantage of shadow DOM and JS functionality, we add a <script>
tag to our my-component.html
file after the closing </template>
tag and implement our desired custom element JS functionality to get the element's attributes and use them to change the text color and log our message.
When inspected with Chrome devtools, our component looks like this:
For a much more indepth tutorial, check out Web Components: How To Craft Your Own Custom Components.
Web components bring powerful, framework-like capabilities to browsers, and while the spec and support are still being finalized, the concepts have inspired frameworks such as Angular (which initially attempted to use Web API web components, but ended up with its own implementation). Many JS frameworks (Angular, React, Ember, Vue) leverage the concept of componetization with varying degrees of similarity to the web components API.
Web Components Takeaways
Web components allow us to create and use custom HTML elements with JS. They can also utilize the shadow DOM to provide CSS scoping and DOM encapsulation. By themselves, web components alone aren't a substitute for all the features of a SPA framework. However, their core concepts are heavily leveraged by many frameworks in use today and their future in the front-end landscape offers many opportunities for a growing community of resources.
To learn more about web components, check out the following resources:
- W3C Web Components Current Status
- webcomponents.org
- Web Components: How To Craft Your Own Custom Components
- MDN: Web Components
- Using Web Components in React
- Build Your First App with Polymer and Web Components
- Are We Componetized Yet?
- webcomponentjs on GitHub
- Google Polymer
Smart and Dumb Components
As we talked about above, some modern JS frameworks are heavily component-based. This leads to concepts of component architecture and component communication. In some cases (such as in React and Angular), component architecture utilizes smart and dumb components. They are also referred to as "container" (smart) and "presentational" (dumb) components.
Smart Components
Also known as container components, smart components can manage interactions with the application's state and data. They handle business logic and respond to events emitted from children (which are often dumb components).
Dumb Components
Also known as presentational components, dumb components rely on inputs supplied by their parents and are ignorant of application state. They can be sometimes be considered pure and are modular and reusable. They can communicate to their parents when reacting to an event, but they don't handle the event themselves.
Presentational and Container Components in React
Dan Abramov, the co-author of Redux, Create React App, React Hot Loader, and more, originally wrote about presentational and container components and their meaning in React, particularly when used with state management like Flux or Redux. In a nutshell, the concepts can be summarized as follows:
Presentational (aka Dumb) Components:
- Focus on "How things look"
- Allow containment with
this.props.children
- No dependencies on the rest of the app (ie., no Flux or Redux actions or stores)
- Only receive data; do not load or mutate it
- Are generally functional (with exceptions)
Container (aka Smart) Components:
- Focus on "How things work"
- Provide data and behavior to other components
- Usually have no or very little DOM markup, and never have styles
- Are often stateful data sources
- Are generally generated from higher order components
Check out his article for more details and explanation.
Example: Smart and Dumb Components in Angular
For a quick example that doesn't involve a state container, let's look at some simple smart and dumb components using Angular.
Let's say we want a madlib-style feature where we apologize for lashing out while hungry, tired, or debugging. When we're done, it should look like this in the browser:
The smart (container) component looks like this:
// app/smart.component.ts
import { Component } from '@angular/core';
import { DumbComponent } from './dumb.component';
@Component({
selector: 'my-smart-cmpnt',
template: `
<h1>I'm sorry for what I said when I was {{selectedOption}}.</h1>
<my-dumb-cmpnt
[options]="optionsArr"
(changedOption)="onOptionChange($event)"></my-dumb-cmpnt>
`
})
export class SmartComponent {
optionsArr = ['hungry', 'tired', 'debugging'];
selectedOption = '______';
onOptionChange(e: string) {
this.selectedOption = e;
}
}
This component manages all of the data necessary for this feature. It provides the options for the madlib (optionsArr
) and then handles when the user chooses an option (onOptionChange()
). It stores the selectedOption
, passes the possible options into the dumb component, and sets the selected option when the dumb component emits a changedOption
event.
// app/dumb.component.ts
import { Component, Input, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'my-dumb-cmpnt',
template: `
<div *ngFor="let option of options">
<button (click)="select(option)">{{option}}</button>
</div>
`
})
export class DumbComponent {
@Input() options: Array;
@Output() changedOption = new EventEmitter();
select(option) {
this.changedOption.emit(option);
}
}
In turn, the dumb component accepts the options array as input and iterates over each item to create the buttons to select an option. When an option is clicked, the changedOption
event is emitted with the selected option as its payload. The parent smart component then handles this event and sets its selectedOption
for display in the UI.
This code is available to run at Plunker: Angular Smart and Dumb Components.
Smart and Dumb Components Takeaways
Smart (container) components manage data, implement business logic, and handle events. Dumb (presentational) components accept inputs and can emit events, which are handled by a parent smart component. Dumb components are modular and can be reused throughout an application due to their more stateless nature. When using a state container like Redux or ngrx/store, only smart components would dispatch actions or interact with the store.
To learn more about smart and dumb components, check out the following resources:
- Container Components
- Presentational and Container Components
- Presentational vs container components
- React at Preact: Smart Components
- React at Preact: Dumb Components
- Angular Smart Components vs Presentation Components: What's the Difference, When to Use Each and Why?
- Managing State in Angular with ngrx/store
JIT (Just-In-Time) Compilation
Just-In-time (JIT) compilation is the process of translating code written in a programming language to machine code at runtime (during a program or application's execution). At runtime, certain dynamic information is available, such as type identification. A JIT compiler monitors to detect functions or loops of code that are run multiple times—this code is considered "warm". These pieces of code are then compiled. If they're quite commonly executed ("hot"), JIT will optimize them and also store the optimized, compiled code for execution.
When the compiler optimizes hot code, it makes assumptions about its types and shape based on consistency of previous executions. At any iteration, if those assumptions turn out to be inaccurate, the optimized code is discarded.
Browsers use JIT compilation to run JavaScript. In the modern JavaScript framework landscape, compilers in frameworks like Angular can use JIT to compile TypeScript and Angular code to JS to machine code at runtime during local development, compiling each file separately. This provides certain advantages, such as no need to rebuild the project when watching for code changes and a faster initial build time.
JIT Compilation Takeaways
JIT compilation is used by browsers to compile JavaScript at runtime, and by frameworks like Angular to provide a fast local development experience.
To learn more about JIT compilation, check out the following resources:
- What does a just-in-time (JIT) compiler do?
- JIT compiler overview
- 1/3 A cartoon intro to WebAssembly
- 2/3 A crash course in just-in-time (JIT) compilers
- 3/3 A crash course in assembly
- The race for speed part 2: How JavaScript compilers work
- Mozilla TraceMonkey
- How the V8 engine works?
- JIT vs AOT in Angular2 and how to use it
AOT (Ahead-Of-Time) Compilation
Ahead-Of-Time (AOT) compilation is the process of translating code written in a programming language to machine code before execution (as opposed to at runtime). Doing so reduces runtime overhead and compiles all files together rather than separately.
With regard to JavaScript application AOT, such as that used by Angular, this means inlining HTML and CSS and being able to deploy without the compiler, saving considerable size. The browser can also render the application immediately since it's precompiled.
There are several benefits to AOT for production builds:
- Fewer asynchronous requests: templates and styles are inlined with JS
- Smaller download size: the compiler doesn't need to be downloaded if the app is already compiled
- Detect template errors earlier: compiler detects binding errors during build rather than at runtime
- Better security: evaluation is already done, which lowers chance of injection
AOT also enables tree shaking. In the browser, an app compiled with AOT promotes a shorter total time to load and bootstrap due to being precompiled. Initial rendering time is also reduced because less code needs to be parsed.
However, AOT will have a longer initial build time than JIT and would require a full recompile of the entire app if any changes are made.
AOT Compilation Takeaways
AOT compilation takes place before runtime and bundles all files together. It enables tree shaking, smaller downloads, and improved security compared to JIT.
To learn more about AOT compilation, check out the following resources:
- Angular: Is AOT Worth It?
- Ahead-of-Time Compilation in Angular
- Ahead-of-Time Comilation
- IBM: The ahead-of-time compiler
Tree Shaking
Tree shaking is a JavaScript module bundling term that refers to the static analysis of all imported code and exclusion of anything that isn't actually used.
In a more literal analogy, consider a living tree. The tree is shaken and this causes the dead leaves to fall off, leaving behind the leaves the tree is actively using for photosynthesis. The concept behind tree shaking is live code inclusion: we include the parts that are needed to begin with, as opposed to removing the parts that are unneeded at the end (dead code elimination).
Tree shaking relies on ES2015 module import
and export
. The import
and export
statements compose an app's static module structure. When the modules are bundled for deployment, the tree shaker analyzes the static module structure so that unused exports can be excluded, reducing the size of the final bundle.
ES2015 enables us to specify explicit imports. For example, rather than importing the entire RxJS library, we can only import exactly what we want:
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
This differs from the dynamic require
statement used by CommonJS or AMD. Tree shaking uses this principle to walk the dependency graph and exclude things that aren't needed in order to reduce the size of deployment bundles.
The principle of tree shaking is not new, but it has recently been popularized by the rollup.js module bundler. Tree shaking is utilized in Webpack 2 as well. The concept of tree shaking and writing code that promotes it is also prevalent in Angular withAOT compilation.
Tree Shaking Takeaways
Tree shaking is term for JavaScript live code inclusion in module bundlers that use ES2015 static import
and export
to "shake out" unneeded dependencies on a more granular level.
To learn more about tree shaking, check out the following resources:
- Tree-shaking versus dead code elimination
- rollup.js
- Tree Shaking with Webpack
- Tree-shaking with webpack 2 and Babel 6
- Angular Docs: Tree shaking
- Kendo UI for Angular: Tree Shaking
- How To Clean Up Your JavaScript Build With Tree Shaking
Aside: Learning and Implementing Authentication with Auth0
One of the most complex features to implement in an application is user authentication and identity management. Security for authentication and identity is an entire glossary unto itself.
If you need to implement a robust, highly customizable identity and access management system quickly and easily for your JavaScript SPAs and Node APIs, Auth0 can help. Auth0 provides Single Page Application QuickStart guides, an SDK for web, plenty of documentation, and this blog's articles and tutorials. You can sign up for a free Auth0 account here to get started.
Auth0 offers a generous free tier to get started with modern authentication.
Conclusion
With the swift rise of JavaScript Single Page Application frameworks and component-based paradigms, it's important to understand JS topics relating to scoping, data flow, components, compilation, and bundling.
With this glossary as a starting point, you can begin taking advantage of these concepts and programming paradigms to increase your JavaScript expertise. If anything is still unclear regarding these topics, please consult the links in each section for additional resources. You can also check out the first part of the Glossary of Modern JS Concepts to learn about the concepts necessary to understand functional programming, reactive programming, and functional reactive programming.