DEV Community

Cover image for Rethinking JS [short notes]
Shihabudheen US
Shihabudheen US

Posted on • Updated on

Rethinking JS [short notes]

Mental model 🧠

  • Mental models are how we think 🤔 about something
  • Slow and fast thinking
  • Slow thinking is laborious, front lobe
  • ⚡️ Fast thinking is less tiring and most often preferred(default)
  • Mental models are essential to write good code, easy to reason about and less prone to errors

Context

You are on the JS asteroid in the space. You see stars⭐️, planets🪐 and asteroids ☄️ floating in space 🌌.

Values and Expressions

Values

  • values are things. They are like numbers in Math, words in a sentence and dots in geometry. It is a thing 🧱. We can't 🚫 do much to them, but we can do things with them
  • there are two types of values in JS. Primitive and Composite
  • Primitive values are numbers and strings(and few more). They are like far distant stars and we can only look and refer them, but we can't change them or affect them.
  • Composite values are different. We can manipulate them from code. Like functions and objects. They are like rocks closer to the asteroid that we are on.

Expression

  • expressions are kind of questions ❓ that we ask JS. The expressions always result in values.

typeof

to know the type of value we can use typeof operator.
typeof _value will give us the type of the value as string.

The types can be,
Primitive

  • undefined (undefined)
  • null (object)
  • number (number)
  • bigint
  • symbol
  • string
  • boolean

Composite

  • object (object)
  • function (function)

Primitives are immutable

In JS, primitives are immutable. For example

let name='yikes'
name[0]='l' // can't change
console.log(name) // 'yikes'
Enter fullscreen mode Exit fullscreen mode

Even though string appears to be similar to an array, which is not a primitive we might have an intuition that we can mutate or change it. But in practice, we can't since the strings are primitive. This also applies to all the primitives.

let number=10
number.value='ten'
console.log(number) // 10
Enter fullscreen mode Exit fullscreen mode

Since the addition of a property is also a kind of mutation, this too is not allowed on Primitives.

Variables

Variables are like wires. We can connect the variables to values. In order to connect a variable wire to a value, we use assignment statements.

let x='Shihab'

Now the variable wire x is connected to string value Shihab. The RHS of an assignment is always an expression.

let world='World'
let say='Hello '+ world
Enter fullscreen mode Exit fullscreen mode

Since we are asking JS, what is 'Hello '+world it is an expression which resolves to a value 'Hello World'.

The RHS of let x='Shihab' is also an expression, since it also resolves to a value 'Shihab'. We call it literlas since we write down the exact value.

In JS, we always pass the value and not the variable itself. We cannot change what the variable points to, but at times we can change the value itself.

let num=10
function double(x){
   x=x*2
}
double(num) // here we pass the value 10 
            // and not the reference to it
console.log(num) // 10
Enter fullscreen mode Exit fullscreen mode
let arr=[10,20]
function mutate(input){
  input[0]=30
}
mutate(arr)
console.log(arr) // [30,20]
Enter fullscreen mode Exit fullscreen mode

This is because we pass the value of arr which is [10,20]. Since arrays are mutable, we were able to mutate the value. And the function cannot change the value arr was wired to, thus we get [30,20] when trying to print arr.

image


Counting Values

We should always think, values as having a precise count.

Undefined ----> Undefined [1]
null -----> null
Boolean -----> true or false [2]
Number ----> 18 quintillion [...]
BigInit ---> Use for arbitrary precision and no round-off. Mainly used in financial calculations.
String ---> A string for each conceivable string that exists in the universe. A string has properties but it is not as same as other objects. Since the string is primitive it is immutable.
Symbols ---> recently new
Objects ---> Each time it creates a brand new Object
Function ---> Each function expressions are distinct. Like any other things in JS, functions are expressions too. When it is called with () [Call expression] JS resolves it to the return value of it. If not, it resolves to function expression or body. Function are also Objects, but special objects. Whatever you can do with Objects can be done with functions too. But what makes function different is, the can be invoked.

In this we way, we have can better place and point our variables to values. In our model, there should be only two booleans, and one undefined and null. All the time, when a primitive is being referred, JS actually summons them. But in the case of Objects {} and functions (), it creates a brand new value for us.




Equality in JS

In JS there are mainly 3 types of equalities

  1. Same Value Object.is()
  2. Strict equality ===
  3. Loose equality ==

Same Value

Same value returns true is we are pointing to the same values.

Strict Value

It is same as Object.is() expect for

NaN === NaN // false
0 === -0 // true
-0 === 0
Enter fullscreen mode Exit fullscreen mode

To test, if a number is NaN we can use Number.isNaN() or num !== num.

Loose Equality

It just compares the sameness of values.

2=='2'
true==0
Enter fullscreen mode Exit fullscreen mode

Properties

Properties are similar to variables. They also point to values, but they start from an Object and they belong to it.

let sherlock={
 surname:'Homes',
 address:{
  city:'London'
 }
}
Enter fullscreen mode Exit fullscreen mode

Object

Even though it seems like a single object is being created there are actually two distinct objects here. An object can never reside inside another object, even though it might seem nested from code.

let sherlock={
 surname:'Holmes',
 age:64
}
Enter fullscreen mode Exit fullscreen mode

Rules of reading a property

console.log(sherlock.age)

Read property

Properties will have names, which are strings basically. They must be unique within an object,i.e. an object cannot have two keys with the same name. The names are case sensitive too.

These rules look roughly like this:

  1. Figure out the value of the part before the dot (.).

  2. If that value is null or undefined, throw an error immediately.

  3. Check whether a property with that name exists in our object.

a. If it exists, answer with the value this property points to.

b. If it doesn’t exist, answer with the undefined value.

If a property is missing, we get an undefined. But it doesn't mean that we have that property on the object pointing to undefined. It is more like, we are asking JS for the value (expression) and it is replying us that it is not defined, undefined.

Assigning to a property

sherlock.age=65

  1. figure out which wire is on the left side
    Read left

  2. we figure out which value is on the right side
    Read right

  3. point the wire on the left side to the value on the right side
    Point

Mutation

Suppose we have the following

let sherlock={
 surname:'Holmes',
 address:{
   city:'London'
 }
}
Enter fullscreen mode Exit fullscreen mode

sherlock object

let john={
 surname:'John',
 address: sherlock.address
}
Enter fullscreen mode Exit fullscreen mode

john object

Now we want to change john.

john.surname='Lennon'
john.address.city='Malibu'
Enter fullscreen mode Exit fullscreen mode

mutation

But we observe we could see sherlock.address.city has also changed to Malibu from London. This is because both sherlock.address and john.address pointed to the same Object.

So because of this, the mutation can be dangerous. It might unintentionally change the values at all the places where it is being referred.

In order to avoid mutation, we could have done the following:

  1. When mutating john,
john={
 surname:'Lennon',
 address:{ city: 'Malibu' }
}
Enter fullscreen mode Exit fullscreen mode

mutation 1

2.

john.surname='Lennon'
john.address={ city:'Malibu' }
Enter fullscreen mode Exit fullscreen mode

mutation 2

Is Mutation that Bad?

The mutation is not bad at all, but we should pay closer attention to it. The bliss with the mutation is, it helps us update or change a value realtime at multiple places. If think the other way, that is misery with it too.

Even though you declare an Object with const it will not presentation mutation to the Object. It will only prevent the reassignments.

const x = {
  name:'Shihab'
}

x.name = 'Shifa' // allowed
x.age = 22 // allowed

x = {} // not allowed
Enter fullscreen mode Exit fullscreen mode

Prototype __proto__

let human={
 teeth:32
}

let gwen={
 age:19
}
Enter fullscreen mode Exit fullscreen mode

console.log(gwen.teeth) // undefined

But we can access teeth property of human in gwen by,

let gwen={
 __proto__: human
}
Enter fullscreen mode Exit fullscreen mode

giphy

Now,

console.log(gwen.teeth) // 32

With adding __proto__ we instruct JS, to continue searching for teeth in __proto__ too.

Prototype Chain

The search for the values will continue until the base prototype is reached. In JS the base prototype is Object.__proto__ which is set to null.

As you can see, so this is kind of a chain that is getting created when we as JS to look for a property on an Object. This is being referred to as prototype chain.

let mammal={
 brainy:true
}

let human={
 __proto__:mammal,
 teeth:32
}

let gwen={
 __proto__:human,
 age:19
}

console.log(gwen.brainy) // true
Enter fullscreen mode Exit fullscreen mode

Shadowing

When an Object has the same property on it and as well as inside the __proto__, the own shadows the value on __proto__. This is called Shadowing.

Assignments

The property assignments directly happen on the Object and not on the __proto__.

let human={
 teeth:32
}

let gwen={
 __proto__:human
}
Enter fullscreen mode Exit fullscreen mode

On gwen.teeth=31

To check if the property belongs to an Object or its __proto__, we have a method called hasOwnProperty on Object.

ObjectName.hasOwnProperty(prop)

If the prop is a property on ObjectName, it will return true if not false.

Object prototype

When we create a new Object, there is a __proto__ that gets added by default. It is the prototype of the Object.

To terminate the prototype chain of any Object we can just assign null to its __proto__.

Polluting prototype

All the in-built methods and properties of Objects, Arrays and Strings are defined in the __proto__ of their base. In this way, these get shared among all the values, that are being created out of it.

But this practice of sharing is highly discouraged.

But the sharing of methods and properties via the prototype chain is the base of classes and all other features. But the direct usage of polluting prototype is not recommended.

proto vs. prototype

You might be wondering: what in the world is the prototype property?

The story around this is confusing. Before JavaScript added classes, it was common to write them as functions that produce objects, for example:

function Donut() {
  return { shape: 'round' };
}

let donut = Donut();
Enter fullscreen mode Exit fullscreen mode

You’d want all donuts to share a prototype with some shared methods. However, manually adding __proto__ to every object looks gross:

function Donut() {
  return { shape: 'round' };
}

let donutProto = {
  eat() {
    console.log('Nom nom nom');
  }
};

let donut1 = Donut();
donut1.__proto__ = donutProto;
let donut2 = Donut();
donut2.__proto__ = donutProto;

donut1.eat();
donut2.eat();
Enter fullscreen mode Exit fullscreen mode

As a shortcut, adding .prototype on the function itself and adding new before your function calls would automatically attach the __proto__:

function Donut() {
  return { shape: 'round' };
}
Donut.prototype = {
  eat() {
    console.log('Nom nom nom');
  }
};

let donut1 = new Donut(); // __proto__: Donut.prototype
let donut2 = new Donut(); // __proto__: Donut.prototype

donut1.eat();
donut2.eat();
Enter fullscreen mode Exit fullscreen mode

Now this pattern has mostly fallen into obscurity, but you can still see prototype property on the built-in functions and even on classes. To conclude, a function’s prototype specifies the __proto__ of the objects created by calling that function with a new keyword.

Discussion (0)