Javascript is weird, let's get that out of the way. Still, it's not like Javascript is illogical, in fact, most of it's weirdness is a side effect of its power while coping up with programmers' whims. But once you understand why it does what it does, we'll realize that it's not that complex after all. All languages has its own fair share of quirks and nuances that we just have to learn.
This article/guide is my transcription of Anthony Alicea's lecture entitled: Javascript - Understanding the Weird Parts. I expounded and added additional explanation on topics where I felt like more examples and details are necessary. This is not for people new to Javascript, it's more for people who are familiar with Javascript, but still have huge knowledge gaps. After reading this and you still crave for more, I highly recommend reading Eloquent Javascript by Marijn Haverbeke (saying the word "eloquent" makes you feel 10x smarter already, but not as much as the word "quintessential") and Kyle Simpson's You Dont Know JS is also one of the best.
A program that reads code and determines what it does and if it's grammar valid.
Where something sits physically in the code you write. It determines how a piece of code will interact with other elements of the code. The Syntax parser makes decisions based from where a code is placed.
A wrapper to help manage the code that is running. It contains some metadata, some elements that you didn't write. Javascript attaches the this
and outerEnvironment
that your code has access to while it runs.
Everytime a function is run, a new execution context is being created. The global execution context is the root context, the main context where there is no other else that's running.
What's executing isn't what you've written, it's being processed by the javascript engine beforehand.
Is the object every running javascript code has, the object associated with the global execution context. You can reference it by the special/keyword variable this
on the root context or by the variable window
(if you're using node, V8 engine's version is the variable global
).
// At the root context (not inside a function)
window === this
Defining a variable and function will attach it to the current execution context.
The process at which the memory space of variables and functions are setup.
The execution is created in two phases:
The phase where this
and the outerEnvironment
is created while also setting up the variables and functions' memory space placing on the variable environment
, together they are what we refer to as the execution context.
One little caveat for variables: Variables are attached to execution context with a placeholder value called undefined
, the actual value is determined on the execution phase. Functions, on the other hand get attached as is and ready for invocation.
Javascript runs your code line by line. Variables' values are resolved based on the result of the expressions.
var a = 'Hello world';
function b() {
console.log('Called b!');
}
b();
console.log(a);
will output:
Called b!
Hello world
Consider the following code below that in most programming languages will throw an error:
b();
console.log(a);
var = a 'Hello world';
function b() {
console.log('Called b!');
}
will output
Called b!
undefined
Noticed how b()
was still executed properly while a
is also available, albeit a different value.
On the creation phase, the javascript engine checks the code and for every variable declaration (eg: var
) it sets up memory and attaches it on the execution context. The value is then resolved on the execution phase.
b();
console.log(a);
var a = 'Hello world';
console.log(a);
function b() {
console.log('Called b!');
}
Called b!
undefined
Hello world
The Javascript language is single threaded which means, command is being ran one at a time (under the hood, the browser maybe not) and synchronous which means, in order. Code is being ran one at a time and in order.
Everytime a function is invocated, a new execution context is created and is placed on top of the execution stack, whichever is on top is the one currently running, once it finishes, it gets popped off the stack.
Which means, each function call gets its own separate and independent execution context, and its own this
. Note that, the variable environment
is not the same with this
.
function b() {
var myVar;
console.log(myVar);
}
function a() {
var myVar = 2;
console.log(myVar);
b();
}
var myVar = 1;
console.log(myVar);
a();
console.log(myVar);
Will give the following result:
1
2
undefined
1
In every execution context, there's a reference to the outer environment. If a certain variable is not found in the current variable environment
, Javascript will look for it at the outer environment which refers to the variable environment
of the one that created it, this does NOT refer to the previous execution context on the stack but from it's lexical environment, or the execution environment of where it was written.
function b() {
console.log(myVar);
}
function a() {
var myVar = 2;
b();
}
var myVar = 1;
a();
The output is:
1
In this example the outer environment of both a()
and b()
is the same, which is the global or root environment.
function a() {
function b() {
console.log(myVar);
}
var myVar = 2;
b();
}
var myVar = 1;
a();
Now, in this example b
's outer environment is a
's, while a
's outer environment is the global environment. Hence, the output is:
2
Also note that in the global scope, the variable environment
resides in the global (window
) object. We can't directly access or inspect the variable environment
of a function's execution context but we can for the root global context because it's attached to the global object. Moreover, in the global execution context this
=== global (window
) object.
It's a bit confusing but just remember that in the global execution context, we could roughly consider the variable environment
, this
and the outer environment
to be on the same place which is the global window
object.
The block-scoping variable declaration of Javascript ES6. The variable is only available on it's own block.
if (a > b) {
let c = true;
}
console.log(c);
undefined
A type of data that represents a single value. The atomic unit that make up an object.
1. undefined
2. null
3. Boolean - true
or false
4. Number - a floating-point, there's always some decimals. 123
5. String - a sequence of characters. string
6. Symbol - Introduced in es6.
A special function that accepts an argument and returns a value that is just syntactically or written differently. +
, -
, *
, /
, (and etc) are just functions with special characters that are written in an infix notation instead of postfix are prefix like normal functions. ie: +(a, b)
Which operator function gets executed first if there's more than one in the same line of executable code. The higher precedence gets executed first.
var a = 3 + 4 * 5; // (4 * 5) first. Pretty basic
When operators have the same precedence, the order from which they get called is the associativity. Left-to-right or right-to-left associativity.
var a = 2, b = 3, c = 4;
a = b = c;
console.log(a); // 4
console.log(b); // 4
console.log(c); // 4
The =
operator has right-to-left associativity. Here's what actually happened.
// eq(a, eq(b, c))
a = b = c
a = (b = c)
a = (4)
Precedence | Operator Type | Associativity | Individual Operator |
---|---|---|---|
20 | Grouping | n/a | ( … ) |
19 | Member Access | left-to-right | …. … |
Computed Member Access | left-to-right | …[…] |
|
new (with argument list) |
n/a | new … (…) |
|
Function call | left-to-right | … (…) |
|
18 | new (without argument list) |
right-to-left | new … |
17 | Postfix Increment | n/a | …++ |
Postfix Decrement | …-- |
||
16 | Logical NOT | right-to-left | !… |
Bitwise NOT | ~… |
||
Unary Plus | +… |
||
Unary Negation | -… |
||
Prefix Increment | ++… |
||
Prefix Decrement | --… |
||
typeof | typeof … |
||
void | void … |
||
delete | delete … |
||
await | await … |
||
15 | Exponentiation | right-to-left | … ** … |
14 | Multiplication | left-to-right | … * … |
Division | … / … |
||
Remainder | … % … |
||
13 | Addition | left-to-right | … + … |
Subtraction | … - … |
||
12 | Bitwise Left Shift | left-to-right | … << … |
Bitwise Right Shift | … >> … |
||
Bitwise Unsigned Right Shift | … >>> … |
||
11 | Less Than | left-to-right | … < … |
Less Than or Equal | … <= … |
||
Greater Than | left-to-right | … > … |
|
Greater Than or Equal | … >= … |
||
10 | Equality | left-to-right | … == … |
Inequality | … != … |
||
Strict Equality | … === … |
||
Strict Inequality | … !== … |
||
9 | Bitwise AND | left-to-right | … & … |
8 | Bitwise XOR | left-to-right | … ^ … |
7 | Bitwise OR | left-to-right | … | … |
6 | Logical AND | left-to-right | … && … |
5 | Logical OR | left-to-right | … || … |
4 | Conditional | right-to-left | … ? … : … |
3 | Assignment | right-to-left | … = … |
… += … |
|||
… -= … |
|||
… **= … |
|||
… *= … |
|||
… /= … |
|||
… %= … |
|||
… <<= … |
|||
… >>= … |
|||
… >>>= … |
|||
… &= … |
|||
… ^= … |
|||
… |= … |
|||
2 | yield | right-to-left | yield … |
yield* | yield* … |
||
1 | Comma / Sequence | left-to-right | … , … |
Because Javascript is dynamically-typed, it automatically converts or attempts to convert a value from one type to another.
var a = 1 + '2'; // '12'
The value 1
was coerced into a String
by the javascript engine.
Here's a more compelling example:
1 < 2 < 3 // true
3 < 2 < 1 // true, why??
The <
operator has the same precedence with a left-to-right associavity. Which means:
3 < 2 < 1
(3 < 2) < 1
false < 1 // false gets coerced into a number = 0
0 < 1
true
Which means, the same applies to 1 < 2 < 3
, false < 3
, 1 < 3
. Type coercion can cause a lot of problems when you don't know what's going on.
Notice how type coercion can affect equality operators that results to weird behaviors
3 == 3 // true
"3" == 3 // true
false == 0 // true
null < 1 // true because null coerces to 0 < 1
null == 0 // false
"" == 0 // true
"" == false // true
Strict equality ===
, it compares two values but doesn't try to coerce any of them.
3 === 3 // true
"3" === "3" // true
"3" === 3 // false
x | y | == | === | Object.is |
---|---|---|---|---|
undefined | undefined | true | true | true |
null | null | true | true | true |
true | true | true | true | true |
false | false | true | true | true |
'foo' | 'foo' | true | true | true |
0 | 0 | true | true | true |
+0 | -0 | true | true | false |
+0 | 0 | true | true | true |
-0 | 0 | true | true | false |
0 | false | true | false | false |
"" | false | true | false | false |
"" | 0 | true | false | false |
'0' | 0 | true | false | false |
'17' | 17 | true | false | false |
[1, 2] | '1,2' | true | false | false |
new String('foo') | 'foo' | true | false | false |
null | undefined | true | false | false |
null | false | false | false | false |
undefined | false | false | false | false |
{ foo: 'bar' } | { foo: 'bar' } | false | false | false |
new String('foo') | new String('foo') | false | false | false |
0 | null | false | false | false |
0 | NaN | false | false | false |
'foo' | NaN | false | false | false |
NaN | NaN | false | false | true |
Javascript coerces undefined
, null
, ""
to false
. Hence, we could write simple if
conditions like this:
var a;
// goes to internet to check some value
if (a) { // if it exists
console.log('Something is there');
}
One caveat though, 0
coerces to false
as well, hence if in the above code a
was 0
, the behavior is not valid anymore. That's why people often do something like this:
if (a || a === 0) {
console.log('Somethign is there');
}
Just never check existence in this way if you expect the value could be 0
.
Using type coercion and operator precedence, there's a common trick being used to create default values.
function greet(name) {
name = name || '<Your name here>';
console.log('Hello ' + name);
}
greet();
greet('Feynman');
This works because ||
operator doesn't just return true
or false
but it actually returns the value that can be coerced to true
.
undefined || 'hello' // hello
null || 4 // 0
In es6, there's a different way of assigning default value but this trick is still common and could be used not just in function parameters.
function greet(name='Your name here') {
console.log('Hello ' + name);
}
Objects in Javascript is the same as what you expect from other languages. It's a container of primitives and other objects, which can be accessed by .
or []
. The dot .
operator and the computed member access []
have the same precedence and associativity.
var person = new Object();
person['firstname'] = 'Sherlock';
person['lastname'] = 'Holmes';
var firstNameProperty = 'firstname';
console.log(person);
console.log(person[firstNameProperty]);
console.log(person['lastname']);
console.log(person.firstname);
person.address = new Object();
person.address.street = '221B Baker Street';
person.address.city = 'London';
person.address.country = 'United Kingdom';
console.log(person.address.street);
console.log(person['address']['city']);
console.log(person.address['country']);
// via object literal
var sherlock = {
firstname: 'Sherlock',
lastname: 'Holmes',
address: {
street: '221B Baker Street',
city: 'London',
country: 'United Kingdom'
};
};
function greet(person) {
console.log('Hi ' + person.firstname);
}
greet(sherlock);
greet({
firstname: 'John',
lastname: 'Watson'
});
In Javascript, everything is either an object or a primitive. Functions are treated like usual objects as well, they could be assigned to variables, passed around, or created on the fly. Javascript functions are first-class functions.
Think of functions as special type of object that can have properties but functions also has an invocable special property called code
and a string property called name
.
function greet() {
console.log('hi');
}
greet.language = 'english';
greet.hey = function () {
console.log('hey');
}
console.log(greet.language);
console.log(greet.hey());
A Statement is a unit of code that when executed doesn't result in a value.
if (true == true) { // this is an if statement
}
// function statement, it will be executed
// and created a spot in memory for greet
function greet() {
console.log('hi');
}
greet();
An Expression is a unit of code that results in a value. a = 1
is an expression, so is x || 2
.
// This is a function expression
// This will return a function.
function() {
console.log('hi');
}
// That's why we could do this:
var greet = function() {
console.log('hi');
}
// essentially the same with:
var greet = function greetMe() {
console.log('hi');
}
// But since greetMe will just be reassigned to greet, it's optional.
Consider the following code, taking into account what we've learned about hoisting.
greet(); // called and then defined.
function greet() {
console.log('greet');
}
newGreet();
var newGreet = function() {
console.log('hello');
}
newGreet()
will throw an error. newGreet
is a value, and in creation phase, it will be assigned to undefined
when the memory space was set up for it. The value is function but it will be resolved when that line was executed.
Moreover, since functions are also treated as an object we could create one on the fly and pass it as an argument to other functions.
function invoker(a) {
a();
}
function invoker(function(){
console.log('sup?');
});
In Javascript when a primitive value is reassigned or passed to a function as an argument, the copy of the said value is created to be used inside the function - this called pass by value. But when an object is reassigned or passed as an argument, what is being used is the actual reference to the object, not a copy. Which means, changing the value of object within the function will also change the original value - this called pass by reference.
// by value
var a = 2;
var b = a;
b = 3;
console.log(a); // 2
console.log(b); // 3
// by reference
var c = { greeting: 'hi' };
var d = c;
c.greeting = 'hello';
console.log(c.greeting); // 'hello'
console.log(d.greeting); // 'hello'
Note that an assignment =
operator also creates a new memory space, a different memory address for the said value. Hence the following code behaves as follows:
var c = { greeting: 'hi' };
var d = c;
c = { greeting: 'hello' };
console.log(d.greeting); // hi
console.log(c.greeting); // hello
this
keywordWe've learned that when a function is invoked, a new execution context is created and put on top of the stack. The execution context has it's own variable environment
, outer environment
and this
.
this
is an object that points to a different thing depending on where it was called. In other words, this
is dynamically bound.
function a() {
console.log(this);
this.greet = 'hello';
}
var b = function() {
console.log(this);
}
// console.log(greet) // will throw an error, not yet defined
console.log(this);
a(); // points to the global object
b(); // points to the global object
console.log(greet); // 'hello'
var c = {
name: 'The c object',
log: function() {
this.name = 'updated c object';
console.log(this); // no longer points to the global object
}
}
c.log(); // { name: 'updated c object', log: function }
This will make total sense if you have something like this:
var sayMyName = function() {
console.log('Hello there, ' + this.name);
}
var holmes = {
name: 'Sherlock',
greet: sayMyName
}
var watson = {
name: 'John',
greet: sayMyName
}
holmes.greet(); // 'Hello there, Sherlock'
watson.greet(); // 'Hello there, John'
The this.a
in sayMayName
refers to the particular object it's attached to on the time it was called. Always remember, this
always refers to an object. But now consider the following:
var d = {
name: '',
fun: function () {
this.name = 'Holmes'
var setName = function(newName) {
this.name = newName;
}
setName('Watson');
}
}
d.fun();
console.log(d.name); // Holmes
console.log(name); // Watson
What happened? When setName
was called inside d.fun
, the this
operator is now pointing to the global object. The object that this
was referring to was lost and defaulted back to the global object.
To understand this, remember that this
is always bound to the object that called it. d.fun()
, fun
's this
is bound to d
, but right before fun()
finished executing, setName()
was invoked but isn't bound to any object (it was invoked by fun
, but fun
is not an object), hence it got thrown off to the global object.
A common solution for solving this problem is usually done by exploiting the concept of how the execution context behaves when it can't find a certain variable, it goes out to the outer environment that points to its parent lexical context until it finds it.
var e = {
name: '',
fun: function () {
var self = this
self.name = 'Holmes'
var setName = function(newName) {
self.name = newName;
}
setName('Watson');
}
}
e.fun();
console.log(e.name); // Watson
// console.log(name); // Error
When setName
executes, self
is not present in it's variable environment
, so it goes to look on the outer environment
which happens to point to fun
's execution context, where it finds self
. setName
is written inside fun
that's why the outer environment
points to fun
and not because it's right below setName
's execution context on the execution stack.
There are other ways to get around and explicitly bind this
to certain object by using Javascript's built-in methods, call()
, apply()
and bind()
.
call()
, apply()
and bind()
Whenever a Javascript function is created, each function gets call()
, apply()
and bind()
attached to them by the Javascript engine as properties that we have access to. The this
keyword is bound to the object where it was invoked, if there's no such object, the binding is lost and this
binds to the default binding, which is the global
object (undefined
when on strict mode). But using said functions we could explicitly bind this
to whatever object we like.
call()
Invokes the target function binding the reference to this
to the given first parameter.
var person = {
name: 'Sherlock Holmes',
log: function() {
console.log(this.name);
}
}
var logFn = person.log;
var person2 = {name: 'John Watson'};
logFn.call(person2);
John Watson
apply()
With respect to this
binding, call(..)
and apply(..)
are identical. They do behave differently when used with their additional parameters.
var sherlock = {
firstName: 'Sherlock',
lastName: 'Holmes',
getFullName: function() {
return this.firstName + ' ' + this.lastName;
}
}
var watson = {
firstName: 'John',
lastName: 'Watson',
getFullName: function() {
return this.firstName + ' ' + this.lastName;
}
}
var wrappedLog = function (a, b) {
console.log(this.getFullName())
console.log(a, b);
console.log('------');
}
console.log(sherlock.getFullName());
console.log('------');
wrappedLog.call(watson, 'arg1', 'arg2');
// wrappedLog.apply(watson, 'arg1', 'arg2'); // will throw an error
wrappedLog.apply(watson, ['arg1', 'arg2']);
Sherlock Holmes
------
John Watson
arg1 arg2
------
John Watson
arg1 arg2
------
apply()
just unrolls its array arguments and provide it as the targetFn
's arguments. It doesn't seem useful at first glance but it's very convenient if you are doing some array manipulations and you want the result to be used as arguments for a generic target function.
function someFrameworkTask = function(targetFn, targetOb, arrArgs) {
// Do some work on arrArgs
arrArgs.push(...);
arrArgs.splice(...);
// targetFn.call(targetOb, arr[0], arr[1], ...arr[2]); // we have to know how many arguments targetFn has beforehand
targetFn.apply(targetOb, arr);
}
Explicit binding is cool but what if we wanted a certain function to be permanently bound to a specific this
without worrying that it could be modified someplace else.
var sherlock = {
firstName: 'Sherlock',
lastName: 'Holmes',
getFullName: function() {
return this.firstName + ' ' + this.lastName;
}
}
var watson = {
firstName: 'John',
lastName: 'Watson',
getFullName: function() {
return this.firstName + ' ' + this.lastName;
}
}
var logName = function() {
console.log(this.getFullName());
}
var hardbound = function() {
logName.call(sherlock);
}
hardbound();
hardbound.call(watson);
hardbound.apply(watson);
Sherlock Holmes
Sherlock Holmes
Sherlock Holmes
We created a hardbound
variable whose value is just a function that wraps a call
invocation, explicitly binding it to sherlock
. When hardbound()
is invoked, it's the same as calling call(sherlock)
, but this wrapping mechanism that we have implemented has made the binding permanent. Invoking hardbound.call(watson)
would only bind this
to the anonymous wrapper function that will just be ignored when logName.call(sherlock)
is invoked.
This hard binding is so common that it was also provided by the Javascript engine called bind()
. Of course its implementation is far more robust and less error prone than ours.
bind()
bind()
on the other hand, takes an object as a parameter and then returns a new function where this
is hard bound to the said object.
var sherlock = {
name: 'Sherlock Holmes',
log: function() {
console.log(this.name);
}
}
var watson = { name: 'John Watson'};
var logName = sherlock.log.bind(watson);
logName();
logName.bind(sherlock)();
John Watson
John Watson
Note that it's still possible to change this binding if we used the keyword new
that we'll explore in a different chapter.
Passing parameters to bind()
does something interesting, it gave us the ability to curry. Currying is creating a copy of a function with some preset parameter values or partial application of parameters.
function power(base, exponent) {
return base ** exponent;
}
var square = power.bind(this, 2);
square(2); // 4
square(5); // 32
In this example, we're not after the this
binding so we could just bind it to the global object, we're not using it anyway. We're more interested on how we can use currying if we pass more arguments to bind
. Passing 2
on power.bind(this, 2)
would make square
a power()
function that have it's base
parameter permanently set to the value 2
. We say that square
is a power()
function whose arguments are partially applied.
Supplying more parameters to bind would just fill the arguments of the target function.
var four = power.bind(this, 2, 2);
four(5); // 4. 5 will be passed as the 3rd parameter that we're just ignoring.
Currying, is widely used in Functional style of programming. We could also create the same behavior by exploiting closures that will be discussed in the next sections.
Javascript has no function overloading capabilities because functions are treated as objects, unlike in other languages. Javascript doesn't care about the number of parameters, it just care about the name of the function you're trying to invoke. If your function expect 2 parameters but you only pass 1 argument, the second parameter will be undefined
. Likewise, if you expect 2 parameters and you pass 3 arguments, you'll just have no straightforward way to access the third parameter (unless you access the built-in arguments
property a functions have). It's still possible to convey a somewhat cleaner overloading concept by utilizing default parameters:
function greet(firstName, greeting = "Hi", question="How are you?") {
return `${greeting} ${firstName}! ${question}`;
}
greet('John'); // "Hi John! How are you?
greet('John', 'Hey'); // "Hey John! How are you?"
greet('John', 'Hey', 'Sup?'); // "Hey John! Sup?"
An anonymous function expression that was being immediately invoked upon load.
function greet(name) { // Function statement
console.log('Hello ' + name);
};
var greetFun = function(name) { // Function expression
console.log('Hello ' + name);
};
var greeting = function(name) { // Immediately-invoked function expression
return 'Hello ' + name;
}('John');
console.log(greeting); // 'Hello John' - Holds the result of the IIFE
// function(name) { // Will throw an error
// console.log('Hello ' + name); // Syntax parser expects
// } // a function name
(function(name){
console.log('Hi ' + name);
}('John'));
IIFE's are widely used among javascript frameworks and libraries for initialization while managing namespace collisions.
// defined in someplace else ie: foo.js
var greeting = 'Hola'; // Bound to global
// somewhere in another file ie: greet.js
(function() {
var greeting = 'Hello';
console.log(greeting);
}());
console.log(greeting);
Hello
Hola
new
Functions that are used to construct objects together with new
operator. These functions are normal functions but when new
is applied to them, Javascript engine does something different, it creates an empty this
object and returns it.
function Foo() {
this.hello = 'hey';
}
var f = new Foo();
console.log(f);
f {
hello: 'hey'
}
But still, don't forget that these are still normal functions. When invoked without new
, this
would refer to the global object like what we've learned. That's why it's a convention that any function intended to use as function constructors should always start with capital letters.
function Foo(){
console.log(this);
}
Foo();
new Foo();
var f = Foo();
console.log(f); // undefine because Foo() was invoked as a function
Window {...}
Foo {}
undefined
That being said, when creating objects using new
don't return anything on the function constructor or it will mess up the javascript engine's decision making.
function Foo(greeting) {
this.greet = greeting;
return {
b: 'azinga'
}
}
var f = new Foo('hello');
console.log(f);
{b: 'azinga'}
Some of the most common objects has built-in function constructors shipped together with Javascript. Almost all primitive values has a wrapper object counterparts that includes some added properties and methods.
var num = new Number(3.14151629);
num.toFixed(2);
var tesla = new String("Nikola Tesla");
tesla.toLowerCase();
Just a word of caution, be careful in using these wrapper objects because it can cause bugs specially when it comes to comparisons where coercion is taking place.
var meaningOfLife = 42;
var is42 = new Number(42);
console.log(meaningOfLife == is42); // true
console.log(meaningOfLife === is42); // false
this
We've seen how bind
, call
and apply
work but what happens when new
is applied into the mix?
var og = {
greet: 'hello'
}
function foo() {
console.log(this.greet);
}
var hi = {
greet: 'hi'
}
var hardbound = foo.bind(hi);
hardbound();
hardbound.call(og);
new foo();
new hardbound();
hi
hi
undefined
undefined
In this example, we see that new
overrides what this
refers to explicitly bound by bind
. It always creates a new empty object, that's why undefined
was always the result.
In summary, the order of precedence to determine the this
binding would be the following, the first rule that applies would be the this
binding:
new
binding - the newly created object.call
, apply
or bind
) - whatever the explicit object wasundefined
on strict-mode
)The ability to treat functions as values, brings up an interesting question. What happens to the functions' local variables when the said function has finished executing and was already popped off the execution stack?
function greet(greeting) {
return function(name) {
console.log(greeting + ' ' + name);
}
}
var sayHi = greet('Hi');
var sayHello = greet('Hello');
sayHi('Holmes');
sayHello('Watson');
greet('Sup')('Morty');
Hi Holmes
Hello Watson
Sup Morty
Remember that each function can access its outer environment whenever it wasn't able to look for a certain variable in its own environment? Not only that, functions in Javascript also maintains a reference to its outer environment even though said environment has already finished executing.
In the example above, sayHi
is a reference to a function where 'Hi'
was passed as an argument to the parameter, said function returns yet another function where said parameter is being used in addition to the latter functions' own parameter.
When sayHi('Holmes')
was invoked, it will execute console.log(greeting + ' ' + name);
, it knows what the variable name
is but greeting
is not present in its own execution environment. It then goes out to its outer environment which is the place where it was written and looks for greeting
variable. Javascript knows that said function was created inside greet()
, eventhough it has already been popped off the stack, it's variable environment still persists which will be picked up by the sayHi
invocation.
This feature — being able to still reference the variable environment of an enclosing scope, albeit said execution environment has already been executed — is called closure. A function that reference variables from it's outer environment is called a closure.
Closures can result to interesting behaviors such as:
function buildFunctions() {
const arr = [];
for (var i = 0; i < 3; i++) {
arr.push(function() {
console.log(i);
});
}
return arr;
}
const fs = buildFunctions();
fs[0]();
fs[1]();
fs[2]();
The result is partly alarming at first sight. We might think that the output would be something like:
// Did you expect?
0
1
2
But instead, when we run this code we would get:
// Actual result:
3
3
3
Why? The value that's being pushed in arr
is a function that accesses the variable i
. We know that functions doesn't get evaluated until invocation. Only when fs[0]()
is run that it looks for i
and has to go to its outer environment to resolve the value. But buildFunctions()
has already been executed when we assigned the resulting array to fs
. buildFunctions
's variable environment still persists but var i
's value has already been incremented to 3
when it finished running. Hence, when we try to reference it on our fs[n]()
calls, what we get is the current state of the enclosing variable environment of buildFunctions
.
We could around this by using es6's let
variable declaration to let Javascript know that we're interested only on the block level scope value of i
:
function buildFunctions() {
const arr = [];
for (let i = 0; i < 3; i++) {
arr.push(function() {
console.log(i);
});
}
return arr;
}
Or by adding what we know from IIFEs and exploiting it, we could come up with something like:
function buildFunctions() {
const arr = [];
for (var i = 0; i < 3; i++) {
arr.push(
(function(j){
return function() {
console.log(j);
}
}(i));
);
}
return arr;
}
var fs = buildFunctions();
This code is confusing and would be frowned upon when used in the real world but it does perfectly demonstrates what we've learned about Javascript's Closures, IIFEs and scope chains. arr
will contain a value that is, still a function but that function is a result of an immediately-invoked function that encapsulates i
as j
.
Now, when we invoked fs[0]();
, console.log(j)
will be executed, it will look for the variable j
but since it wasn't in its own variable environment, it will then find it as the parameter of the enclosing IIFE which was in turn a copy of the value of the parameter i
of the enclosing function. Remember that objects are pass by reference and primitives are pass by values? The value of j
is whatever the value of i
at the time of its execution, subsequent changes to i
will not affect j
.
Going back to the original example, if it isn't a function and we immediately used the value of i
like arr.push(i)
, it would be a different scenario. But since it's a function along with the behavior of closures we are somehow able to retain the state of buildFunctions
. This behavior is further demonstrated by the code below:
function saveState() {
const arr = [];
return function(n) {
arr.push(n);
return arr;
};
}
const myState = saveState();
console.log(myState(1));
console.log(myState('Yo'));
const test = myState('test');
console.log(myState(100));
// ... some other code here
console.log(test);
console.log(myState('7'));
[1]
[1, "Yo"]
[1, "Yo", "test", 100]
[1, "Yo", "test", 100]
[1, "Yo", "test", 100, "7"]
Using closures we could come up with interesting patterns like the commonly used function factory below:
function makeGreeting(language) {
return function(firstName, lastName) {
if (language === 'en') {
console.log(`Hello ${firstName} ${lastName}`);
}
if (language === 'es') {
console.log(`Hola ${firstName} ${lastName}`);
}
}
}
var greetEnglish = makeGreeting('en');
var greetSpanish = makeGreeting('es');
greetEnglish('John', 'Doe'); // Hello John Doe
greetSpanish('John', 'Doe'); // Hola John Doe
As you might expect, due to the nature of closure we could also create currying functions without relying on bind
var power = function(base) {
return function(exponent) {
return base ** exponent;
}
}
var square = power(2);
square(5); // 32
Or, using ES6's arrow functions we could create an arguable concise version albeit slightly confusing if we're not aware of what's actually happenning.
let power = (base) => (exponent) => base ** exponent;
let square = power(2);
let cube = power(3);
square(5); // 32
cube(4); // 81
Using our knowledge in Closures, Functions and how Javascript treats it as first-class functions, we could now peform a certain style of programming that involves function manipulations like the following:
var mapForEach = function(arr, work) {
const newArr = [];
for (var i=0; i < arr.length; i++) {
newArr.push(work(arr[i]));
};
return newArr;
}
var list = [1, 2, 3];
var doubled = mapForEach(list, function(eachElem) {
return eachElem * 2;
});
console.log(doubled); // [2, 4, 6]
We created a function mapForEach
that accepts an array and applies some work()
on each element and then returns the new array, this sort of thing enables us to transform an array that is very generic — something that we could use in different scenarios.
var isUnderAge = function(age) {
return age < 18;
}
var areAllUnderage = mapForEach(list, isUnderAge);
console.log(areAllUnderAge);
Using closures and currying we could make the code above something more interesting.
// Currying using bind
var checkPastLimitViaBind = function(limit) {
return function(limiter, element) {
return element > limter;
}.bind(this, limit);
}
// Or we could use just plain closures
var checkPastLimit = function(limit) {
return function(element) {
return element > limit;
}
}
var isAdult = checkPastLimit(18); // equal to checkPastLimitViaBind(18)
console.log(list, isAdult);
Building up on the above examples, we could demonstrate the power of using small, independent and granular functions to do more interesting things:
function filterArray(arr, predicate) {
var newArr = [];
for(var i = 0; i < arr.length; i++) {
if(predicate(arr[i])) {
newArr.push(arr[i]);
}
}
}
var population = [15, 20, 23, 25, 16, 14, 10, 18];
var adults = filterArray(population, isAdult);
console.log(adults);
var me = {
name: 'John Smith',
age: 18
}
console.log(isAdult(me.age));
[20, 23, 25, 18]
true
Note that in ES6, map
, filter
and other functional utilities are available in arrays by default. There's also a good Javascript library called lodash
that provides us with a lot of functional helpers.
The Javascript engine always attaches a prototype whenever an object gets created. A prototype is used as the fallback source of properties. When an object gets a request for a property that it does not have, its prototype will be searched for the said property. When we create a string
for example:
var myString = "The quick brown fox jumps over the lazy dog";
After being processed by the Javascript engine, myString
will have several properties and methods available for us that we didn't code, these methods are coming from the String prototype. We could take a look at the prototype by accessing the __proto__
property. Or by invoking Object.getPrototypeOf()
.
myString.__proto__;
Object.getPrototypeOf(myString);
Think of the prototype as the original blueprint that describes a certain object. Note that two object can each have a prototype that refers to the same protoype object.
Object.getPrototypeOf("new String") === Object.getPrototypeOf(myString)
The prototype is also an object and thus it also have a prototype. This is how the concept of inheritance is being achieved by Javascript. Instead of the conventional classes, Javascript objects are composition of various prototypes that will ultimately define the said object's behavior. When a property is not found on the object, it will search its prototype and if not found, it will look on the prototype's prototype and so on, further down the prototype chain until it eventually finds the said property.
To demonstrate this, let's manually modify an object's prototype. Note that this is for demonstration purposes only and should never be done in actual development or you might mess up the background decision making of the Javascript engine as it runs your code.
var human = {
firstName: 'Default',
lastName: 'Default'
getFullName: function() {
return this.firstName + ' ' + this.lastName;
}
}
var watson = {
firstName: 'John',
lastName: 'Watson'
}
// Demo purposes only, let's reassign the prototype object
watson.__proto__ = human
console.log(watson.getFullName());
console.log(watson.firstName);
var nameless = {}
nameless.__proto__ = human
console.log(nameless.getFullName());
John Watson
John
Default Default
The first console.log
references getFullName
which the object watson
doesn't have, the Javascript engine then goes in the prototype object which we have set to human
and finds it there thus printing John Watson
.
The second console.log
on the other hand references firstName
which watson
already have that's why the Javascript engine doesn't search further down the prototype chain.
If we go further down to the root of the prototype chain of any object we'll discover that everything originates from the Object
prototype.
var str = "";
var arr = [];
var fun = function() {};
var ob = {};
console.log(str.__proto__.__proto__ === arr.__proto__.__proto__ === fun.__proto__.__proto__ === ob.__proto__);
console.log(ob.__proto__.__proto__);
true
null
A javascript object can look at itself, extend its by behavior and functionality by adding and/or modifying its own property and methods.
var base = {
a: 'a',
b: 'b',
c: 'c'
};
var z = {
a: 'x',
b: 'y'
}
z.__proto__ = base;
for (var prop in z) {
if (z.hasOwnProperty(prop)) {
console.log(z[prop]);
}
}
word of caution, don't use
for
..in
when iterating array values, because you could also iterate through the properties of the Array object itself as well, not just the elements.x y
The example below is a contrived version of the inner workings of the lodash's
extend
function.createArray()
andlib
is actually not necessary but important to demonstrate how javascript libraries exploit functional programming to implement interesting stuff like this.createAssigner
takes some sort of utility function, in this case a function that determines all existing props in an object, then returns another function that takes anobj
parameter where thegetKeysFunc
will be used against.lib
is now an object where all utility functions likeextend
exists by composing and chaining utility functions likecreateAssigner
. Also notice that the resulting return function ofcreateAssigner
only takes one parameter but when we uselib.extend
we passed 3 arguments. If this were any other language this wouldn't be possible but since Javascript doesn't care about parameter length, and all it cares about is the function name, it's possible to not define all parameters. After all, we can still access them viaarguments
.function createAssigner(getKeysFunc) return function(obj) { var length = arguments.length; if (length < 2 || obj == null) { return obj; }
for (var i = 1; i < length; i++) {
var source = arguments[i];
var keys = getKeysFunc(source);
for (var j = 0; j < keys.length; j++) {
var key = keys[j];
obj[key] = source[key];
}
}
} }
var lib = { extend: createAssigner(Object.keys); }
var ob1 = { a: 'a', b: 'b' c: 'c' }
var ob2 = { m: 'm', n: 'n', o: 'o' }
var ob3 = { x: 'x', y: 'y', z: 'z' }
lib.extend(ob1, ob2, obj3); for (var key in ob1) { console.log(obj1[key]); }
a b c m n o x y z
`ob1` extended its behavior by adding all properties and methods coming from `ob2` and `ob3`. And we did that dynamically at run-time.
#### Function Constructor's `.prototype`
In every javascript function there's a `.prototype` property **NOT** to be confused with `.__proto__` property. The latter is the fallback source of any object, or the source template, like what we've discussed before. On the other hand, `.prototype` is being set and used when a function is used as a function constructor upon using the `new` operator.
```javascript
function Person(name) {
this.name = name;
}
function foo() {}
var holmes = new Person('holmes');
var watson = new Person('watson');
console.log(Person.__proto__ === foo.__proto__);
console.log(holmes.__proto__ === Person.prototype === watson.__proto__);
true
true
Does it make sense? Person
itself is a function that's why its and foo
's __proto__
refer to the same thing. But then holmes
and watson
's __proto__
points to Person.prototype
because they were created with new
. Hence, .prototype
of a function a is a special property that points to the object that will be the __proto__
of all the objects that will be created out of it via new
.
.prototype
Using .prototype
we can now extend all the objects behavior by adding or modifying it.
function Person(firstname, lastname) {
this.firstname = firstname;
this.lastname = lastname;
}
Person.prototype.getFullName = function() {
return this.firstName + ' ' + this.lastName;
}
var sherlock = new Person('Sherlock', 'Holmes');
var watson = new Person('John', 'Watson');
console.log(sherlock.getFullName());
console.log(watson.getFullName());
Sherlock Holmes
John Watson
Object.create
Another way of creating objects without relying on new
operator. Using Object.create
is considered as the pure prototypal inheritance because it really just assigns the target object as the prototype, nothing more and nothing less. Note that Object.create
may not be present in older javascript versions.
var person = {
firstname: 'Default',
lastname: 'Default',
greet: function() {
return 'Hi ' + this.firstname;
}
}
var holmes = Object.create(person);
holmes.firstname = 'Sherlock';
holmes.lastname = 'Holmes';
ES6 added a better syntax to create objects without using function constructors.
class Person {
constructor(firstname, lastname) {
this.firstname = firstname;
this.lastname = lastname;
}
greet() {
return 'Hello ' + this.firstname + ' ' + this.lastname;
}
}
You then use the keyword extends
to indicate the prototype
class InformalPerson extends Person {
constructor(firstname, lastname) {
super(firstname, lastname);
}
greet() {
return 'Yo ' + this.firstname;
}
}
Tread carefully though, while it looks like Java classes and other conventional OOP classes, it's very important to remember that these are still just function constructors under the hood and they create objects. Don't try to strictly wrap your head around conventional OOP relationships when using ES6 classes.
typeof
and instanceof
Due to the dynamic nature of Javascript we will sometimes find ourselves trying to figure out what the type of the object truly is for some reason or another.
var num = 4;
var str = "Hello";
var ob = {};
var arr = [];
var fun = function() {};
console.log(typeof num); // number
console.log(typeof str); // string
console.log(typeof ob); // object
console.log(typeof arr); // object
console.log(typeof fun); // function
console.log(typeof undefined); // undefined
console.log(typeof null); // object
console.log(holmes instanceof Person); // true
We can see in the code above how typeof
and instanceof
works, and immediately you should pay attention to arr
and null
. null
apparently is also an object
, and same as arrays. It does make sense since arrays aren't really primitives but a type of an object. In order to determine if an object is an array, we could do something like this:
// a little weird, more of a workaround
console.log(Object.prototype.toString.call(arr) === '[object Array]'); // true
// Or, in newer versions of Javascript, we could use:
console.log(arr instanceof Array); // true
console.log(Array.isArray(arr)); // true
We've learned that Javascript is a bit liberal or flexible. It's useful but sometimes we want it to be a bit stricter if we use strict mode.
var person;
persom = {}; // suppose we accidentally typed a typo
console.log(persom); // Object {}, person is undefined.
If we then do this:
"use strict"
var person;
persom = {}; // Will throw an error here.
Strict mode could also be applied locally on a function.
function funfun() {
"use strict"
x = {};
}
var y;
z = {};
console.log(z);
funfun();
Object {}
Uncaught ReferenceError: x is not defined
More details about strict mode could be read over at MDN docs.