Beware of Globals
Things that Belong to Objects
Previously, we saw that the properties and methods contained inside an object_belong_to that object. Let's drive this home with one quick example:
const chameleon = {
eyes: 2,
lookAround: function () {
console.log(`I see you with my ${this.eyes} eyes!`);
}
};
chameleon.lookAround();
// 'I see you with my 2 eyes!'
We've already looked at howthis
inside a method refers to the object that the method was called on. Let's take a closer look atchameleon
'slookAround()
method.
lookAround: function () {
console.log(`I see you with my ${this.eyes} eyes!`);
}
Inside the function body is the codethis.eyes
. Since thelookAround()
method was called on the chameleon object aschameleon.lookAround();
, the value ofthis
is thechameleon
object itself! As such,this.eyes
is the number2
, since it refers to the value of thechameleon
object'seyes
property.
What is this?
Now, let's check out a different example. What do you think will be the value ofthis
inside the following code?
function whoThis () {
this.trickyish = true
}
whoThis();
// (what does the above expression output?)
Write your thoughts below.
SUBMIT:?
this
and Function Invocation
Let's compare the code from thechameleon
object with thewhoThis()
code.
const chameleon = {
eyes: 2,
lookAround: function () {
console.log(`I see you with my ${this.eyes} eyes!`);
}
};
chameleon.lookAround();
function whoThis () {
this.trickyish = true
}
whoThis();
this
in the Function/Method
Before we dive into how this all works, take a look at the use ofthis
inside both of these code snippets:
// from the chameleon code:
console.log(`I see you with my ${this.eyes} eyes!`);
// from the whoThis() code:
this.trickyish = true
There is some other code around them, but both of them have the formatthis.<some-identifier>
. For our purposes of discovering the value ofthis
, it does not matter that in thechameleon
code, we're usingthis
to _retrieve _a property, while in thewhoThis()
code, we're usingthis
to _set _a property.
So, in both of these cases, the _use _ofthis
is virtually identical.
Compare the Structures of the Function/Method
Now, I want you to pay attention to the differences in _structure _of how the two snippets of code are invoked. The lookAround()
code is a _method _because it belongs to an object. Since it's a method, it's invoked as a property on the chameleon
object:
chameleon.lookAround();
Now compare that with thewhoThis()
code.whoThis()
is_not_a method; it's a plain, old, regular function. And look at how thewhoThis()
function is invoked:
whoThis();
Just like every normal function is invoked; it's just the name of the function and the parentheses (there's no object and no dot in front of it).
this
and Invocation
How the function is invoked determines the value ofthis
inside the function.← That sentence is really important, so read that two more times...we'll wait!
Because.lookAround()
is invoked as a method, the value ofthis
inside of.lookAround()
is whatever is _left of the dot _at invocation. Since the invocation looks like:
chameleon.lookAround();
Thechameleon
object is left of the dot. Therefore, inside the.lookAround()
method,this
will refer to thechameleon
object!
Now let's compare that with thewhoThis()
function. Since it is called as a regular function (i.e., _not _called as an method on an object), its invocation looks like:
whoThis();
Well, there is no dot. And there is no object left of the dot. So what is the value ofthis
inside thewhoThis()
function? This is an interesting part of the JavaScript language.
When a regular function is invoked, the value ofthis
is the globalwindow
object.
Let's see it all in action!
Here's the code from the preceding video.
The
window
ObjectIf you haven't worked with the
window
object yet, this object is provided by the browser environment and is globally accessible to your JavaScript code using the identifier,window
. This object is not part of the JavaScript specification (i.e., ECMAScript); instead, it is developed by the W3C.This
window
object has access to a ton of information about the page itself, including:
- The page's URL (
window.location;
)- The vertical scroll position of the page (
window.scrollY'
)- Scrolling to a new location (
window.scroll(0, window.scrollY + 200);
to scroll 200 pixels down from the current location)- Opening a new web page (
window.open("https://www.udacity.com/");
)
QUESTION 2 OF 3
You've seen whatthis
refers to inchameleon.lookAround();
and inwhoThis()
. Carefully review this code:
const car = {
numberOfDoors: 4,
drive: function () {
console.log(`Get in one of the ${this.numberOfDoors} doors, and let's go!`);
}
};
const letsRoll = car.drive;
letsRoll();
What does you thinkthis
refers to in the code above?
TheletsRoll
function itselfThe
window
objectThedocument
objectThe<body>
elementIt will cause an error
SUBMIT: Even though car.drive
is a method, we're storing the function itself in the a variable letsRoll
. Because letsRoll()
is invoked as a regular function, this
will refer to the window
object inside of it.
Global Variables are Properties onwindow
Since thewindow
object is at the highest (i.e., global) level, an interesting thing happens with global variable declarations. Every variable declaration that is made at the global level (outside of a function) automatically becomes a property on thewindow
object!
var currentlyEating = 'ice cream';
window.currentlyEating === currentlyEating
// true
Here we can see that thecurrentlyEating
variable is set to'ice cream'
. Then, we immediately see that thewindow
now has acurrentlyEating
property! Checking this property against thecurrentlyEating
variable shows us that they are identical.
Globals and
var
,let
, andconst
The keywords
var
,let
, andconst
are used to declare variables in JavaScript.var
has been around since the beginning of the language, whilelet
andconst
are significantly newer additions (added in ES6).Only declaring variables with the
var
keyword will add them to thewindow
object. If you declare a variable outside of a function with eitherlet
orconst
, it willnotbe added as a property to thewindow
object.
let currentlyEating = 'ice cream'; window.currentlyEating === currentlyEating // false!
Global Functions are Methods onwindow
Similarly to how global variables are accessible as properties on thewindow
object, any global function declarations are accessible on thewindow
object as methods:
function learnSomethingNew() {
window.open('https://www.udacity.com/');
}
window.learnSomethingNew === learnSomethingNew
// true
Declaring thelearnSomethingNew()
function as a global function declaration (i.e., it's globally accessible and not written _inside _another function) makes it accessible to your code as eitherlearnSomethingNew()
orwindow.learnSomethingNew()
.
QUESTION 3 OF 3
Which of the following variables and functions will be available on thewindow
object?
var iceCreamEaten = 1;
function consume (numberOfGallons) {
var result = iceCreamEaten + numberOfGallons;
function updateTotals (newTotal) {
iceCreamEaten = result;
}
updateTotals();
}
consume(3);
iceCreamEaten
consume
numberOfGallons
result
updateTotals
SUBMIT: Only consume()
and iceCreamEaten
are declared globally, so only these two identifiers are accessible as properties on
window
.
Avoid Globals
We've seen that declaring global variables and functions add them as properties to thewindow
object.Globally-accessible code sounds like something that might be super helpful, right? I mean, wouldn't it be great if you could always be within arms reach of some ice cream (or is that just my lifelong dream)?
Counterintuitively, though, global variables and functions are _not _ideal. There are actually a number of reasons why, but the two we'll look at are:
- Tight coupling
- Name collisions
Tight Coupling
Tight couplingis a phrase that developers use to indicate code that is too dependent on the details of each other. The word "coupling" means the "pairing of two items together." In tight coupling, pieces of code are joined together in a way where changing one unintentionally alters the functioning of some other code:
var instructor = 'Richard';
function richardSaysHi() {
console.log(`${instructor} says 'hi!'`);
}
In the code above, note that theinstructor
variable is declared globally. TherichardSaysHi()
function does _not _have a local variable that it uses to store the instructor's name. Instead, it reaches out to the global variable and uses that. If we refactored this code by changing the variable frominstructor
toteacher
, this would break therichardSaysHi()
function (or we'd have to update it there, too!). This is a (simple) example of tightly-coupled code.
Name Collisions
Aname collisionoccurs when two (or more) functions depend on a variable with the same name. A major problem with this is that both functions will try to update the variable and or set the variable, but these changes are overridden by each other!
Let's look at an example of name collision with this DOM manipulation code:
let counter = 1;
function addDivToHeader () {
const newDiv = document.createElement('div');
newDiv.textContent = 'div number ' + counter;
counter = counter + 1;
const headerSection = document.querySelector('header');
headerSection.appendChild(newDiv)
}
function addDivToFooter() {
const newDiv = document.createElement('div');
newDiv.textContent = 'div number ' + counter;
counter = counter + 1;
const headerSection = document.querySelector('footer');
headerSection.appendChild(newDiv)
}
In this code, we have anaddDivToHeader()
function and aaddDivToFooter()
function. Both of these functions create a<div>
element and increment acounter
variable.
This code looks fine, but if you try running this code and adding a few<div>
s to the<header>
and<footer>
elements, you'll find that the numbering will get off! BothaddDivToHeader()
andaddDivToFooter()
expect a globalcounter
variable to be accessible to them -- not change out from under them!
Since both functions increment thecounter
variable, if the code alternates between callingaddDivToHeader()
andaddDivToFooter()
, then their respective<div>
s will not have numerically ascending numbers. For example, if we had the following calls:
addDivToHeader();
addDivToHeader();
addDivToFooter();
addDivToHeader();
The developer_probably wanted_the<header>
to have three<div>
elements with the numbers 1, 2, and 3 and the<footer>
element to have a single<div>
with the number 1. However, what this code will produce is a<header>
element with three<div>
but with the numbers 1, 2, and 4 (not 3) and a<footer>
element with the number 3...these are very different results. But it's happening because both functions depend on thecounter
variable and both update it.
So what should you do instead? You should write as few global variables as possible. Write your variables inside of the functions that need them, keeping them as close to where they are needed as possible. Now, there_are_times when you'll need to write global variables, but you should only write them as a last resort.
Summary
Thewindow
object is provided by the browser and is not part of the JavaScript language or specification. Any global variable declarations (i.e., those that usevar
) or global function declarations are added as properties to thiswindow
object. Excessive use of global variables is not a good practice, and can cause unexpected problems to accurately-written code.
Whether you're working with thewindow
object, or with an object you create yourself, recall that all objects are made up of key/value pairs. In the next section, we'll check out how to extract these individual keys or values!
Further Research
- The window object on MDN
- The window specification on W3C
- Article: Globals are Bad Coupling on Wikipedia
- Name Collision on Wikipedia