Good habits or bad habits

JavaScript is an easy language to learn. You can grab a snippet from the internet and pop it into your HTML page, and you’ve started on your journey. One reason why it’s easy to learn is that in some respects, it’s not as strict as it should be. It lets you do things that it possibly shouldn’t, which leads to bad habits. In this section, we’ll take a look at some of these bad habits and show you how to turn them into good habits.

Variables, scope, and functions

The first step is looking at variables, scope, and functions, which are all closely tied together. JavaScript has three types of scope: global, function (using the var keyword), and lexical (using let or const keywords). JavaScript also has scope inheritance. If you declare a variable in global scope, it’s accessible by everything; if you declare a variable with var inside a function, it’s accessible only to that function and everything inside it; if you declare a variable with let or const in a block, it’s accessible inside the braces and everything inside that block, but unlike var, access doesn’t bleed through to the surrounding function block.

The var keyword in ES2015 and later

Modern practice tends to frown on using the var keyword, which will eventually be deprecated. var comes with a lot of baggage, and if you’re coming from other languages, its scoping can be difficult to work with and can trip up even the most experienced developer. We’ll discuss it here, though, because a lot of JavaScript has been built with var.

With ES2015, the language specification introduced the let and const keywords, which are lexically (block) scoped. These keywords have greater similarity with other variable-definition schemes. The difference is explained in more detail in the following sections.

Working with scope and scope inheritance

Start with a simple example in which scope is used incorrectly.

Listing D.3. Scope example
const firstname = 'Simon';                    1
const addSurname = function () {
  const surname = 'Holmes';                   2
  console.log(firstname + ' ' + surname);     3
};
addSurname();
console.log(firstname + ' ' + surname);       4

  • 1 Variable declared in global scope
  • 2 Variable declared in local lexical scope
  • 3 Outputs “Simon Holmes”
  • 4 Throws error because surname isn’t defined

This piece of code throws an error because it’s trying to use the variable surname in the global scope, but it was defined in the local scope of the function addSurname(). A good way to visualize the concept of scope is to draw some nested circles. In figure D.1, the outer circle depicts the global scope; the middle circle depicts the function scope; and the inner circle depicts lexical scope. You can see that the global scope has access to the variable firstname and that the local scope of the function addSurname() has access to the global variable firstname and the local variable surname. In this case, lexical scope and function scope overlap.

Figure D.1. Scope circles depicting global scope versus local scope and scope inheritance

If you want the global scope to output the full name while keeping the surname private in the local scope, you need a way of pushing the value into the global scope. In terms of scope circles, you’re aiming for what you see in figure D.2. You want a new variable, fullname, that you can use in both global and local scopes.

Figure D.2. Using an additional global variable to return data from the local scope

Pushing from local to global scope: The wrong way

One way you could do it—and we’ll warn you now that it’s bad practice—is to define a variable against the global scope from inside the local scope. In the browser, the global scope is the object window; in Node.js, it’s global. Sticking with browser examples for now, the following listing shows how this would look if you updated the code to use the fullname variable.

Listing D.4. Global fullname variable
const firstname = 'Simon';
const addSurname = function () {
  const surname = 'Holmes';
  window.fullname = firstname + ' ' + surname;      1
  console.log(fullname);
};
addSurname();
console.log(fullname);                              2

  • 1 The fullname variable is defined in the window object.
  • 2 Global scope can output the full name.

This approach allows you to add a variable to the global scope from inside a local scope, but it’s not ideal. The problems are twofold. First, if anything goes wrong with the addSurname() function and the variable isn’t defined, when the global scope tries to use it, you’ll get an error thrown. The second problem becomes obvious when your code grows. Suppose that you have dozens of functions adding things to different scopes. How do you keep track of them? How do you test them? How do you explain to someone else what’s going on? The answer to all these questions is with great difficulty.

Pushing from local to global scope: The right way

If declaring the global variable in the local scope is wrong, what’s the right way? The rule of thumb is always declare variables in the scope in which they belong. If you need a global variable, you should define it in the global scope, as in the following listing.

Listing D.5. Declaring globally scoped variables
var firstname = 'Simon',
    fullname;                    1
var addSurname = function () {
  var surname = 'Holmes';
  window.fullname = firstname + ' ' + surname;
  console.log(fullname);
};
addSurname();
console.log(fullname);

  • 1 Variable declared in global scope, even if a value isn’t assigned to it yet

Here, it’s obvious that the global scope now contains the variable fullname, which makes the code easier to read when you come back to it.

Referencing global variables from local scope

You may have noticed that from within the function, the code still references the global variable by using the fully qualified window.fullname. It’s best practice to do this whenever you reference a global variable from a local scope. Again, this practice makes your code easier to come back to and debug, because you can explicitly see which variable is being referenced. The code should look like the following listing.

Listing D.6. Using global variables in local scope
var firstname = 'Simon',
    fullname;
var addSurname = function () {
  var surname = 'Holmes';
  window.fullname = window.firstname + ' ' + surname;      1
  console.log(window.fullname);                            1
};
addSurname();
console.log(fullname);

  • 1 When using global variables in local scope, always use the fully qualified reference.

This approach might add a few more characters to your code, but it makes it obvious which variable you’re referencing and where it came from. There’s another reason for this approach, particularly when assigning a value to a variable.

Implied global scope

JavaScript lets you declare a variable without using var, which is a bad thing indeed. Worse, if you declare a variable without using var, JavaScript creates the variable in the global scope, as shown in the following listing.

Listing D.7. Declaring without var
var firstname = 'Simon';
var addSurname = function () {
  surname = 'Holmes';                      1
  fullname = firstname + ' ' + surname;    1
  console.log(fullname);
};
addSurname();
console.log(firstname + surname);          2
console.log(fullname);                     2

  • 1 surname and fullname are both defined in the global scope by implication.
  • 2 They can be used in the global scope.

We hope that you can see how this could be confusing and is a bad practice. The takeaway is always declare variables in the scope in which they belong, using the var statement.

The problem of variable hoisting

You’ve probably heard that with JavaScript, you should always declare your variables at the top. That’s correct, and the reason is because of variable hoisting. With variable hoisting, JavaScript declares all variables at the top anyway without telling you, which can lead to some unexpected results.

The following code listing shows how variable hoisting might show itself. In the addSurname() function, you want to use the global value of firstname and later declare a local scope value.

Listing D.8. Shadowing example
var firstname = 'Simon';
var addSurname = function () {
  var surname = 'Holmes';
  var fullname = firstname + ' ' + surname;     1
  var firstname = 'David';
  console.log(fullname);                        2
};
addSurname();

  • 1 You expect this to use a global variable.
  • 2 The output is actually “undefined Holmes.”

Why is the output wrong? JavaScript “hoists” all variable declarations to the top of their scope. You see the code in listing D.8, but JavaScript sees the code in listing D.9.

Listing D.9. Hoisting example
var firstname = 'Simon';
var addSurname = function () {
  var firstname,                           1
      surname,                             1
      fullname;                            1
  surname = 'Holmes';
  fullname = firstname + ' ' + surname;    2
  firstname = 'David';
  console.log(fullname);
};
addSurname();

  • 1 JavaScript has moved all variable declarations to the top.
  • 2 No value is assigned before it’s used, so it’s undefined.

When you see what JavaScript is doing, the bug is a little more obvious. JavaScript has declared the variable firstname at the top of the scope, but it doesn’t have a value to assign to it, so JavaScript leaves the variable undefined when you first try to use it.

You should bear this fact in mind when writing your code. What JavaScript sees should be what you see. If you can see things from the same perspective, you have less room for error and unexpected problems.

Lexical scope

Lexical scope is sometimes called block scope. Variables defined between a set of braces are limited to the scope of those braces. Therefore, scoping can be limited to looping and flow logic constructs.

JavaScript defines two keywords that provide lexical scope: let and const. Why two? The functionality of the two is slightly different.

let is a bit like var. It sets up a variable that can be changed in the scope in which it is defined. It differs from var in that its scope is limited as described earlier, and variables declared this way aren’t hoisted. As they’re not hoisted, they’re not tracked by the compiler the same way as var; the compiler leaves them where they are on the first pass, so if you try to reference them before they’re defined, the compiler complains with a ReferenceError.

Listing D.10. let in action
if (true) {
  let foo = 1;               1
  console.log(foo);          2
  foo = 2;                   3
  console.log(foo);          4
  console.log(bar);          5
  let bar = 'something';     6
}

  • 1 Initially declares variable
  • 2 Prints out value of 1
  • 3 Redefines value
  • 4 Prints out value of 2
  • 5 Tries to print out a value that’s not defined yet (ReferenceError)
  • 6 Definition of variable that’s not hoisted

const has the same caveats as let. const differs from let in that variables declared in such a way aren’t allowed to change, either by reassignment or redeclaration; they’re declared to be immutable. const also prevents shadowing—redefining a previously defined outer scoped variable. Suppose you have a variable defined in global scope (with var), and you try to define a variable with const with the same name in an enclosed scope. The compiler will throw an Error. The type of the error returned depends on what you’re trying to do.

Listing D.11. Using const
var bar = 'defined';                1
if (true) {
  const foo = 1;                    2
  console.log(foo);                 3
  foo = 2;                          4
  const bar = 'something else';     5
}

  • 1 Initially declares bar
  • 2 Initially declares foo variable
  • 3 Prints out value of 1
  • 4 Tries to redefine foo (Error)
  • 5 Tries to shadow bar variable

Because of the clarity afforded by declaring variables with let and const, this method is now the preferred way. Issues of hoisting are no longer a concern, and variables behave in a more conventional way that programmers familiar with other mainstream languages are more comfortable with.

Functions are variables

You may have noticed throughout the preceding code snippets that the addSurname() function has been declared as a variable. Again, this is a best practice. First, this is how JavaScript sees it anyway, and second, it makes it clear which scope the function is in.

Although you can declare a function in the format

function addSurname() {}

JavaScript interprets it as follows:

const addSurname = function() {}

As a result, it’s a best practice to define functions as variables.

Limiting use of the global scope

We’ve talked a lot about using the global scope, but in reality, you should try to limit your use of global variables. Your aim should be to keep the global scope as clean as possible, which becomes important as applications grow. Chances are that you’ll add various third-party libraries and modules. If all these libraries and modules use the same variable names in the global scope, your application will go into meltdown.

Global variables aren’t the “evil” that some people would have you believe, but you must be careful when using them. When you truly need global variables, a good approach is to create a container object in the global scope and put everything there. Do this with the ongoing name example to see how it looks by creating a nameSetup object in the global scope and use this to hold everything else.

Listing D.12. Using const to define functions globally
const nameSetup = {                                             1
  firstname : 'Simon',
  fullname : '',
  addSurname : function () {
    const surname = 'Holmes';                                   2
    nameSetup.fullname = nameSetup.firstname + ' ' + surname;   3
    console.log(nameSetup.fullname);                            3
  }
};
nameSetup.addSurname();                                         3
console.log(nameSetup.fullname);                                3

  • 1 Declares a global variable as an object
  • 2 Local variables are still okay inside functions.
  • 3 Always access values of an object by using a fully qualified reference.

When you code like this, all your variables are held together as properties of an object, keeping the global space nice and neat. Working like this also minimizes the risk of having conflicting global variables. You can add more properties to this object after declaration, and even add new functions. Adding to the preceding code listing, you could have the code shown next.

Listing D.13. Adding object properties
nameSetup.addInitial = function (initial) {           1
  nameSetup.fullname = nameSetup.fullname.replace(" ", " " + initial + " ");
};
nameSetup.addInitial('D');                            2
console.log(nameSetup.fullname);                      3

  • 1 Defines a new function inside a global object
  • 2 Invokes a function and sends a parameter
  • 3 The output is “Simon D Holmes.”

Working in this way gives you control of your JavaScript and reduces the chances that your code will give you unpleasant surprises. Remember to declare variables in the appropriate scope and at the correct time, and group them into objects wherever possible.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.218.234.83