Types in JS

How can we add another protection to our JS application?

If you want to know more about typing in JS, I suggest you check out one of my blogs, where I've covered in depth every types in JS( String, Object, Collections)

Typeof vs vs. instance of

typeof

Per the MDN documentation, typeof is a unary operator that returns a string indicating the type of the unevaluated operand.

In the case of string primitives and string objects, typeof returns the following:

const a = "I'm a string primitive";
const b = new String("I'm a String Object");

typeof a; --> returns 'string'
typeof b; --> returns 'object'
💡
Only works with primitive value (string, number , boolean, to be precise)

Instanceof

is a binary operator, accepting an object and a constructor. It returns a boolean indicating whether or not the object has the given constructor in its prototype chain.

const a = "I'm a string primitive";
const b = new String("I'm a String Object");

a instanceof String; --> returns false
b instanceof String; --> returns true

DOM instance of ???

element instanceof EventTarget;
element instanceof Node;
element instanceof Element;
element instanceof HTMLElement;

Undefined and null💡

💡
To check undefined and null values, we have to use the ===

This is the reason why we use JSON.stringify it will eleminate the undefined value because it not an object like null. It kind of makes sense.

Array and Object

  • Array can be checked using Array.isArray([]);

  • Object can be checked using myvar instanceof Object;

Thumb Rule:

  • For checking primitive types, use typeof

  • Null can be checked as myvar === null

  • Undefined can be checked as myvar === undefined

  • An array can be checked using Array.isArray([])

  • Object can be checked using myvar instanceof Object

  • Constructor column can be utilized in a similar fashion as ({}).constructor or ([]).constructor

Type coercion

1 == "1"

// True

In simple term, type coersion means a language converts a certain type to a certain type

Do all languages have type coercion?

Yes, we do, because we need to transform some types into others. Even when we write some JS behind the scenes, it translates to a bunch of 0 and 1 :) JS is a special language that, by nature, is a little bit too much

== (Compare two different values, if they have different types
translate it to the same type)
=== (Compare two values but don't translate it please)

In JS world, the standard of the industry is to always use ===
because == is very blur

If statement with coersion

if(1) {
   console.log(5);
}
This is so tricky, JS coersion this to boolean because it
guess that in if else statement we always wants to have boolean value
💡
This is so confusing and is mainly the main bug in our application

💡
I would say that if else coersion in JS works really well with primitive value("",0,false,null,undefined) but not with objects. This is exactly how the lodash compact function works

There're some quirks with if-else statement I'd like to point out

  • [] is not a true value

  • {} is not a true value

The in type guard

The in type guard checks if an object has a particular property, using that to differentiate between different types. It usually returns a boolean, which indicates if the property exists in that object. It is used for its narrowing features as well as to check for browser support.

The basic syntax for the in type guard is below

"house" in { name: "test", house: { parts: "door" } }; // => true
"house" in { name: "test", house: { parts: "windows" } }; // => true
"house" in { name: "test", house: { parts: "roof" } }; // => true
"house" in { name: "test" }; // => false
"house" in { name: "test", house: undefined }; // => true

Nullish coalescing operator?? (ES2020)

💡
Nullist = undefined || null;

let myPower = {
    mind : {
       level:"";
    }
}

let mindPower = myPower?.mind?.level || "no power"; // no power

// What if we want to only check for undefined and null
mindPower = myPower?.mind?.level ?? "no power"

typeof in TS

//Bassed on the context
type T1 = typeof p; // type is Person
type T2 = typeof email; // type is (p:Person,subject:string)=>Response

const v1 = typeof p; // value is an "object"
const v2 = typeof email; // value is "function"
💡
TS type is much more complicated and oftentimes provides what we want

Any vs unknown

In general, it's recommended to use 'unknown' instead of 'any' whenever possible, because it helps to catch type errors at compile time and ensures that your code is type-safe.

type AdminUser = {
    data:string;
}

// Runtime JS + Static analysis with TS
function isAdminUser(arg:any)arg is AdminUser{
      if(arg instanceof Object) {
           return "data" in arg;
      }
      return false;
}

//with promise after we run .json() it returns any
const goodUser:unknown = await response.json();
//if we don't type-guard it, there's a chance that it will lead to error
if(isAdminUser(goodUser) {
    goodUser.data; //could be error if server returns undefined;
}

User-defined Defined guards

As we know, JS has type guard checking like we listed above, but it's too simple in real life when we work with complicated data. For those cases, we can define type guard functions. These are just functions that return someArgumentName is SomeType

type Foo = {
    a:string;
}

// Open the gate as wide as possible with any, close the gate with is
function isFoo(arg:any): arg is Foo {
    return (typeof arg.a === "string" && arg.a!=="");
}

function doStuff(arg?:Foo){ 
    if(isFoo(arg)) {
        //do something
    }
}
💡
Type-guard is especially useful when we're dealing with data from a server. Sometimes it's there, sometimes it's not there, so best practice is to make the assumption that if it's not there, we should handle it in different way

Type-checking at runtime

//Type's checking functions
function isITeam(arg:any):arg is Iteam {
    return (
        typeof arg.iconUrl === "string" &&
        typeof arg.name === "string" &&
        typeof arg.id === "string" &&
        Array.isArray(arg.channels);
    )
}

//High-order-functions
function assertIsTypedArray<T>(arg:any,
         check:(val:any)=>assert arg is T[]) {
    if(!Array.isArray(val) {
        throw new Error('Not an array');
    }
    if(arg.some((item)=>!check(item)) {
        throw new Error('Violators found');
    }
}

// Use-case
cachedAllItems = apiCall('teams').then((rawData)=> {
     assertIsTypedArray<Iteam>(rawData,isITeam);
     return rawData;
})

Type narrowing

We can narrow the type from a broad type to a narrower one to handle some specific logics depending on its type

const el = document.getElementById('foo'); null || HTMLElement
if(!el) {
    el.innerHTML = "Party Time!";
} else {
    alert('No elements was found');
}

Narrowing types

Instanceof

function contains(text:string, search:string | RegExp) {
    if(search instanceof RegExp) 
        { search.exec(text); }
    text.include(search);
}

In

interface A = { x:number }
interface B = { y:number }
function pickAB(ab: A| B ) {
    if("x" in ab) { return ab.x }
    if("y" in ab) { return ab.y }
}

Array.isArray

function contains(text:string, terms:string | Array<string>){
    if(Array.isArray(terms) { term.map(..) //Array methods }
    terms.startWith('H');
}

Be-careful with null

const el = document.getElementById("foo"); //HTMLElement | null
if(typeof el === "object") {
     //null is an object
}


function foo(x?:number | string | null) {
    if(!x) {
        x; //Type is string | number | null | undefined
    }
}

False primitive values

function foo(x?:number | string | null) {
    if(!x) {
        x; //Type is string | number | null | undefined
    }
}
//because "" and 0 are both falsy values

User-defined-type-guard for DOM

Like mentioned above, with complicated types, we can create our own function to do the checking for us :)

function isInputElement(element:any):element is HTMLInputElement {
   return 'value' in element;
   //Smart move only value exists in HTMLInput element
}

function getElementContent(el:HTMLElement) {
  if(isInputElement(element)) {
      el.value; // HTMLInputElement;
  } 
  else {
      el.textContent; // HTMLElement
  }
}