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.
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
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
Boolean: Represents a logical entity and can have only two values: true or false.
let isActive = true;
let hasPermission = false;
null: Represents the intentional absence of any object value. It's a primitive value.
let user = null; // Variable is explicitly empty
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
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)
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.
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).
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.