Daniel Salvagni — Yet another software engineer
Things to be aware when you are starting in JavaScript
JavaScript is the world’s most misunderstood programming language, said Douglas Crockford. I tried to compile some topics which I think is too confusing to work with if you are just starting in JavaScript. It is almost about the scope.
Table of contents
- Scope
- What is Scope?
- Global Scope
- Local Scope
- Function Scope
- Lexical Scope
- The
this
keyword - Closure
- Hoisting
1. Scope
Different from other languages like C
, C++
or C#
, JavaScript doesn’t has block-level scope, but it has function-level scope. Which means that only function can create a new scope. They aren’t created by for
or while
loops or expression statements like if
ir switch
.
1.1. What is Scope?
Be aware that the scope refers to the context of your code so it could be globally
or locally
defined. Understanding it is key to be a better JavaScript developer by knowing where variables and function are accessible
and be able to change the scope of your code’s context when it is neccessary.
1.2. Global Scope
By default, when write a line of JavaScript you’re in what is called Global Scope
or Global Object
. So, if we are declaring a variable in the default scope, we are doing it on global scope.
// app.js
var fullName = "David Gilmour";
Control the global scope is easy and in doing so, you’ll run into no issues with global scope. You’ll hear people saying “Global Scope is bad”, but it isn’t. You need it to creade Modules and APIs that are accessible across scopes and take it as your advantage.
Think about it as a global object where each global variable is present as a property of this object. In browsers, the global scope object is stored in the window
variable.
1.3. Local Scope
Everything defined past the global scope refers to local scope. You’ll have one global scope and for each function defined it has its own local scope. Like I said before, only functions can create new scopes. So, if I have a function with variables inside it, those variables are in the local scope (the function scope). Look at the example below.
// Scope A: Global scope
var myFunction = function() {
// Scope B: Local scope
var fullName = "David Gilmour";
console.log(fullName); // David Gilmour
}
console.log(fullName); // Uncaught ReferenceError: fullname is not defined
We can’t access the fullName
variable because it is locally scoped and it isn’t exposed to the parent scope.
1.4. Function Scope
Just remember: Functions creates scopes.
// Scope A
var myFunction = function() {
// Scope B
var someOtherFunction = function() {
// Scope C
}
}
1.5. Lexical Scope
Also called as Closure or Static Scope, Lexical Scope is the possibility of an inner scope access a variable or function in parent scope.
// Scope A
var myFunction = function() {
// Scope B
var fullName = "David Gilmour"; // It is defined in Scope B
var someOtherFunction = function() {
// Scope C
console.log("I'm listening " + fullName + " albuns.");
}
console.log(fullName); // "David Gilmour"
someOtherFunction(); // Will log out: "I'm listening David Gilmour albuns."
}
Lexical scope is easy to understand and work with. Just think that any variable, object and functions defined in it’s parent scope are available in the scope chain. The important thing to remember is that Lexical scope doesn’t work backwards.
// Scope A
// fullName = undefined
var myFunction = function() {
// Scope B
// fullName = undefined
var someOtherFunction = function() {
// Scope C
var fullName = "David Gilmour"; // Local scope
}
}
##2. The this
keyword
this
keyword could be hard to use if you don’t exacly know how it works.
The this
keyword always refers to owner of the function we’re executing, or rather, to the object that a function is a method of.
Here is a few very important points to remember about the this
keyword:
- The
this
keyword value isn’t related to the function itself but how the function is called. So it can be dynamic. - Yoo can change the
this
context through .call(), .apply() and .bind()
Default context
We can change the this
vaue in a few different ways and it’s usually the caller of a function that can change his context.
Window Object, Global Scope
From a regular function declaration we can expect that the this
value would be window Object
, which refers to the global scope. The ownership of this
will be window
.
var someFunction = function() {
console.log(this); // [object Window]
};
Object literals
In the case of Object literals, the ownership will be the it’s own Object.
var someObject = {};
someObject.someMethod = function() {
console.log(this); // someObject
};
So, why isn’t window
the ownership in this case? Because the window Object
didn’t call the function but someObject
did. It is the same for constructors
.
Prototypes and Constructors
var musician = function() {
var _firstName;
var _lastName;
this.setFirstName = function(firstName) {
this._firstName = firstName;
};
this.setLastName = function(lastName) {
this._lastName = lastName;
};
};
musician.prototype.getFullName = function() {
return this._firstName + ' ' + this._lastName;
};
var Gilmour = new musician();
Gilmour.setFirstName('David');
Gilmour.setLastName('Gilmour');
Gilmour.getFullName(); // David Gilmour
In both cases above, the this
ownership is the Constructor object, which is musician
;
Events
Same rules are applied when we bind events. The this
refers to his owner.
// .musician = <div class="musician">David Gilmour</div>
var musician = document.querySelector('.musician');
var someFunction = function() {
console.log(this); // <div class="musician">David Gilmour</div>
};
element.addEventListener('click', someFunction, false);
To see how this
is dynamic you can just invoke the function and the return will be different from the event call.
// .musician = <div class="musician">David Gilmour</div>
var musician = document.querySelector('.musician');
var someFunction = function() {
console.log(this);
};
element.addEventListener('click', someFunction, false); // <div class="musician">David Gilmour</div>
someFunction(); // [object Window]
Changing this
context
There are many reasons why we need to change the context of a function and we have a few methods that use can use to do it: .call()
,.apply()
and .bind()
.
You will use the methods above when you want this
to refer to something different thant the scope it’s in.
With .call()
and .apply()
we can pass a new scope and arguments. They immediately invoke the function.
var Musician = function()
{
this.getFullName = function() {
console.log(this.firstName + ' ' + this.lastName);
};
}
var GuitarMan = new Musician();
GuitarMan.getFullName(); //undefined undefined
var Gilmour = {
firstName: "David",
lastName: "Gilmour"
};
GuitarMan.getFullName.call(Gilmour); //David Gilmour
GuitarMan.getFullName.apply(Gilmour); //David Gilmour
Different from .call()
and .apply()
the .bind()
method doesn’t invoke the function when it is called. It is used to setup
the context for a function.
var Gilmour = {
firstName: "David",
lastName: "Gilmour",
instrument: "Guitar"
};
var Musician = function()
{
console.log(this);
}.bind(Gilmour);
Musician(); //Gilmour object
It can also be used in event handlers to add some extra information:
// .musician = <div class="musician">David Gilmour</div>
var Gilmour = {
firstName: "David",
lastName: "Gilmour",
instrument: "Guitar"
};
var musician = document.querySelector('.musician');
var someFunction = function() {
console.log(this);
};
element.addEventListener('click', someFunction.bind(Gilmour), false); // Gilmour
3. Closures
Private and public members in JavaScript is possible using closures. Closures treat functions as values, and as functions have his own scope, local variables are “re-created” every time a function is called. It emulates public and private scopes.
JavaScript have some design patterns, such as the Module
pattern which can create a public
and private
scope. As we know that functions create scope, we can create a private scope by wrapping a function inside other function:
(function(){
// here is a private scope
})();
We can add some functions for use:
(function(){
var myFunction = function() {
// your code here
};
})();
Remember that the function is defined inside a private scope, so it isn’t possible to access it from the global scope:
(function(){
var myFunction = function() {
// your code here
};
})();
myFunction(); // Uncaught ReferenceError: myFunction is not defined
So, that’s it! If we need the function to be public, we can now use the Module Pattern
which allow us to scope our functions using private and public scopes and an Object
. Basically, to turn a function public, we need to return a function or an Object
.
Module Pattern
var Module = (function(){
return {
myPublicMethod: function() {
console.log('myPublicMethod called');
}
};
})();
Module.myPublicMethod();
So, follow this idea, everything that isn’t be returned is private.
var Module = (function(){
var _myPrivateMethod = function() {
console.log('myPrivateMethod called');
};
return {
myPublicMethod: function() {
console.log('myPublicMethod called');
}
};
})();
Module.myPublicMethod();
You myght notice that JavaScript have a naming convetion to begin private
methods with an underscore, which visually helps you differentiate between public and private methods. This allow us to return an anonymous Object
simply assigning the function references.
var Module = (function(){
var _myPrivateMethod = function() {
console.log('myPrivateMethod called');
};
var publicMethod = function() {
console.log('myPublicMethod called');
};
return {
myPublicMethod: publicMethod
};
})();
Module.myPublicMethod();
4. Hoisting
Functions and variable declarations are always moved invisibly to the top of theirs containing scope by JavaScript interpreter. This JavaScript behaviour is called “hoisting”.
With variables, only the declaration name is hoisted. With function declaration
, the entire function body will be hoisted. It doesn’t is applied for function expressions
assigned to local variable. Follow the examples below:
console.log(fullName); // undefined
var fullName = "David Gilmour";
console.log(fullName); // David Gilmour
This will be interpreted like this:
var fullName;
fullName = "David Gilmour";
As this is applied to the scope, lets take a look in this function scope.
function getFoo() {
if(true)
{
var a = 1;
}
var b = 2;
return;
}
Will be interpreted like this:
function getFoo() {
var a, b;
if(true)
{
a = 1;
}
b = 2;
return;
}
Notice that only the name is hoisted, not the variables value. It isn’t he case with function declarations
, where the entire function body will be hoisted as well.
function fooBar() {
foo(); // TypeError "foo is not a function"
bar(); // "bar() function"
/*
this is a `function expression` which isn't hoisted like `function declaration`.
*/
var foo = function () {
console.log("foo() function");
}
/*
function declaration
*/
function bar() {
console.log("bar() function");
}
}
fooBar();
It is a good practice always declare your variables with just one var
statement per scope and that it be at the top. Forcing yourself to do this, will be hard to get into some hoisting-related confusion.
I hope it’s useful.