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 scopevar globalVar = 'Hello, ';console.log(localVar); // Uncaught ReferenceError: localVar is not definedsomeFunction() { // 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 chainvar 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 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:
// Closuresfunction 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!"
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:
// Closuresfunction addCreator(x) { return function(y) { alert(x + y); }}var add1 = addCreator(1);var add5 = addCreator(5);add1(2); // alerts 3add5(2); // alerts 7
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}
); }); });}());
<body ng-app="myApp"> <div ng-controller="MyCtrl"> <input type="text" ng-model="text" /> <p>Text: {{text}}</p> </div></body>
To create the my-component
web component utilizing shadow DOM, our my-component.html
might look something like this:
<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>
For a much more indepth tutorial, check out .
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 (which initially attempted to use Web API web components, but ended up with its own implementation). Many JS frameworks (, , , ) 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 .
To learn more about web components, check out the following resources:
modern js glossary: Angular smart and dumb components
The smart (container) component looks like this:
// { Component } from '@angular/core';import { DumbComponent } from 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; }}
Auth0 hosted login screen
If you need to implement a robust, highly customizable system quickly and easily for your JavaScript SPAs and , Auth0 can help. Auth0 provides , an , plenty of , and . You can sign up for a free Auth0 account here to get started.
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 to learn about the concepts necessary to understand functional programming, reactive programming, and functional reactive programming.