Skip to content

Instantly share code, notes, and snippets.

@PintoGideon
Last active June 6, 2019 17:39
Show Gist options
  • Save PintoGideon/815bcb82e0f5bf91f30460b04d5bd91b to your computer and use it in GitHub Desktop.
Save PintoGideon/815bcb82e0f5bf91f30460b04d5bd91b to your computer and use it in GitHub Desktop.
Js Foundations

Hoisting

console.log(teddy)
var teddy='bear;
sing();
function sing(){
console.log('Oh hey hi')
}

Output undefined Oh hey hi

The JS engine is a 2 pass system. In the first pass, it is going to hoist or assign a memory location to variables and functions and in the 2nd pass, it's going to assign a value.

Variables are partially hoisted and functions are fully hoisted. In the above

(function(){
console.log("Does this hoist")
})()

The above function does not hoist. const and let are not hoisted. It's going to throw a reference error.

What about function expressions?

var app=function(){
console.log('How does this hoist?');
}

The variable app is going to be hoisted and assigned undefined. If I run sing2() before it is defined, I get an error saying that the function is not defined.

Difference between a function expression and declaration

Function expressions are defined at runtime while function declarations are defined at parse time.

Variable environment

When a function is invoked, we get the this keyword and arguments. The arguments keyword give us an object with the argument passed as property values.

Now we want the arguments to be an array so that we can perform some computations on them. Now the arguments are an array.

function marry2(...args){
console.log('Arguments',arguments);
}

marry('Tim','Tina')

Variable Environment

In Javascript, our lexical scope determines our available variable. Not where the function is called.

The scope chain

function sayName(){
var a='a';

return function findName(){
var b='b';
console.log(c)

return function printName(){
var c='c';
return 'Andrei Neagoie'
}
}
}

IIFE

One major issue with global variables is that we can have collissions. To avoid this JS developers used an IIFE.

An IIFE is an function expression which looks like this:

(function(){

})();

Using thi design pattern, we can define all our variables in a local scope. We can aalso see that the function expression is immediately invoked. We cannot do the same with a function declaration. What's the benefit of this?

(function(){
var a='Is this available outside'
})();

console.log(a) //reference error

This is really powerful though. I can encapsulate the behaviour of the underlying function and only return the values neeed. We still used a global namespace but we kept it from polluting.

var z=1
var script1=(function(){
return 5;

})()

JQuery made use of the IIFE pattern

// Importing jquery here
<script src="http://code.jquery.com/......"></script>

// Now jquery has been added to our window object
<script>

var script1=(function(what){
what('h1').click(function(){
what('h1').hide();
})

function a(){
return 5;
}

return {
a:a
}

})(jQuery)
</script>

We can also do something like this

script.a()  //5

this keyword

this is the object that the function is a property of.

function a()
{
console.log(this)
}
a();  //Window

Object Oriented Javascript

function userCreator(name,score)
{
let newUser={};
newUser.name=name;
newUser.score=score;

newUser.increment=function(){
newUser.score++
};

return newUser;
}

let user1=userCreator('Will',3);
let user2=userCreator('Gidi',4);
user1.increment();

The increment function has a common behavior for both objects. This code is not scalable to 10000 users as we are making copies for each user.

proto

Mental Model Store the increment function in just one object and have the interpreter, if it doesn't find the function on user1, look up to that object if it's there.

How to make this link?

function userCreator(name,score)
{
let newUser=Object.create(userFunctionStore)
newUser.name=name;
newUser.score=score;
return newUser;
}

let userFunctionStore={
increment:function(){this.score++}
login:function(){console.log('You are logged in');}
}

let user1=userCreator('Will',3);
let user3=userCreator('Tim',4);
user1.increment();
Object.create(userFunctionStore)

The above snippet essentially creates an empty object with a bond to the userFunctionStore. This is how the object that the function returns.

{
name:'Gideon',
score:4,
__proto__:userFunctionStore
}

The __proto__ is a reference to the userFunctionStore in which the increment function is defined.

new operator

In JavaScript, a function is an object first.

userCreator={
call:
}

userCreator() actually is userCreator.call under the hood.

When we call the constructor function with new in front, we automate 2 things

  1. Create a new user object
  2. return the new user object
function User(name,score){
this.name=name;
this.score=score;
}

User.prototype.increment=function(){
console.log('login')
}

let user1=new User("Eva",3);
user1.increment();

User is an object and a function. It has a property on it called prototype to which we add an increment function.

It's going to look something like this

User={
prototype:{
increment:function(){},
login:function(){}
}

}

The function User returns out is an object which looks something like this.

this:{
name:'Eva',
score:3,
__proto__:User.prototype
}

Arrow Functions

function UserCreator(name,score){

this.name=name;
this.score=score;

}

UserCreator.prototype.increment=function()
{
function add1(){
this.score++
}

add1();
}

var user1= new UserCreator('Gidi',10);
user1.increment();

The returned object from the UserCreator function looks like this {name:'Gidi', score:10, __proto__:User.prototype}

When the increment function is called, the this inside the function is set to user1. However, the this inside function add1 is set to the window object. The arrow function has it's this assignment lexically scoped.

Now we can do the following the above code

UserCreator.prototype.increment=function()
{
const add=()=>{this.score++}
add()
}

Arrow functions bind 'this' lexically

Class keyword

Javascript masks the prototypal nature of Javascript with the class keyword.

class UserCreator{
constructor(name,score){
this.name=name;
this.score=score;
}

increment(){
this.score++
}

login(){
console.log('Login')
}

}

const user1=new UserCreator('Gidi',10)
user1.increment();

Javascript uses the proto link to give objects, functions and array a bunch of bonus functionality. All objects by default have proto

  • With Object.create, we overrule the default __proto__ reference to Object.prototype and replace it with functionStore. = But functionStore is an object so it has a __proto__ reference to Object.prototype. We just intercede in the chain.
const obj={
num:3
}

obj.num // 3
obj.hasOwnProperty('num')

The object method looks like this:

obj={
num:3
__proto__:Object.prototype
}

Object is a function as well as an object. Object has a prototype property on it and looks like this.

Object={
prototype: {
hasOwnProperty: function(){
//  inside function body
}
}
}

Array.prototype and Function.prototype

function multiplyBy2(num){
return num*2
}

multiplyBy2.toString()
multiplyBy2.hasOwnProperty('score')

The toString method does not exist in the function definition of multiplyBy2. The object type of multiplyBy2 looks like this.

{
__proto__: Function.prototype
}

The Function keyword is similary to Object. They are both created by the JavaScript Engine. This is how the object type of function looks like.

Function={
prototype:{
toString: function(){},
call:function(){},
apply:function(){},
bind:function(){}
__proto__:Object.prototype
}
}

This is how the Object's object type looks like

Object={
prototype:{
hasOwnProperty:function(){}
}
}

If we look for a method which is not present in both the Function and the Object, the it throws an error. The proto on the Object's object points to null

Subclassing

Mental Model

Suppose we have a user with name and score properties. We want another user to have these properties with extended features.

function userCreator(name,score)
{
const newUser=Object.create(userFunctionStore);
newUser.name=name;
newUser.score=score;

return newUser;
}

const userFunctionStore={
sayName: function(){console.log(this.name)),
increment:function(){this.score++}
}

const user1=userCreator('Gidi',10);
user1.sayName() //Gideon

The userCreator returns an object which looks like this:

{
name:'Gideon',
score:10,
__proto__:userFunctionStore
}

Now I want to create an user with an extended feature

function paidUserCreator(paidUserName,paidUserScore, monthlyFees)
{
const paidUser=userCreator('Gideon',8);
Object.setPrototypeOf(paidUser, paidUserFunction);
paidUser.monthlyFees=monthlyFees;
return paidUser;

}



const paidUserfunction={
displayFees: function(){console.log(this.monthlyFees));
}

Object.setPrototypeOf(paidUserFunction, userFunctionStore);


const subscribedUser=paidUser('Gideon',1-0,500);
subscribedUser.displayFees() //500
subsribedUser.sayName()  // 'Gideon'

Factory function approach

userCreator is going to return an object

{
name:'Gideon',
score:8,
__proto__:userFunctionStore
}

We do not want the __proto__ to reference userFunctions. We want the paidUser function to be referenced instead.

Object.setPrototypeOf(newPaidUser,paidUserFunctions)

The paid user object returned out by paidUserCreator is this

{
name:'Gideon',
score:8,
monthlyFees:500,
__proto__:paidUserFunctions
}

Prototypal lookup

paidUser.displayFees();

// paidUser does not find displayFees defined as a method.
// Look's in proto

// It finds a reference to paidUserFunction which has the displayFees method defined.
paidUser.sayName();

//Method's not defined in the object body
//Look in __proto__
//Does not find it
//Look in the __proto__ of the referenced object
//Finds it

Call and Apply approach

const obj={
num:3,
increment:function(){this.name++);
}

const otherObj={
num:10
}

obj.increment();

obj.increment.call(otherObj);

Subclassing alternate approach.

function userCreator(name,score){
this.name=name,
this.score=score
}


userCreator.prototype.sayName=function(){
console.log('I am ' + this.name)
}

userCreator.prototype.increment=function(){
this.score++;
}

const user1=new UserCreator('Phil',3);
user1.sayName();

Here we use the new creator which creates an object implicitly with it's __proto__ reference to userCreator.prototype.

function paidUserCreator(paidUserName, paidUserScore, accoundBalance)
{
userCreator.call(this,paidUserName,paidUserScore)
this.accountBalance=accountBalance
}

paidUserCreator.prototype=Object.create(userCreator.prototype);

paidUserCreator.prototype.increaseBalance=function(){
this.accountBalance++;
}

const paidUser=new paidUserCreator('Alyssa',8,10);
paidUser.increaseBalance();
paidUser.sayName();

The userCreator.call() sends this as reference to the newly created object in userCreator. This userCreator function assigns the values name and score to the object which is essentially one layer above. Hence if we observe, the userCreator function does not return the object as we have not used a new keyword. It is just manipulating the object in the paidUserCreator.

The paidUserCreator function returns an object which looks like this

{
name:'Alyssa',
score:8,
accountBalance:10
__proto__:paidUserCreator.prototype
}
paidUserCreator.prototype=Object.create(userCreator.prototype);

The above line assigns the userCreator.prototype to the proto reference of the paidUserCreator.prototype object.

class UserCreator{
constructor(name,score){
this.name=name,
this.score=score
}
sayName(){
console.log('I am' + this.name);
}
increment(){
this.score++
}
}

const user1=new UserCreator('Will',9);

Class syntax with the Super and Extends Keyword

The class keyword is essentially a object-function combo like the example before.

The constructor function is function part of the combo which takes in the parameters and assigns them to the object created by the 'new' keyword.

class paidUserCreator extends userCreator{
constructor(paidUser,paidScore,accountbalance)
{
super(paidName,paidScore);
this.accountBalance=accountBalance;
}
incrementBalance(){
this.accountBalance++;
}
}

const paidUser1=new paidUserCreator('Alyssa',8,10);
paidUser1.increaseBalnce();
paidUser.sayName()

We know that the paidUserCreator is a function-object combo which has on it's object has a prototype property with the method increaseBalance.

The extends keyword sets the proto reference to the UserCreator.prototype.

{prototype: {
           increaseBalance:function(){},
          __proto__:UserCreator.prototype
           } 
__proto__:userCreator
}

The extends keyword also sets the proto reference to the userCreator object-function combo.

paidUser1=new paidUserCreator('Alyssa',8,25)

When we call the paidUserCreator, we are going to assign the arguments to the parameters. The this assignment is uninitialized. We call super with the paidName and the paidScore parameter values.

The this in paidUserCreator is going to be assigned to the return value of calling super.

this=super('Alyssa',8)

//Behind the scenes
this=Reflect.construct(userCreator,[Alyssa,8],paidUserCreattor)

//Which is essentially doing this
this=new userCreator('Alyssa',8)

We are going to create the new object inside of our class Usercreator unlike our above approach where we created a new object in paidUserCreator and created a side effect.

One gotcha to note here is whenever we create an object using the new keyword infront of userCreator, the returned object's proto reference is set to userCreator.prototype.

However, with the super keyword overrides the proto referece in our new object to paidUserCreator.prototype which we did manually before.

The new object returned is assigned to this in paidUserCreator.

Global Execution Context:

Default:


Phase: Creation

  • window : global object
  • this : window
var name='Tyler'
var handle='@tylermcginnis'

function getUser(){
return{
name : name,
handle : handle
}

}

Global Execution Context

1st Step:


Phase : Creation

  • window : global object
  • this : window
  • name : undefined
  • handle : undefined
  • getUser : fn()

2nd Step:


Phase: Execution

  • window : global object
  • this : window
  • name : 'gideon'
  • handle : 'gpinto'
  • getUser : fn()

functions

Hoisting

Hoisting is a language convention to discuss the idea of lexical scope.

function teacher(){
return "Gideon"
}

var otherTeacher;
teacher(); //"Gideon"

otherTeacher(); //Type Error

otherTeacher=function(){
return "Mahesh"
}

Let doesn't hoist?

There is a difference in how let and const hoist. let is hoisted in it's block scope but don't get initialized to undefined.

Type Coercion

Whenever there's a divergence between what your brain thinks is happening and what the computer does, that's where bugs enter the code.

Fundamental Pillars of JavaScript

  • Types
  • Scope
  • Objects

Coercion

Primitive Datatypes

  • undefined
  • string
  • number
  • boolean
  • object
  • symbol

typeof operator

var v;

typeof v; //undefined

v="1"
typeof v; //string

v=2
typeof v //number

v=true;
typeof v  //boolean

v={}
typeof v   //object

v=Symbol();
typeof v;  //symbol

typeof doesntExist; //undefined

var v=null;
typeof v;  // object  OOPS!!

v=function(){
}
typeof v   // function

undefined vs undeclared

undefined means that there is a variable declared and at the moment it has no value

undeclared means it's never been created in any scope we have access to.

NaN

Essentially NaN means a special sentinel value that indicates a nonnumeric value.

var myAge=Number('0o46') //38
var myNextAge=Number('39) //39
var myCatsAge=Number("n/a")  //NaN
myAge="my son's age";  //NaN

isNaN

isNaN(myAge);  //false
isNaN(myCatsAge) //true
isNaN('my sons age') //true

The third example in the above code return true for a string input. The isNaN function is going to coerce the input to a Number and return true if it's a NaN value.

The ES6 specification gives us the correct result for the input of the type NaN.

NaN: Invalid Number

typeof NaN
number
Number.isNaN(myCatsAge)    //true
Number.isNaN("My son's age")  //fase

Negative Zero

The negative representation of zero.

var trendRate=-0;
trendRate===-0 //true

trendRate.toString();  // "0" OOPS!
trendRate===0;   //"True"  OOPS!
trendRate<0; //false
trendRate>0; //false

Object.is(trendRate,-0); //true
Object.is(trendRate,0);  //false;

Application

function formatTrend(trendRate){
var direction=(trendRate<0 || Object.is(trendRate,-0)) ? 
"down":"up";
return `$(direction) $(Math.abs(trendRate)"`;

}

formatTrend(-3)   //"down 3"
format(-0) // "down 0"

Fundamental Objects

  • Built-in Objects
  • Native Functions

Use new:

  • Object()
  • Array()
  • Function()
  • Date()
  • RegExp()
  • Error()

Don's use new:

  • String()
  • Number()
  • Boolean()
var yesterday=new Date("March 6, 2019");
yesterday.toUTCString();


var myGPA=String(transcript.gpa);

Abstract Operations

  • ToPrimitive(hint)
  • ToString- It takes any value and gives us a string representation
[]===>""
[1,2,3]===>"1,2,3"
[null, undefined]=","
[[],[],[]]="..."

ToPrimitive(hint)

hint "number"
number.valueOf()
number.toString()


hint:"string" string.toString() string.valueof()


ToString

The ToString abstract takes any value and gives us a string representation of the value.

If we call ToString on an object, it's going to invoke the ToPrimitive with string as a hint.

Example:

 [] gives you "",
 [1,2,3] gives "1,2,3"
 

ToNumber

If we don't have a number but need to perform a numeric operation, we can use ToNumber as the abstract operation.

Examples:

"" when coerced into a number gives me 0.

ToBoolean

Falsy:

-0,-0, null, NaN, false, undefined


Truthy:

"foo", 23, {a:1}, [1,3], true, function(){...}


Case of Coercion

var numStudents=16;
console.log(`There are ${numStudents} of students`);

If either of them is a string, then the '+' operator prefers a string concatenation

function addAStudent(numStudents){
return numStudents+1
}

addAStudent(
+studentsInputElem.value
);

The '+' operator invokes the ToNumber abstract operation.

Falsy && Truthy

if(studentsInputElem.value)
{
numStudents=Number(studentsInputElem.value);
}

while(newStudents.length){
enrollStudent(newStudents.pop());
}

Boxing

It is a form of implicit coercion. Javascript coerces the primitive string value into an object upon which the .length method can be called on.

studentNameElem.value.length

Corner Case for Type Conversion

The Root of all (Coercion) Evil

studentInput.value="";
Number(studentInput.value) //0
studentInput.value=" \t\n"
Number(studentInput.value)  //0

Double Equals Algorithm

This algorithm prefers to do numeric coercion. It prefers to reduce everything down to number for comparison.

var workshopEnrollment=16;
var workshopEnrollment2=worshopElem.value

if(Number(workshopEnrollment)==Number(workshopEnrollment2)
{
//
}

Summary of the algorithm

If the Types are the same: ===
If null or undefined: equal
If non-primitives:ToPrimitive
Prefer:ToNumber
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment