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 howthisinside 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 ofthisis thechameleonobject itself! As such,this.eyesis the number2, since it refers to the value of thechameleonobject'seyesproperty.

What is this?

Now, let's check out a different example. What do you think will be the value ofthisinside the following code?

function whoThis () {
  this.trickyish = true
}

whoThis();
// (what does the above expression output?)

Write your thoughts below.

SUBMIT:?

thisand Function Invocation

Let's compare the code from thechameleonobject 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();

thisin the Function/Method

Before we dive into how this all works, take a look at the use ofthisinside 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 thechameleoncode, we're usingthisto _retrieve _a property, while in thewhoThis()code, we're usingthisto _set _a property.

So, in both of these cases, the _use _ofthisis 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 chameleonobject:

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).

thisand Invocation

How the function is invoked determines the value ofthisinside 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 ofthisinside of.lookAround()is whatever is _left of the dot _at invocation. Since the invocation looks like:

chameleon.lookAround();

Thechameleonobject is left of the dot. Therefore, inside the.lookAround()method,thiswill refer to thechameleonobject!

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 ofthisinside thewhoThis()function? This is an interesting part of the JavaScript language.

When a regular function is invoked, the value ofthisis the globalwindowobject.

Let's see it all in action!

VIDEO

Here's the code from the preceding video.

ThewindowObject

If you haven't worked with thewindowobject 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.

Thiswindowobject 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 whatthisrefers 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 thinkthisrefers to in the code above?

  • TheletsRollfunction itself

  • Thewindowobject

  • Thedocumentobject

  • The<body>element

  • It 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 thewindowobject 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 thewindowobject!

var currentlyEating = 'ice cream';

window.currentlyEating === currentlyEating
// true

Here we can see that thecurrentlyEatingvariable is set to'ice cream'. Then, we immediately see that thewindownow has acurrentlyEatingproperty! Checking this property against thecurrentlyEatingvariable shows us that they are identical.

Globals andvar,let, andconst

The keywordsvar,let, andconstare used to declare variables in JavaScript.varhas been around since the beginning of the language, whileletandconstare significantly newer additions (added in ES6).

Only declaring variables with thevarkeyword will add them to thewindowobject. If you declare a variable outside of a function with eitherletorconst, it willnotbe added as a property to thewindowobject.

let currentlyEating = 'ice cream';

window.currentlyEating === currentlyEating 
// false!

Global Functions are Methods onwindow

Similarly to how global variables are accessible as properties on thewindowobject, any global function declarations are accessible on thewindowobject 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 thewindowobject?

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 thewindowobject.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 theinstructorvariable 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 frominstructortoteacher, 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 acountervariable.

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 globalcountervariable to be accessible to them -- not change out from under them!

Since both functions increment thecountervariable, 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 thecountervariable 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

Thewindowobject 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 thiswindowobject. Excessive use of global variables is not a good practice, and can cause unexpected problems to accurately-written code.

Whether you're working with thewindowobject, 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

results matching ""

    No results matching ""