Determining variables types in JavaScript is an important skill for any developer working with the language. Unlike traditional strongly-typed languages, JavaScript employs dynamic typing – understanding this system and its implications is key to writing stable JavaScript programs.

In this comprehensive guide, we will deep dive into everything you need to know about JavaScript variable types – how internal types work, why they matter, different ways to check types at runtime, common issues that arise, and potential solutions:

Section 1: JavaScript Typing System

To start off, we need to understand JavaScript‘s dynamic typing system and why it works the way it does.

History of JavaScript and Dynamic Typing

When JavaScript was being standardized in the 90‘s, the intent was to make it approachable for beginner developers from a non-traditional coding background. Hence, JavaScript does not enforce any variable type declarations unlike languages like Java or C#.

This design decision resulted in dynamic typing – a system where variables can hold values of any type without explicit type casting. Their "type" can change at runtime seamlessly:

let dynamicVar = "I am a string"; // String type
dynamicVar = 12; // Changed to number type!

So JavaScript determines or coerces types based on assignment and usage rather than declarations. This makes coding easier but can also lead to hard-to-catch bugs due to wrong assumptions about types.

Typing Under the Hood

Behind the scenes, JavaScript engines like V8 actually have internal methods and hidden classes to keep track of dynamic variable types while the code is running.

Each value has an internal type tag updated by the engine tracking its type. For example, strings are marked differently from numbers internally.

So even if your code doesn‘t specify types, JavaScript works hard to categorize values correctly for optimized performance and operations.

Implications of Dynamic Typing

The flexibility of typing in JavaScript has some nice benefits:

  • Beginner-friendly syntax
  • Rapid development with less code
  • Easy to work with JSON data

However, the pitfall of this system is assuming types rather than explicitly checking:

function sum(a, b) {
  return a + b; // Error if params passed are not numbers!
}

This function lacks type validation and can break in production if a string is passed accidentally instead of a number.

To summarize, JavaScript‘s dynamic typing system allows great flexibility but also requires manual type checking to prevent bugs. Understanding this balance is key.

Section 2: Checking Variable Type

Now that we understand how JavaScript types work behind the scenes, let‘s explore different ways to check types in your code.

We will cover the following topics:

So let‘s get started!

The typeof Operator

The typeof operator is the easiest way to see the type tag associated with a value in JavaScript:

typeof "JavaScript"; // returns "string"
typeof 12; // returns "number" 
typeof x; // returns "undefined" if x is undefined

Here is a handy table of the different values that typeof returns:

Type Result
Undefined ‘undefined‘
Boolean ‘boolean‘
Number ‘number‘
String ‘string‘
Function object ‘function‘
Any other object ‘object‘

However, typeof has three major quirks to watch out for:

1. null:

typeof null; // returns ‘object‘ 

2. New Boolean() object wrapper:

typeof new Boolean(true); // ‘object‘

3. Host objects:

typeof window; // ‘object‘ 
typeof document.write; // ‘function‘

So combine typeof checks with strict equality operator for robust type checking.

Object.prototype.toString()

In addition to typeof, you can use the toString() method from Object.prototype to find the internal class associated with a particular object:

Object.prototype.toString.call([]) // String: "[object Array]" 

Object.prototype.toString.call(new Date) // String: "[object Date]"

Unlike typeof, toString():

  • Returns more precise class names for objects
  • Can detect null type properly

However, for primitives we need to wrap values in object form before checking:

Object.prototype.toString.call(1); // "[object Number]"

So toString() offers a robust type checking for objects, null and function types.

instanceof vs Duck Typing

The instanceof operator is useful for checking if an object belongs to a certain class:

let str = "hi"; 

str instanceof String; // true

This checks if str inherits properties from String.prototype internally.

Duck typing takes this further by checking if object has particular methods or properties rather than inheritance:

function isArray(value) {
  return Array.isArray 
    ? Array.isArray(value) 
    : value && typeof value === ‘object‘ && typeof value.length === ‘number‘; 
}

Here we check if value has .length property to determine if it array-like.

So instanceof and duck typing both rely on existence of properties rather than typeof.

Strict Equality Checks

When working with typeof results, always compare values using strict equality checks:

function isNumber(value) {
  return typeof value === "number"; // strict equality
}

isNumber(5); // true

Strict equality prevents unexpected coercion during comparisons and returns accurate results.

Type Coercion Issues

One key area where types pose an issue is during type coercion.

Type coercion means JS tries to convert between types automatically in certain situations.

For example, this code causes an unintended concatenation:

var x = 5;
var y = "3";

x + y; // "53" instead of 8 due to coercion

Here JS converts x to a string first before appending strings.

These implicit conversions can cause strange breakages in code. Over 34% of all JavaScript bugs are related to incorrect handling of data types according to a 2022 survey.

So validate function arguments and parameters explicitly:

function sum(x, y) {

  //add explicit checks
  if (typeof x !== "number") {  
    throw new Error("x must be number");
  }

  if (typeof y !== "number") {
    throw new Error("y must be number");  
  }

  return x + y; 
}

Proactively avoiding type issues is important for writing robust JavaScript code.

Third Party Type Checkers

There are also a few validation libraries like Joi and Yup that provide additional ways to check types in JavaScript:

import Joi from "joi";

const schema = Joi.object({
  name: Joi.string().required(), 
  age: Joi.number().required()   
});

schema.validate({ name: "John", age: "invalid" }); // Errors

While verbose, these protect against bugs by validating objects and function arguments based on type schemas.

I‘d recommend considering third party type checking on large codebases and teams collaborating on a JavaScript project together.

Section 3: Typing in JavaScript – Best Practices

Now that we have seen different ways to check for types in JavaScript, let‘s discuss some best practices:

Validate Function Arguments and Return Types

Add basic typeof checks to validate values passed to functions:

function parseJSON(str) {
  if (typeof str !== "string") {
    throw "Must pass string argument!"; 
  }

  // rest of logic
}

Also validate return value matches expected type before usage to prevent errors.

Prefer Strict Equality Over Coercion

When comparing typeof results, use strict equality operator:

if (typeof x === "string") {
  // do something 
}

The strict check doesn‘t attempt to coerce so safer.

Check Types From External Inputs

Validate query parameters, network responses, input data etc. before usage. Sanitize and normalize when possible.

Use Type Assertion Functions

Standardize reusable checks for types like numbers, strings etc:

function isNumber(val) {
  return typeof val === "number" && !Number.isNaN(val);
}

This way type checks are consistent across code.

Consider Typescript for Large Projects

For big codebases with extensive type handling, consider moving to Typescript – a typed superset of JavaScript that compiles down to plain JavaScript.

Typescript allows declaring interfaces with value types that are checked during compilation:

interface User {
  name: string;
  id: number; 
}

function getAdmin(user: User) {
  // user checked for matching types 
}

This moves error discovery to build time rather than runtime. Typescript prevents nearly 30% type related bugs according to research.

I‘d recommendstart with pure JavaScript, and migrate code to Typescript once codebase size and team expands. This allows you leverage additional type safety.

Conclusion

Determining types dynamically at runtime is a key aspect of working with loosely typed JavaScript code. Mastering tools like typeof and instanceof along with understanding coercion helps avoid tricky type-related bugs.

Hopefully this guide gave you a comprehensive overview into working with JavaScript types – their internal representation, checking types at runtime, issues like coercion, and best practices followed by experts.

Let me know if you have any other questions!

Similar Posts