Javascript
Javascript
JAVASCRIPT
ADVANCE CONCEPTS
CHEATSHEET
CHEATSHEET
JAVASCRIPT ENGINE
A JavaScript engine is a computer program that you give JavaScript code to and it tells
the computer how to execute it. Basically a translator for the computer between
JavaScript and a language that the computer understands. But what happens inside of
the engine? Well, that depends on the engine. There are many JavaScript Engines out
there and typically they are created by web browser vendors. All engines are
standardized by ECMA Script or ES.
Ni#y Snippet: 2008 was a pivotal moment for JavaScript when Google
created the Chrome V8 Engine. The V8 engine is an open source high-
performance JavaScript engine, wriBen in C++ and used in the Chrome
browser and powers Node JS. The performance outmatched any engine that
came before it mainly because it combines 2 parts of the engine, the
interpreter and the compiler. Today, all major engines use this same
technique.
THE AST
The parser produces a data structure called the Abstract Syntax Tree or AST. AST is a
tree graph of the source code that does not show every detail of the original syntax, but
contains structural or content-related details. Certain things are implicit in the tree and
do not need to be shown, hence the Btle abstract.
THE INTERPRETER
An interpreter directly executes each line of code line by line, without requiring them to
be compiled into a machine language program. Interpreters can use different strategies
to increase performance. They can parse the source code and execute it immediately,
translate it into more efficient machine code, execute precompiled code made by a
compiler, or some combinaBon of these. In the V8 engine, the interpreter outputs
bytecode.
Ni#y Snippet: The first JavaScript engine was wriBen by Brendan Eich, the
creator of JavaScript, in 1995 for the Netscape navigator web browser.
Originally, the JavaScript engine only consisted of an interpreter. This later
evolved into the SpiderMonkey engine, sQll used by the Firefox browser.
THE COMPILER
The compiler works ahead of Bme to convert instrucBons into a machine-code or lower-
level form so that they can be read and executed by a computer. It runs all of the code
and tries to figure out what the code does and then compiles it down into another
language that is easier for the computer to read. Have you heard of Babel or TypeScript?
They are heavily used in the Javascript ecosystem and you should now have a good idea
THE COMBO
In modern engines, the interpreter starts reading the code line by line while
the profiler watches for frequently used code and flags then passes is to the compiler to
be opBmized. In the end, the JavaScript engine takes the bytecode the interpreter
outputs and mixes in the opBmized code the compiler outputs and then gives that to the
computer. This is called "Just in Time" or JIT Compiler.
Ni#y Snippet: Back in 1995 we had no standard between the browsers for
compiling JavaScript. Compiling code on the browser or even ahead of Qme
was not feasible because all the browsers were compeQng against each
other and could not agree on an executable format. Even now, different
browsers have different approaches on doing things. Enter WebAssembly a
standard for binary instrucQon (executable) format. Keep your eye on
WebAssembly to help standardize browsers abiliQes to execute JavaScript in
the future! WebAssemby
MemoizaAon
MemoizaBon is a way to cache a return value of a funcBon based on its parameters. This
makes the funcBon that takes a long Bme run much faster aper one execuBon. If the
parameter changes, it will sBll have to reevaluate the funcBon.
addTo80(5
)
addTo80(5
)
addTo80(5
// long time... 85 //
)long time... 85 //
long time... 85
// Memoized Way
functions memoizedAddTo80() {
let cache = {}
return function(n) { // closure to access cache obj
if (n in cache) {
return cache[n]
} else {
console.log('long time...')
cache[n] = n + 80
return cache[n]
}
}
} c o n s t m e m o i z e d
=
m e m o i z e d A d d T o 8 0
( )
console.log('1.', memoized(5))
console.log('2.', memoized(5))
console.log('3.', memoized(5))
console.log('4.', memoized(10))
// long time... //
1. 85 // 2. 85 // 3.
85 // long
time... // 4. 90
Here are a few things you should avoid when wriBng your code if possible:
•eval()
•arguments
•for in
Inline Caching
function findUser(user) {
return `found ${user.firstName} ${user.lastName}`
}
const userData = {
firstName: 'Brittney',
lastName: 'Postma'
}
findUser(userData)
If this code gets opBmized to return only 1 name, then the computer would have to do a
lot more work if you needed to return a different user.
Hidden Classes
function Animal(x, y) {
this.x = x;
this.y = y;
}
obj1.a = 30;
obj1.b = 100;
obj2.b = 30;
obj2.a = 100;
Go Backobj1.x
delete to Table of Contents
= 30;
Page 10 of 93
By serng these values in a different order than they were instanBated, we are making
the compiler slower because of hidden classes. Hidden classes are what the compiler
uses under the hood to say that these 2 objects have the same properBes. If values are
introduced in a different order than it was set up in, the compiler can get confused and
think they don't have a shared hidden class, they are 2 different things, and will slow
down the computaBon. Also, the reason the delete keyword shouldn't be used is
because it would change the hidden class.
function Animal(x, y) {
// instantiating a and b in the constructor
this.a = x; this.b = y;
Managing Arguments
There are many ways using arguments that can cause a funcBon to be unopBmizable. Be
very careful when using arguments and remember:
Memory Heap
The memory heap is a place to store and write informaBon so that we can use our
memory appropriately. It is a place to allocate, use, and remove memory as needed.
Think of it as a storage room of boxes that are unordered.
first: "Brittney",
last: "Postma"
};
function subtractTwo(num) {
return num - 2;
}
function calculate() {
const sumTotal = 4 + 5;
return subtractTwo(sumTotal);
} debugger;
calculate();
Things are placed into the call stack on top and removed as they are finished. It runs in a
first in last out mode. Each call stack can point to a locaBon inside the memory heap. In
the above snippet the call stack looks like this (see next page).
calculate(
// steps through calculate() sumTotal = 9
anonymous
); // CALL
STACK
subtractTwo; // returns 9 - 2
calculate(anonymous);
// CALL STACK
calculate(
// returns 7
anonymous
)(
// CALL STACK
anonymous
);
// CALL STACK
// CALL STACK
inception();
// returns Uncaught RangeError:
// Maximum call stack size exceeded
Ni#y Snippet: Did you know, Google has hard-coded recursion into their
program to throw your brain for a loop when searching recursion?
Garbage CollecAon
JavaScript is a garbage collected language. If you allocate memory inside of a funcBon,
JavaScript will automaBcally remove it from the memory heap when the funcBon is done
being called. However, that does not mean you can forget about memory leaks. No
system is perfect, so it is important to always remember memory management.
JavaScript completes garbage collecBon with a mark and sweep method.
var person = {
first: "Brittney",
last: "Postma"
};
In the example above a memory leak is created. By changing the variable person from
an object to a string, it leaves the values of first and last in the memory heap and does
not remove it. This can be avoided by trying to keep variables out of the global
namespace, only instanBate variables inside of funcBons when possible. JavaScript is a
single threaded language, meaning only one thing can be executed at a Bme. It only has
one call stack and therefore it is a synchronous language.
// 1
// 3
// 2
console.log("1");
setTimeout(() => {
console.log("2"), 0;
});
console.log("3");
// 1
// 3
// 2
In the last example, we get the same output. How does this work if it waits 0 seconds?
The JavaScript engine will sBll send off the setTimeout() to the Web API to be ran and it
will then go into the callback queue and wait unBl the call stack is empty to be ran. So,
we end up with the exact same end point.
What the heck is the event loop anyway? | Philip Roberts | JSConf EU (link to YouTube)
Ni#y Snippet: UnQl 2009, JavaScript was only run inside of the browser.
That is when Ryan Dahl decided it would be great if we could use JavaScript
to build things outside the browser. He used C and C++ to build an
executable (exe) program called Node JS. Node JS is a JavaScript runQme
environment built on Chrome's V8 engine that uses C++ to provide the event
loop and callback queue needed to run asynchronous operaQons.
The very same Ryan Dahl then gave a talk back in 2018, 10 Things I Regret About
Node.js which led to the recent release of his new (and improved) JavaScript and
TypeScript called Deno which aims to provide a producBve and secure scripBng
environment for the modern programmer. It is built on top of V8, Rust, and TypeScript.
If you're interested in learning Deno, Zero To Mastery instructors, Andrei
Neagoie and Adam Odziemkowski (also an official Deno contributor), released the
very first comprehensive Deno course.
// 3
console.log("3", "is a crowd");
// 3 is a crowd
// 2 hi
// undefined Promise resolved
// 1 is the loneliest number
// 2 can be as bad as one
sequence().then(console.log);
parallel().then(console.log);
race().then(console.log);
Web Workers
Scaling NodeJS
MulB threading
addEventListener("message");
EXECUTION CONTEXT
Code in JavaScript is always ran inside a type of execuAon context. ExecuBon context is
simply the environment within which your code is ran. There are 2 types of execuBon
context in JavaScript, global or funcBon. There are 2 stages as well to each context, the
creaBon and execuBng phase. As the JavaScript engine starts to read your code, it
creates something called the Global ExecuAon Context.
CreaAon Phase
3. Variable Environment created - memory space for var variables and funcQons
created
4. IniBalizes all variables to undefined (also known as hoisAng) and places them
with any funcBons into memory
this;
window;
this === window;
// Window {...} //
Window {...} //
true
CreaAon Phase
ExecuAng Phase
3. Variable Environment created - memory space for variable and funcQons created
4. IniBalizes all variables to undefined and places them into memory with any new
funcBons
showArgs("hello", "world");
function noArgs() {
console.log('arguments: ', arguments);
}
noArgs();
// arguments: {}
// even though there are no arguments, the object is still created
showArgs("hello", "world");
function showArgs2(...args) {
console.log(console.log("arguments: ", args));
console.log(Array.from(arguments));
return `${args[0]} ${args[1]}`;
}
showArgs2("hello", "world");
Some people think of arrow funcQons as just being syntacQc sugar for a
regular funcQon, but arrow funcQons work a bit differently than a regular
funcQon. They are a compact alternaQve to a regular funcQon, but also
without its own bindings to this, arguments, super,
or new.target keywords. Arrow funcQons cannot be used as constructors
and are not the best opQon for methods.
var obj = {
// does not create a new scope
i: 10,
b: () => console.log(this.i, this),
c: function() {
console.log(this.i, this);
}
} ;
HOISTING
HoisBng is the process of purng all variable and funcBon declaraBons into memory
during the compile phase. In JavaScript, funcBons are fully hoisted, var variables are
hoisted and iniBalized to undefined, and let and const variables are hoisted but not
iniBalized a value. Var variables are given a memory allocaBon and iniBalized a value of
undefined unBl they are set to a value in line. So if a var variable is used in the code
before it is iniBalized, then it will return undefined. However, a funcBon can be called
from anywhere in the code base because it is fully hoisted. If let and const are used
console.log("ohhhh la la la");
}
console.log(a());
// bye
foodThoughts();
before they are declared, then they will throw a reference error because they have not
TAKEAWAYS
Avoid hoisQng when possible. It can cause memory leaks and hard to catch
bugs in your code. Use let and const as your go to variables.
LEXICAL ENVIRONMENT
A lexical environment is basically the scope or environment the engine is currently
reading code in. A new lexical environment is created when curly brackets {} are used,
even nested brackets {{...}} create a new lexical environment. The execuBon context tells
the engine which lexical environment it is currently working in and the lexical scope
determines the available variables.
function one() {
var isValid = true; // local env
two(); // new execution context
}
function two() {
var isValid; // undefined
}
/*
two() isValid = undefined
one() isValid = true
global() isValid = false -----
------------------- call stack
*/
Each environment context that is created has a link outside of its lexical environment
called the scope chain. The scope chain gives us access to variables in the parent
environment (conBnued on next page).
function findName() {
console.log(x);
var b = "b";
return printName();
}
function printName() {
var c = "c";
return "Brittney Postma";
}
function sayMyName() {
var a = "a";
return findName();
}
sayMyName();
In this example, all the funcBons have access to the global variable x, but trying to access
a variable from another funcBon would return an error. The example below will show
how the scope chain links each funcBon.
In this example, you can see that the funcBons only get access to the variables in their
parent container, not a child. The scope chain only links down the call stack, so you
almost have to think of it in reverse. It goes up to the parent, but down the call stack.
heyhey();
doodle(); // Error! because it is enclosed in its own scope.
//Function Scope
function loop() {
for (var i = 0; i < 5; i++) {
console.log(i);
}
c o n s o l e . l o g ( " f i n a l " , i ) ; / / r e t u r n s f i n a l 5
}
//Block Scope
function loop2() {
for (let i = 0; i < 5; i++) {
// can access i here
}
c o n s o l e . l o g ( " f i n a l " , i ) ; / / r e t u r n s a n e r r o r h e r e
}
loop(); /*
1 2
3 4
f i n
a l
*/ 5loop2(); // ReferenceError: i is not
defined
Here we are…
The moment has arrived, Bme to talk about this. What is this? Why is this so confusing?
For some, this is the scariest part of JavaScript. Well, hopefully we can clear some things
up.
There that's simple right? Well, maybe not, what does that mean? Back in ExecuBon
Context, we talked about how the JavaScript engine creates the global execuBon context
and iniBalizes this to the global window object.
function a() {
console.log(this);
}
a();
// Window {...}
const obj = {
property: `I'm a property of obj.`,
method: function() {
// this refers to the object obj
console.log(this.property);
}
} ;
o b j . m e t h o d ( )
; / / I ' m a
p r o p e r t y o f
o b j .
this refers to whatever is on the lec of the . (dot) when calling a method
function whichName() {
console.log(this.name);
}
const obj1 = {
name: "Obj 1",
whichName
}; const obj2 =
{
name: "Obj 2",
whichName
};
whichName(); // window
obj1.whichName(); // Obj 1
obj2.whichName(); // Obj 2
const a = function() {
console.log("a", this);
const b = function() {
console.log("b", this);
const c = {
hi: function() {
console.log("c", this);
}
};
c.hi(); // new obj c called function
};
b(); // ran by a window.a(b())
}; a(); // called by window
// a Window {…}
// b Window {…}
// c {hi: ƒ}
•new keyword binding - the new keyword changes the meaning of this to be the
object that is being created.
•implicit binding - "this" refers to the object that is calling it. It is implied, without
doing anything it's just how the language works.
•explicit binding - using the "bind" keyword to change the meaning of "this".
•arrow funcAons as methods - "this" is lexically scoped, refers to it's current
surroundings and no further. However, if "this" is inside of a method's funcBon, it
falls out of scope and belongs to the window object. To correct this, you can use a
higher order funcBon to return an arrow funcBon that calls "this".
name: "person",
age: 20,
hi() {
console.log("hi " + this);
}
} ; p e r s o n . h i ( ) ; / / t h i s =
p e r s o n { n a m e : ' p e r s o n ' ,
a g e : 2 0 , h i ( ) { . . . } } / / e x p l i c i t
b i n d i n g l e t n a m e =
" B r i t t n e y " ; c o n s t p e r s o n 3 = {
name: "person3",
age: 50,
hi: function() {
console.log("hi " + this.name);
}.bind(window)
}; person3.hi(); // hi Brittney // this
= window {...} // arrow functions
inside objects const person4 = {
name: "person4",
age: 40,
hi: function() {
var inner = () => {
console.log(this);
};
return inner();
}
} ; p e r s o n 4 . h i ( ) ; / / t h i s =
p e r s o n 4 { n a m e : ' p e r s o n 4 ' ,
a g e : 4 0 , h i ( ) { . . . } } / / i f
e i t h e r f u n c t i o n i s c h a n g e d
a r o u n d , i t d o e s n ' t w o r k
const obj = {
name: "Billy",
sing() {
console.log("a", this);
var anotherFunc = function() {
console.log("b", this);
};
anotherFunc();
}
} ;
o b j . s i
n g ( ) ;
// a {name: "Billy", sing: ƒ}
// b Window {…}
In the example above, the obj called sing() and then anotherFunc() was called within the
sing() funcBon. In JavaScript, that funcBon defaults to the Window object. It happens
because everything in JavaScript is lexically scoped except for the this keyword. It doesn't
ma^er where it is wri^en, it ma^ers how it is called. Changing anotherFunc() instead to an
arrow funcBon will fix this problem, as seen below. Arrow funcBons do not bind or set
their own context for this. If this is used in an arrow funcBon, it is taken from the outside.
Arrow funcBons also have no arguments created as funcBons do.
const obj = {
name: "Billy",
sing() {
console.log("a", this); var
anotherFunc = () => {
console.log("b", this);
};
anotherFunc();
}
} ;
o b j . s i
n g ( ) ;
// a {name: "Billy", sing: ƒ} // b
{name: "Billy", sing: ƒ}
var b = {
name: "jay",
say() {
console.log(this);
}
} ;
var c = {
name: "jay",
say() {
return function() {
console.log(this);
};
}
} ;
var d = {
name: "jay",
say() {
return () => console.log(this);
}
} ;
Aper everything is said and done, using this can sBll be a bit confusing. If you aren't sure
what it's referencing, just console.log(this) and see where it's poinBng.
const wizard = {
name: "Merlin",
health: 100,
heal(num1, num2) {
return (this.health += num1 + num2);
}
} ;
const archer = {
name: "Robin Hood",
health: 30
}; console.log(archer); // health: 30
In this example call is used to borrow the heal method from the wizard and is used on
the archer (which is actually poinBng this to archer), with the opBonal arguments added.
Apply
Apply is almost idenBcal to call, except that instead of a comma separated list of
arguments, it takes an array of arguments.
// instead of this
// wizard.heal.call(archer, 50, 20)
// apply looks like this
wizard.heal.apply(archer, [50, 20]);
// this has the same result
console.log(archer); // health: 30
const healArcher = wizard.heal.bind(archer, 50, 20);
healArcher();
console.log(archer); // health: 100
function multiply(a, b) {
return a * b;
}
function getMaxNumber(arr) {
return Math.max.apply(null, arr);
}
getMaxNumber(array); // 3
const character = {
name: "Simon",
getCharacter() {
return this.name;
}
} ; c o n s t
g i v e M e T h e C h a r a c t e r N O W =
c h a r a c t e r . g e t C h a r a c t e r ;
//How Would you fix this?
console.log("?", giveMeTheCharacterNOW()); //this should return
'Simon' but doesn't
// ANSWER
// change this line
const giveMeTheCharacterNOW =
character.getCharacter.bind(character);
console.log("?", giveMeTheCharacterNOW()); // ? Simon
Type Result
Undefined undefined
Null object*
Boolean boolean
number
Number
bigint
BigInt (new in ECMAScript 2020) string
String symbol
Symbol (new in ECMAScript 2015) function
FuncBon object object
Any other object
*Null - Why does the typeof null return object? When JavaScript was first
implemented, values were represented as a type tag and a value. The
objects type tag was 0 and the NULL pointer (0x00 in most plaeorms)
consequently had 0 as a type tag as well. A fix was proposed that would
have made typeof null === 'null', but it was rejected due to legacy code that
would have broken.
// Numbers typeof 37 === "number"; typeof 3.14 === "number"; typeof 42 === "number"; typeof
Math.LN2 === "number"; typeof Infinity === "number"; typeof NaN === "number"; // Despite
being "Not-A-Number" typeof Number("1") === "number"; // Number tries to parse things into
numbers typeof Number("shoe") === "number"; // including values that cannot be type
coerced to a number
// Booleans
typeof true === "boolean";
typeof false === "boolean";
typeof Boolean(1) === "boolean"; // Boolean() will convert values based on if they're truthy or
falsy
typeof !!1 === "boolean"; // two calls of the ! (logical NOT) operator are equivalent to Boolean()
// Symbols
typeof Symbol() === "symbol";
typeof Symbol("foo") === "symbol";
typeof Symbol.iterator === "symbol";
// Undefined
typeof undefined === "undefined";
typeof declaredButUndefinedVariable === "undefined";
typeof undeclaredVariable === "undefined";
// Objects
typeof { a: 1 } === "object";
// Functions
typeof function() {} === "function";
typeof class C {} === "function";
typeof Math.sin === "function";
Objects in JavaScript
Objects are one of the broadest types in JavaScript, almost "everything" is an
object. MDN Standard built-in objects
•string
•number
•bigint
•boolean
•null
•undefined
•symbol
There are two ways to get around this, Object.assign() or use the spread operator {...} to
"spread" or expand the object into a new variable. By doing this, it will allow the new
variable to be modified without changing the original. However, these only create a
"shallow copy".
> Deep copy: A deep copy copies all fields, and makes copies of dynamically
allocated memory pointed to by the fields. A deep copy occurs when an
object is copied along with the objects to which it refers.
/*
originalObj: {nested: {
nestedKey: "changed value"
},
key: "changed value"}
assignObj: {nested: {
nestedKey: "changed value"
},
key: "changed value"}
shallowObj: {nested: {
nestedKey: "changed value"
},
key: "value"}
deepObj: {nested: {
nestedKey: "nestedValue"
},
key: "value"}
*/
Type Coercion
Type coercion is the process of converBng one type of value into another. There are 3
types of conversion in JavaScript.
•to stringx
•to boolean
•to number
let num = 1;
let str = "1";
num == str; // true
// notice loose equality ==, not ===
// double equals (==) will perform a type conversion
// one or both sides may undergo conversions
// in this case 1 == 1 or '1' == '1' before checking equality
Strict equals: The triple equals (===) or strict equality compares two values
without type coercion. If the values are not the same type, then the values
are not equal. This is almost always the right way to check for equality in
JavaScript, so you don't accidentally coerce a value and end up with a bug in
your program. Here is the MDN Equality Comparison page and
the ECMAScript Comparison Algorithm.
FuncAon Constructor
FuncBons are objects in JavaScript, which is not true for other languages. Because of
that, they can be called mulBple ways, but they can also be constructors. A funcAon
constructor creates a new object and returns it. Every JavaScript funcBon, is actually a
funcBon object itself.
// function constructor
new Function("optionalArguments", "functionBody");
Almost everything in JavaScript can be created with a constructor. Even basic JavaScript
types like numbers and strings can be created using a constructor.
Object.prototype.__proto__;
// null
Object.prototype;
{
__proto__: null;
// ...more methods and properties
}
Object;
// function Object()
// This is the object constructor function
Object.prototype.constructor;
// function Object()
// Points to the constructor
Object.__proto__;
// function () {...}
// Because it is created with a constructor function
Prototype vs __proto__
Understanding the difference between __proto__ and prototype can be quite a
confusing concept for JavaScript developers. Every funcBon in JavaScript automaBcally
gets a prototype property when it is created that gives it the call, apply, and bind
methods. It doesn't really do anything with regular funcBons, but in constructor
function say() {
console.log('say something')
}
// with an obj
const obj = {
// nothing gets created
}
•funcBon ()
•funcBon (a,b)
•funcBon hof() { return funcBon () {} }
Instead of wriBng mulBple funcBons that do the same thing, remember DRY (don't
repeat yourself). Imagine in the example below, if you separated each code out into
individual funcBons how much more code you would be wriBng and how much code
would be repeated.
function auth(roleAmt) {
let array = [];
for (let i = 0; i < roleAmt; i++) {
array.push(i);
}
r e t u r n t r u e ;
}
Take the example below of how you can separate code out and break it down to make it
more reusable.
function multBy(a) {
return function(b) {
return a * b;
};
}
multByTwo(4); // 8
multByTen(5); // 50
function a() {
let grandpa = 'grandpa'
return function b() {
let father = 'father'
let random = 12345 // not referenced, will get garbage collected
return function c() {
let son = 'son'
return `closure inherited all the scopes: ${grandpa} > ${father} > ${son}`
}
}
}
a()()()
// closure inherited all the scopes: grandpa > father > son
const closure = grandma => mother => daughter => return `${grandma} > ${mother} > ${daughter}
`
function callMeMaybe() {
const callMe = `Hey, I just met you!`
setTimeout(function() {
console.log(callMe)
}, 8640000000);
callMeMaybe()
Two of the major reasons closures are so beneficial are memory efficiency and
encapsulaBon.
function inefficient(idx) {
const bigArray = new Array(7000).fill(" 😄 ");
console.log("created!");
return bigArray[idx];
function efficient() {
const bigArray = new Array(7000).fill(" 😄 ");
console.log("created again!");
return function(idx) {
return bigArray[idx];
};
}
inefficient(688);
inefficient(1000);
inefficient(6500);
getEfficient(688);
getEfficient(1000);
getEfficient(6500);
// created!
// created!
// created!
// created Again!
// ' 😄' // inefficient created the bigArray 3
times
// efficient created the bigArray only once
const elf2 = {
name: 'Legolas',
type: 'high',
weapon: 'bow',
say: function() {
return `Hi, my name is ${this.name}, I am a ${this.type} elf.`
}
a t t a c k : f u n c t i o n ( ) {
return `attack with ${this.weapon}`
}
}
elf1.attack()
// attack with cloth
elf2.attack()
// attack with bow
const elfMethodsStore = {
attack() {
return `attack with ${this.weapon}`;
},
say() {
return `Hi, my name is ${this.name}, I am a ${this.type} elf.`;
}
} ;
const elfMethodsStore = {
attack() {
return `attack with ${this.weapon}`;
},
say() {
return `Hi, my name is ${this.name}, I am a ${this.type} elf.`;
}
} ;
Constructor FuncAons
Using Object.create is true prototypal inheritance, the code is cleaner and easier to read.
However, you will not see this being used in most programs. Before Object.create came
around, we had the ability to use constructor funcBons. Constructor funcBons are
exactly like the funcBon constructor we talked about above. The number and string
funcBons were constructed and invoked with the new keyword and they were
capitalized. The new keyword actually changes the meaning of this for the constructor
Class
Confused yet? Prototype is a li^le weird and hard to read unless you really understand
your prototypal inheritance. No one really liked using the prototype way of adding
methods, so in ES6 JavaScript gave us the class keyword. However, classes in JavaScript
are not true classes, they are syntacBc sugar. Under the hood, it is sBll using the old
prototype method. They are in fact just "special funcBons" with one big difference,
funcBons are hoisted and classes are not. You need to declare your class before it can be
used in your codebase. Classes also comes with a new method, the constructor that
creates and instanBates an object created with class. Classes are able to be extended
upon using the extends keyword, allowing subclasses to be created. If there is a
constructor present in the extended class, the super keyword is needed to link the
constructor to the base class. You can check if something is inherited from a class by
using the keyword instanceof to compare the new object to the class.
// public declarations
class Rectangle {
height = 0;
width;
constructor(height, width) {
this.height = height;
this.width = width;
}
}
// private declarations
class Rectangle {
#height = 0;
#width;
constructor(height, width) {
this.#height = height;
this.#width = width;
}
}
So, did we obtain perfect object oriented programming? Well, that is up for debate. It is
really up to you the developer to decide which style of wriBng you like best. We did
learn that object oriented programming helps make you code more understandable,
easy to extend, easy to maintain, memory efficient, and DRY!
"If I had done classes in JavaScript back in May 1995, I would have been told
that it was too much like Java or the JavaScript was compeQng with Java ... I
was under markeQng orders to make it look like Java but not make it too big
for its britches ... [it] needed to be a silly liBle brother language." —Brendan
Eich
4 PILLARS OF OOP
•EncapsulaBon - Organizes code into containers that relate to each other and makes
it easier to maintain and reuse.
•AbstracBon - Hides the complexity from the user by doing the method calculaBons
behind the scenes.
•Inheritance - Gives the properBes of a class to another class, keeping code DRY and
saving on memory space.
•Polymorphism - The ability of an object to take on many forms allowing methods to
be used differently by different classes.
Pure FuncAons
A pure funcAon has no side effects to anything outside of it and given the same input
will always output the same value. They do not change any data passed into them, but
create new data to return without altering the original. However, it is not possible to
have 100% pure funcBons. At some point you need to interact with the dom or fetch an
api. Even console.log makes a funcBon unpure because it uses the window object
outside of the funcBon. Fact is a program cannot exist without side effects. So, the goal
of funcBonal programming is to minimize side effects by isolaBng them away from the
data.
Build lots of very small, reusable and predictable pure funcBons that do the following:
•Complete 1 task per funcBon.
•Do not mutate state.
•Do not share state.
•Be predictable.
•Be composable, one input and one output.
•Be pure if possible.
•Return something.
ReferenAal transparency
One important concept of funcBonal programming is referenAal transparency, the
ability to replace an expression with the resulBng value without changing the result of
the program.
function b(num) {
return num * 2; }
b(a(3, 4)); // 14
// a should always return 7
// so it could be changed to
b(7); // 14
// and the output is the same
Idempotence
Idempotence is another important piece of funcBonal programming. It is the idea that
given the same input to a funcBon, you will always return the same output. The funcBon
could be used over and over again and nothing changes. This is how you make your code
predictable.
ImperaAve vs DeclaraAve
ImperaBve programming tells the computer what to do and how to complete it.
DeclaraBve programming only tells the computer what to do, but not how to do things.
Humans are declaraBve by nature, but computers typically need more imperaBve type
programming. However, using higher level languages like JavaScript is actually being less
declaraBve. This is important in funcBon programming because we want to be more
declaraBve to be^er understand our code and let the computer handle the dirty work of
figuring out the best way to do something.
console.log(i);
}
// more declarative
let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
arr.forEach(item => console.log(item));
// Bad code
const obj = {name: 'Brittney'}
function clone(obj) {
return {...obj} // this is pure
}
// Better code
function updateName(obj) {
const newObj = clone(obj)
newObj.name = 'Joe'
return newObj
}
You may be thinking that this could get really expensive, memory wise, to just copy code
over and over. However, there is something called structural sharing that allows the data
to only copy new informaBon and points to the original state for any commonaliBes.
Arity
Arity simply means the number of arguments a funcBon takes. The more parameters a
funcBon has the harder it becomes to break apart and reuse. Try to sBck to only 1 or 2
parameters when wriBng funcBons.
function taxItems(user) {
userHistory.push(
Object.assign({}, user, { cart: user.cart, purchases: user.purchases })
);
const { cart } = user;
const taxRate = 1.4;
const updatedCart = cart.map(item => {
return {
name: item.name,
price: item.price * taxRate
};
});
return Object.assign({}, user, { cart: updatedCart });
}
function buyItems(user) {
userHistory.push(
Object.assign({}, user, { cart: user.cart, purchases: user.purchases })
);
return Object.assign({}, user, { purchases: user.cart });
}
function emptyCart(user) {
userHistory.push(
Object.assign({}, user, { cart: user.cart, purchases: user.purchases })
);
return Object.assign({}, user, { cart: [] });
}
purchaseItems(
emptyCart,
buyItems,
taxItems,
addToCart
)(user, { name: "laptop", price: 200 });
OOP Problems
One of the drawbacks to inheritance is that it is based on the fact that it won't change,
we tell it what it is. We create a class and give it properBes and methods that describe
the class. But say, down the road, we need to update that class and add more
funcBonality. Adding a new method to the base class will create rippling effects through
your enBre program. FP is more declaraBve, what to do not how, and OOP is more
imperaBve, what and how to do something. This is the Aght coupling problem, things
having to depend on one another, which leads to the fragile base class problem,
seemingly safe changes cause unforeseen repercussions. It is the opposite of small
reusable code. Changing one small thing in either of the class or subclasses could break
the program. Another problem is hierarchy where you may need to create a subclass
that can only do 1 part of the class, but instead you get everything passed down to it.
Finally
ComposiBon is probably a be^er tool to use when creaBng programs because it creates
a more stable environment that is easier to change in the future. The key is to decide
which structure is be^er for your project. You can use ideas from both of these styles to
write your code. React uses OOP in class components to extend inheritance and then
uses FP in the pure components.
Module PaUerns
Originally in JavaScript, we had the module paUern. Before block scope came around,
there was only global scope and funcBon scope. To create this idea of modules,
a module scope was implemented just above the funcBon scope. This allowed variables
to be shared, by exporBng and imporBng, between the funcBons without having to go
through the global scope. A funcBon as a module is essenBally just an immediately
invoked funcBon expression, IIFE.
•CommonJS - uses the keywords require and exports to interact with the module
system. Require is a funcBon used to import from another module and exports is an
object where funcBons get exported from. These are run synchronously where we
wait on one module to load before another can start and this is not ideal for browsers.
However, this code may look familiar because NodeJS sBll uses this library. There are
other packages such as Browserify and webpack that aid in bundling scripts with
CommonJS to be used in the browsers.
•Asynchronous Module DefiniAon (AMD) - as in the name, AMD loads modules
asynchronously. This was great for browsers early on before packages that bundled
code.
define(['module1', 'module2'], funcBon(module1, module2)
{console.log(module1.setName());});
The define funcBon takes an array of dependency modules that are loaded in a non-
blocking manner in the background. Once completed, the callback funcBon is then
executed. Packages came out like RequireJS that implemented the AMD endpoint and
was the main way people used AMD modules.
ES6 Modules
Aper ES6 came out, pre^y much everything above was thrown out the window with 2
new keywords. We can now use the import and export keywords in our files to
implement modules. This again may look familiar from popular frameworks like React.
There are 2 types of exports, named and default. A named export is imported using curly
braces ({ importFnName }) and a default funcBon is added in created like this:
Trying to run this in the browser there is sBll 2 more things that have to be done. You
have to declare the type in the html script tag as module and the file has to be served
from a server. You can spin up your own server with a package like live-server on npm.
fail();
// this works // because it goes line by line
// we have made an oopsie Error: oopsie at fail
// still good
// asynchronous .catch()
Promise.resolve("asyncfail")
.then(response => {
console.log(response);
return response;
})
.catch(error => {
console.log(err);
});
(async function() {
try {
await Promise.resolve("oopsie #1");
await Promise.reject("oopsie #2");
} catch (err) {
console.log(err);
}
c o n s o l e . l o g ( " i s t h i s s t i l l g o o d ? " ) ;
})();
myError.name; // "Error"
myError.message; // "oopsie"
myError.stack; // "Error: oopsie at <anonymous>:1:17
function a() {
const b = new Error("uh oh");
return b;
}
b(); // b().stack
// Error: uh oh
// at a (<anonymous>:2:12)
// at <anonymous>:1:1
}
}
THE END…
This is the "official" end of the Advanced JavaScript secBon, but Bri^ney added a small
secBon of her notes on data structures and algorithms because they are an important
part of developing great programs.
Arrays
Arrays order items sequenBally with an index. Arrays are probably the simplest and the
most widely used data structure because the are fast and take up the least amount of
space. They also have the least amount of rules. Array methods have different Bme
complexiBes, called Big-Order or Big-O notaBons. _O(1) is constant Bme, meaning the Bme
does not change with the data input. The _O(n) is linear Bme, meaning Bme changes or
goes up the more operaBons that need to be performed. _O(1) can end up as _O(n) in
languages like JavaScript if it needs to allocate more memory for the array. There is also,
Big-Omega or Big-Ω notaBon that give the best possible Bme for your
strings[2]; // c // O(1)
strings.push("e"); // O(1)
// ['a', 'b', 'c', 'd', 'e']
strings.pop(); // O(1)
// ['a', 'b', 'c', 'd']
strings.unshift("z"); // O(n)
// ['z', 'a', 'b', 'c', 'd']
// unshift took 5 operations to complete.
// ['a', 'b', 'c', 'd']
ImplemenAng an Array
Arrays can be declared easily in JavaScript, but what if we built our own…
Hash FuncAon
A hash funcBon takes a key and maps it to a value of fixed length for every input. It is an
idempotent funcBon meaning given the same input the output will always be the same. A
hash table uses the hash funcBon to compute the key into a hash code and map that to an
address in memory where it is stored with the value in a bucket. Using the hashing
technique makes looking up data inside the hash table very fast and is usually O(1) Bme.
let character = {
age: 20,
name: "Harry Potter",
muggle: false,
patronus: function() {
console.log("Expecto Patronum!");
}
} ;
character.age; // 20 // O(1)
character.levitate = "Wingardium Leviosa!"; // O(1)
character.patronus(); // Expecto Patronum! // O(1)
Hash Collisions
Every data structure is going to come with downsides. Hash collisions are what happens
when a hash funcBon maps a key to the same address as a previously added key. With
enough data and limited memory, we will always run into this collision. This does not
overwrite the previous informaBon, but creates a linked list and slows down our ability
to access the informaBon. Your big O notaBon Bme jumps from O(1) to O(n/k) where n is
the Bme and k is the size of the hash table.
_hash(key) {
let hash = 0;
for (let i = 0; i < key.length; i++) {
hash = (hash + key.charCodeAt(i) * i) % this.data.length;
}
r e t u r n h a s h ;
}
set(key, value) {
let address = this._hash(key); if
(!this.data[address]) {
this.data[address] = [];
}
t h i s . d a t a [ a d d r e s s ] . p u s h ( [ k e y , v a l u e ] ) ;
r e t u r n t h i s . d a t a ;
}
get(key) {
const address = this._hash(key);
const currentBucket = this.data[address];
if (currentBucket) {
for (let i = 0; i < currentBucket.length; i++) {
if (currentBucket[i][0] === key) {
return currentBucket[i][1];
}
}
}
r e t u r n u n d e f i n e d ;
}
keys() {
const keysArray = [];
for (let i = 0; i < this.data.length; i++) {
if (this.data[i]) {
keysArray.push(this.data[i][0][0]);
}
}
r e t u r n k e y s A r r a y ;
}
}