JavaScript  

Chapter 2: JavaScript Core Syntax and Basic Data Types

Now that you have your development environment set up and understand the basics of how JavaScript runs, it's time to dive into the fundamental building blocks of the language: its core syntax and the various types of data it can handle. Understanding these concepts is crucial for writing any meaningful JavaScript code.

2.1. Statements, Expressions, and Comments

Before we get into variables, let's clarify some basic terminology.

  • Statements: A statement is a complete instruction that performs an action. In JavaScript, statements typically end with a semicolon (;). While semicolons are often optional in modern JavaScript due to Automatic Semicolon Insertion (ASI), it's a good practice to use them for clarity and to prevent potential bugs, especially for beginners.
    // This is a statement
    console.log("Hello World");
    
    // This is another statement
    let message = "Welcome!";
  • Expressions: An expression is a piece of code that evaluates to a single value. Expressions can be part of statements.
    // The number 10 is an expression
    10;
    
    // "Hello" + " World" is an expression that evaluates to "Hello World"
    let greeting = "Hello" + " World";
    
    // x > 5 is an expression that evaluates to true or false
    let x = 7;
    let isGreaterThanFive = x > 5;
  • Comments: Comments are non-executable lines of code that you add to explain your code to yourself or other developers. They are ignored by the JavaScript engine. Good commenting practices make your code more readable and maintainable.
    • Single-line comments: Start with two forward slashes (//).
      // This is a single-line comment
      let age = 30; // This comment explains the 'age' variable
    • Multi-line comments: Start with /* and end with */.
      /*
      This is a multi-line comment.
      It can span across several lines
      to provide more detailed explanations.
      */
      let firstName = "John";
      let lastName = "Doe";

2.2. Variables: var, let, const

Variables are containers for storing data values. In JavaScript, you declare variables using keywords. Historically, var was the only way, but with ES6 (ECMAScript 2015), let and const were introduced, offering better ways to manage variable scope and mutability.

var (Legacy Declaration)

  • Function-scoped: Variables declared with var are accessible throughout the function they are declared in, regardless of block scope (like if statements or for loops).
  • Can be re-declared and re-assigned: You can declare the same var variable multiple times in the same scope without an error.
  • Hoisted: var declarations are "hoisted" to the top of their function or global scope. This means you can use a var variable before it's declared in your code, but its value will be undefined until the actual assignment line is reached.
    console.log(carName); // Output: undefined (due to hoisting)
    var carName = "Volvo";
    console.log(carName); // Output: Volvo
    
    var carName = "BMW"; // No error, re-declared
    console.log(carName); // Output: BMW
    
    if (true) {
        var fruit = "Apple";
        console.log(fruit); // Output: Apple
    }
    console.log(fruit); // Output: Apple (var is function-scoped, not block-scoped)
    Due to its hoisting behavior and lack of block-scoping, var can lead to unexpected behavior and is generally discouraged in modern JavaScript in favor of let and const.

let (Modern Declaration)

  • Block-scoped: Variables declared with let are only accessible within the block ({}) where they are defined. This includes if blocks, for loops, while loops, and functions.
  • Cannot be re-declared: You cannot declare the same let variable twice in the same scope.
  • Can be re-assigned: You can change the value of a let variable after it's declared.
  • Not hoisted (in a practical sense): While let declarations are technically hoisted, they are in a "temporal dead zone" until their declaration line is executed. This means trying to access them before declaration will result in a ReferenceError.
    // console.log(city); // ReferenceError: Cannot access 'city' before initialization
    let city = "New York";
    console.log(city); // Output: New York
    
    // let city = "London"; // SyntaxError: Identifier 'city' has already been declared
    
    city = "Paris"; // Re-assigned, no error
    console.log(city); // Output: Paris
    
    if (true) {
        let animal = "Dog";
        console.log(animal); // Output: Dog
    }
    // console.log(animal); // ReferenceError: animal is not defined (animal is block-scoped)
    ```let` is the preferred choice when you need a variable whose value might change.

const (Constant Declaration)

  • Block-scoped: Like let, const variables are block-scoped.
  • strong>Cannot be re-declared: You cannot declare the same const variable twice in the same scope.
  • Cannot be re-assigned: Once a const variable is assigned a value, it cannot be re-assigned to a different value. It stands for "constant."
  • Must be initialized: You must assign a value to a const variable at the time of its declaration.
  • Not hoisted (in a practical sense): Similar to let, const variables are in a "temporal dead zone" until declared.
    // const PI; // SyntaxError: Missing initializer in const declaration
    const PI = 3.14159;
    console.log(PI); // Output: 3.14159
    
    // PI = 3.14; // TypeError: Assignment to constant variable.
    // const PI = 3.1415926535; // SyntaxError: Identifier 'PI' has already been declared
    
    const userName = "Alice";
    // userName = "Bob"; // TypeError: Assignment to constant variable.
    
    if (true) {
        const GRAVITY = 9.8;
        console.log(GRAVITY); // Output: 9.8
    }
    // console.log(GRAVITY); // ReferenceError: GRAVITY is not defined
    Important Note on const with Objects and Arrays: While it const prevents re-assignment of the variable itself, it does not make the contents of an object or array immutable. You can still modify the properties of a const object or the elements of a const array.
    const person = {
        name: "Jane",
        age: 25
    };
    person.age = 26; // This is allowed!
    console.log(person); // Output: { name: 'Jane', age: 26 }
    
    // person = { name: "John" }; // This is NOT allowed (re-assignment of 'person')
    
    const numbers = [1, 2, 3];
    numbers.push(4); // This is allowed!
    console.log(numbers); // Output: [1, 2, 3, 4]
    
    // numbers = [5, 6]; // This is NOT allowed (re-assignment of 'numbers')

    Best Practice: In modern JavaScript, the general recommendation is:

    • Use const by default. If you know a variable's value won't change, const provides better code clarity and helps prevent accidental re-assignments.
    • Use let if you know the variable's value needs to be re-assigned later in its scope.
    • Avoid var unless you are working with older codebases that explicitly use it.

2.3. Data Types: Primitives and Non-primitives

JavaScript variables can hold many different types of data. These types are broadly categorized into Primitive and Non-Primitive (or Object) types.

Primitive Data Types

Primitive values are immutable (cannot be changed after creation) and are passed by value.

  1. String: Represents textual data. Enclosed in single quotes (' '), double quotes (" "), or backticks (` for template literals, which we'll cover in ES6+ features).
    let greeting = "Hello, world!";
    let name = 'Alice';
    let message = `You are ${name}.`; // Template literal
  2. Number: Represents both integer and floating-point numbers.
    let age = 30; // Integer
    let price = 99.99; // Floating-point
    let temperature = -5;
    let bigNumber = 1e6; // 1 * 10^6 = 1000000
    
    // Special Number values:
    let result = 0 / 0; // NaN (Not a Number)
    let infinity = 1 / 0; // Infinity
  3. Boolean: Represents a logical entity and can have only two values: true or false.
    let isActive = true;
    let hasPermission = false;
  4. null: Represents the intentional absence of any object value. It's a primitive value.
    let user = null; // Variable is explicitly empty
  5. undefined: Represents a variable that has been declared but has not yet been assigned a value.
    let quantity; // quantity is undefined
    console.log(quantity); // Output: undefined
  6. Symbol (ES6): Represents a unique identifier. Often used as keys for object properties to avoid name clashes.
    const id1 = Symbol('id');
    const id2 = Symbol('id');
    console.log(id1 === id2); // Output: false (each Symbol is unique)
  7. BigInt (ES11): Represents whole numbers larger than 2^53 - 1 (the largest number Number can reliably represent). You add n to the end of an integer to make it a BigInt.
    const veryBigNumber = 9007199254740991n;
    const anotherBigInt = BigInt(12345678901234567890);

Non-Primitive Data Type (Object)

Non-primitive values are mutable and are passed by reference.

  1. Object: The most complex data type. Everything else in JavaScript (arrays, functions, dates, regular expressions) is technically an object or behaves like one. Objects are collections of key-value pairs (properties).
    // An object literal
    let person = {
        firstName: "John",
        lastName: "Doe",
        age: 30,
        isStudent: false,
        hobbies: ["reading", "hiking"] // An array inside an object
    };
    
    // An array (which is a special type of object)
    let colors = ["red", "green", "blue"];
    
    // A function (which is also a special type of object)
    function greet() {
        console.log("Hello!");
    }

    We will dive much deeper into objects and arrays in Chapter 7.

2.4. Type Coercion and Type Checking

JavaScript is a dynamically typed language, meaning you don't declare the type of a variable explicitly, and a variable can hold different types of values over its lifetime. This flexibility can sometimes lead to unexpected behavior due to type coercion.

Type Coercion

Type coercion is JavaScript's automatic conversion of values from one data type to another. This happens implicitly (automatically by JavaScript) or explicitly (when you intentionally convert types).

  • Implicit Coercion (Automatic)
    console.log(10 + "5"); // Output: "105" (Number 10 is coerced to String "10")
    console.log("10" - 5); // Output: 5 (String "10" is coerced to Number 10)
    console.log(true + 1); // Output: 2 (Boolean true is coerced to Number 1)
    console.log("5" * "2"); // Output: 10 (Both strings coerced to numbers)
    console.log(null == undefined); // Output: true (Loose equality allows coercion)
    console.log(0 == false); // Output: true
    console.log("" == false); // Output: true
    Implicit coercion can be a source of bugs, which is why strict equality (===) is highly recommended over loose equality (==).
  • Explicit Coercion (Manual): You can explicitly convert types using built-in functions or constructors.
    • To Number
      let strNum = "123";
      let num = Number(strNum); // num is 123 (number)
      let parsedInt = parseInt("45.67"); // parsedInt is 45
      let parsedFloat = parseFloat("45.67"); // parsedFloat is 45.67
    • To String
      let num = 123;
      let str = String(num); // str is "123" (string)
      let bool = true;
      let strBool = String(bool); // strBool is "true"
    • To Boolean
      let value = "Hello";
      let boolValue = Boolean(value); // boolValue is true
      let zero = 0;
      let boolZero = Boolean(zero); // boolZero is false
      
      // Falsy values: false, 0, -0, "", null, undefined, NaN
      // All other values are truthy.

Type Checking (typeof and instanceof)

To determine the type of a variable or value, you can use the typeof operator or, for objects, the instanceof operator.

  • typeof Operator: Returns a string indicating the type of the unevaluated operand.
    console.log(typeof "Hello");      // Output: "string"
    console.log(typeof 123);          // Output: "number"
    console.log(typeof true);         // Output: "boolean"
    console.log(typeof undefined);    // Output: "undefined"
    console.log(typeof Symbol('id')); // Output: "symbol"
    console.log(typeof 123n);         // Output: "bigint"
    
    console.log(typeof null);         // Output: "object" (This is a long-standing bug in JavaScript!)
    console.log(typeof {});           // Output: "object"
    console.log(typeof []);           // Output: "object"
    console.log(typeof function(){}); // Output: "function" (Functions are a special type of object)
    Be aware of the typeof null quirk!
  • instanceof Operator: Used to check if an object is an instance of a particular class or constructor function. It works for objects, arrays, functions, and custom classes.
    let myArr = [1, 2, 3];
    let myObj = { a: 1 };
    
    console.log(myArr instanceof Array);  // Output: true
    console.log(myArr instanceof Object); // Output: true (Arrays are objects)
    console.log(myObj instanceof Object); // Output: true
    console.log(myArr instanceof String); // Output: false
    
    function Car(make) {
        this.make = make;
    }
    let myCar = new Car("Toyota");
    console.log(myCar instanceof Car);    // Output: true
    console.log(myCar instanceof Object); // Output: true
    To check if a value is an array, Array.isArray() is generally more reliable than instanceof Array because instanceof can have issues with multiple JavaScript contexts (e.g., iframes).
    console.log(Array.isArray(myArr)); // Output: true
    console.log(Array.isArray(myObj)); // Output: false

Understanding variables and data types is foundational. In the next chapter, we'll learn how to perform operations on these data types using various operators.