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.