Data Types
JavaScript, like most programming languages, has two types of data: Primitive Data Types and Reference Data Types. Understanding the difference between these two categories is crucial because it influences how variables are stored and manipulated in memory. Let’s break them down.
Data Types in JavaScript
JavaScript has two main categories of data types:
- Primitive Types: Immutable and stored by value.
- Reference Types: Mutable and stored by reference.
Primitive Data Types
Primitives are immutable and copied by value, meaning a copy of the value is passed when assigned or passed to a function.
List of Primitive Types:
-
undefined – Variable declared but not initialized.
let x; console.log(x); // undefined
-
null – Represents intentional absence of a value.
let y = null; console.log(y); // null
-
boolean – Logical values:
true
orfalse
.let isTrue = true; let isFalse = false;
-
number – Includes integers and floats.
let age = 25; let pi = 3.14;
-
string – A sequence of characters.
let greeting = "Hello!";
-
symbol – Unique identifiers.
let sym1 = Symbol('id'); let sym2 = Symbol('id'); console.log(sym1 === sym2); // false
-
bigint – For large integers beyond
Number.MAX_SAFE_INTEGER
.let largeNum = 12345678901234567890n;
Key Behavior:
- Pass by Value: A new copy is created.
let a = 10; let b = a; // b is a copy of a b = 20; console.log(a); // 10 console.log(b); // 20
Reference Data Types
Reference types are mutable and copied by reference. This means they store a reference (address) to the object in memory. Changes to one variable affect all references.
List of Reference Types:
-
Objects – Key-value pairs.
let person = { name: "Alice", age: 30 };
-
Arrays – Ordered collections.
let arr = [1, 2, 3];
-
Functions – Reusable blocks of code.
function greet() { return "Hello!"; }
Key Behavior:
- Pass by Reference: The memory reference is copied, not the actual object.
let obj1 = { name: "Alice" }; let obj2 = obj1; // obj2 references obj1 obj2.name = "Bob"; console.log(obj1.name); // "Bob" (both point to the same object)
Comparison: Primitive vs Reference Types
Aspect | Primitive Types | Reference Types |
---|---|---|
Mutability | Immutable | Mutable |
Stored | By Value | By Reference |
Copying | Copies value | Copies reference |
Examples | number , string , boolean | object , array , function |
Conclusion
- Primitive types are simple, immutable, and copied by value.
- Reference types are complex, mutable, and copied by reference. Understanding the difference between the two helps in managing data more effectively and avoiding unintended mutations.
Shallow vs Deep Copy
Shallow Copy
A shallow copy of an object only copies the top-level properties. If the original object contains nested objects or arrays, the shallow copy will reference the same nested objects.
How to Create a Shallow Copy
-
Using
Object.assign()
const originalObject = { name: "Alice", age: 28, contact: { email: "alice@example.com", phone: "123-456-7890" } }; const shallowCopy1 = Object.assign({}, originalObject);
-
Using the Spread Operator
const shallowCopy2 = { ...originalObject };
Example:
const userProfile = {
name: "Alice",
contact: { email: "alice@example.com" },
addresses: [{ city: "Los Angeles" }]
};
// Create shallow copy
const shallowCopy = { ...userProfile };
// Modify shallow copy
shallowCopy.contact.email = "newemail@example.com";
shallowCopy.addresses[0].city = "San Diego";
console.log("Original User Profile:", userProfile);
console.log("Shallow Copy:", shallowCopy);
Output:
Original User Profile:
{
name: 'Alice',
contact: { email: 'newemail@example.com' },
addresses: [{ city: 'San Diego' }]
}
Shallow Copy:
{
name: 'Alice',
contact: { email: 'newemail@example.com' },
addresses: [{ city: 'San Diego' }]
}
Deep Copy
A deep copy duplicates all levels of the object, including nested objects and arrays. The copied object and the original object do not share any references.
How to Create a Deep Copy
-
Using
JSON.parse()
andJSON.stringify()
const deepCopy = JSON.parse(JSON.stringify(originalObject));
-
Using a Recursive Function
function deepCopy(obj) { if (obj === null || typeof obj !== 'object') return obj; if (Array.isArray(obj)) return obj.map(deepCopy); const copy = {}; for (const key in obj) { if (obj.hasOwnProperty(key)) { copy[key] = deepCopy(obj[key]); } } return copy; }
Example:
const userProfile = {
name: "Alice",
contact: { email: "alice@example.com" },
addresses: [{ city: "Los Angeles" }]
};
// Create deep copy
const deepCopy = JSON.parse(JSON.stringify(userProfile));
// Modify deep copy
deepCopy.contact.email = "anothernewemail@example.com";
deepCopy.addresses[0].city = "Seattle";
console.log("Original User Profile:", userProfile);
console.log("Deep Copy:", deepCopy);
Output:
Original User Profile:
{
name: 'Alice',
contact: { email: 'alice@example.com' },
addresses: [{ city: 'Los Angeles' }]
}
Deep Copy:
{
name: 'Alice',
contact: { email: 'anothernewemail@example.com' },
addresses: [{ city: 'Seattle' }]
}
Summary
- Shallow Copy: Copies only the top level, with nested objects being referenced. Changes to nested objects affect both the original and copied objects.
- Deep Copy: Duplicates all levels of an object, making the copied object fully independent of the original. Changes to the deep copy do not affect the original object.
Type Coercion
Type coercion is the process of converting one data type to another. JavaScript is a loosely typed language, meaning it allows implicit and explicit conversions between different data types.
Types of Type Coercion:
- Implicit Coercion: JavaScript automatically converts types when an operation involves mismatched types.
- Explicit Coercion: The developer manually converts one data type to another.
Implicit Type Coercion
JavaScript tries to automatically convert types when it encounters expressions involving different data types. Implicit coercion usually happens in operations like addition, equality comparisons (==
), and conditionals.
Examples of Implicit Coercion:
-
String + Number: JavaScript converts the number to a string and concatenates.
let result = "5" + 10; console.log(result); // "510"
-
String - Number: JavaScript converts the string to a number for subtraction.
let result = "5" - 2; console.log(result); // 3
-
Boolean + Number:
true
is converted to1
, andfalse
is converted to0
.let result = true + 2; console.log(result); // 3
-
null + Number:
null
is converted to0
.let result = null + 5; console.log(result); // 5
-
undefined + Number:
undefined
becomesNaN
(Not-a-Number).let result = undefined + 3; console.log(result); // NaN
Common Implicit Coercion in Conditional Statements:
- Falsy Values:
false
,0
,""
(empty string),null
,undefined
,NaN
are all falsy. - Truthy Values: Everything else (non-zero numbers, non-empty strings, objects) is considered truthy.
if ("") {
console.log("This won't execute, because '' is falsy");
}
if ("Hello") {
console.log("This will execute, because 'Hello' is truthy");
}
Explicit Type Coercion
Explicit coercion is when you manually convert a value to another type using JavaScript methods.
Examples of Explicit Coercion:
-
String to Number: Using
Number()
,parseInt()
, or the unary+
operator.let str = "123"; let num = Number(str); // 123 let num2 = +str; // 123
-
Number to String: Using
String()
or.toString()
.let num = 123; let str = String(num); // "123" let str2 = num.toString(); // "123"
-
Boolean to Number:
true
becomes1
, andfalse
becomes0
.let bool = true; let num = Number(bool); // 1
-
Number to Boolean: Any non-zero number becomes
true
;0
becomesfalse
.let num = 10; let bool = Boolean(num); // true let zeroBool = Boolean(0); // false
Example: Convert between types explicitly
let strNum = "42";
console.log(typeof strNum); // "string"
let convertedNum = Number(strNum);
console.log(typeof convertedNum); // "number"
Equality Comparisons
One of the most common areas where type coercion occurs is equality comparisons. JavaScript provides two types of equality operators:
==
(Loose Equality): Allows type coercion. It converts the operands to the same type before comparing them.===
(Strict Equality): Does not allow type coercion. It checks both the value and the type.
Loose Equality (==) Example:
-
In the case of
==
, JavaScript attempts to coerce types to make the comparison succeed.console.log(1 == "1"); // true (string "1" is coerced to number 1) console.log(true == 1); // true (true is coerced to 1) console.log(null == undefined); // true (special case) console.log([] == false); // true (empty array is coerced to false)
-
Explanation:
1 == "1"
: The string"1"
is coerced into a number1
for comparison.true == 1
:true
is coerced to1
, and the comparison succeeds.
Strict Equality (===) Example:
-
With
===
, type coercion is not allowed, and both the value and type must be identical.console.log(1 === "1"); // false (no coercion, number vs string) console.log(true === 1); // false (boolean vs number) console.log([] === false); // false (object vs boolean)
-
Explanation:
1 === "1"
: This isfalse
because the types are different (number
vsstring
).true === 1
: This isfalse
becauseboolean
is not equal tonumber
.
Type Coercion Rules
-
Number and String: The string is converted to a number.
console.log(2 == "2"); // true (string "2" is coerced to number 2)
-
Boolean and Number:
true
becomes1
, andfalse
becomes0
.console.log(0 == false); // true (false coerced to 0)
-
null
andundefined
: These are only loosely equal to each other and not to anything else.console.log(null == undefined); // true console.log(null == 0); // false
-
Objects: Objects are not coerced; they are compared by reference.
let obj1 = {}; let obj2 = {}; console.log(obj1 == obj2); // false (different references)
Example of Type Coercion
// Example 1: Implicit Type Coercion with `==`
console.log('5' == 5);
// Output: true
// Explanation: The string '5' is coerced to the number 5, so they are equal.
console.log('5' == '5');
// Output: true
// Explanation: Both values are strings and are equal.
console.log(0 == false);
// Output: true
// Explanation: The number 0 is coerced to false, so they are considered equal.
console.log(null == undefined);
// Output: true
// Explanation: `null` and `undefined` are considered equal when using `==`.
// Example 2: Strict Equality with `===`
console.log('5' === 5);
// Output: false
// Explanation: The types are different (string vs number), so they are not strictly equal.
console.log('5' === '5');
// Output: true
// Explanation: Both values are strings and are equal.
console.log(0 === false);
// Output: false
// Explanation: The types are different (number vs boolean), so they are not strictly equal.
console.log(null === undefined);
// Output: false
// Explanation: The types are different, so they are not strictly equal.
// Example 3: Implicit Coercion in Arithmetic
console.log('10' + 5);
// Output: '105'
// Explanation: The number 5 is coerced into a string, resulting in concatenation.
console.log('10' - 5);
// Output: 5
// Explanation: The string '10' is coerced into the number 10, so the subtraction yields 5.
console.log('10' * '2');
// Output: 20
// Explanation: Both strings are coerced into numbers, so the multiplication yields 20.
console.log('10' / '2');
// Output: 5
// Explanation: Both strings are coerced into numbers, so the division yields 5.
// Example 4: Implicit Coercion with `==` and Arrays
console.log([] == false);
// Output: true
// Explanation: The empty array [] is coerced into an empty string "", and "" is loosely equal to false.
console.log([] == ![]);
// Output: true
// Explanation: ![] is false, and the empty array [] is coerced into an empty string "", which is loosely equal to false.
console.log([] == [] + []);
// Output: true
// Explanation: [] + [] results in an empty string "", so [] == "" is true.
console.log([] == [1] - [1]);
// Output: false
// Explanation: [] - [1] results in -1, and [] == -1 is false.
// Example 5: Explicit Type Conversion
console.log(Number('5'));
// Output: 5
// Explanation: The string '5' is explicitly converted to the number 5.
console.log(String(5));
// Output: '5'
// Explanation: The number 5 is explicitly converted to the string '5'.
console.log(Boolean('0'));
// Output: true
// Explanation: Non-empty strings are truthy, so '0' evaluates to true.
console.log(Boolean(''));
// Output: false
// Explanation: Empty strings are falsy, so '' evaluates to false.
console.log(Boolean(0));
// Output: false
// Explanation: The number 0 is falsy.
console.log(Boolean(1));
// Output: true
// Explanation: The number 1 is truthy.
Summary
- Implicit coercion happens automatically when JavaScript tries to convert types during operations (e.g., string + number, boolean in conditions).
- Explicit coercion requires using specific methods to manually convert data types (e.g.,
Number()
,String()
). ==
allows type coercion during comparison, while===
does not, making it stricter and more predictable.
Scope in JavaScript
Understanding scope in JavaScript and the differences between var
, let
, and const
is crucial for writing predictable and bug-free code. Let’s break this down in detail with examples.
Global Scope
Variables declared outside of any function or block are in the global scope. They are accessible from anywhere in the code.
var globalVar = 'I am global';
function displayGlobal() {
console.log(globalVar); // Accessible here
}
displayGlobal();
console.log(globalVar); // Accessible here as well
Explanation:
globalVar
is declared in the global scope.- It can be accessed from anywhere in the code, including inside functions.
Function Scope
Variables declared inside a function are only accessible within that function. This is known as function scope.
function exampleFunction() {
var functionVar = 'I am inside a function';
console.log(functionVar); // Accessible here
}
exampleFunction();
console.log(functionVar); // Error: functionVar is not defined
Explanation:
functionVar
is declared insideexampleFunction
and is only accessible within that function.- Trying to access
functionVar
outsideexampleFunction
results in an error.
Block Scope
Variables declared within a block (e.g., inside curly braces {}
) are only accessible within that block. This scope is introduced with let
and const
.
if (true) {
let blockLet = 'I am inside a block';
const blockConst = 'I am also inside a block';
console.log(blockLet); // Accessible here
console.log(blockConst); // Accessible here
}
console.log(blockLet); // Error: blockLet is not defined
console.log(blockConst); // Error: blockConst is not defined
Explanation:
blockLet
andblockConst
are declared inside anif
block.- They are only accessible within that block, and trying to access them outside results in an error.
Differences Between var
, let
, and const
var
- Scope: Function scope.
- Hoisting: Variables declared with
var
are hoisted to the top of their function or global scope. They are initialized withundefined
. - Re-declaration: Variables declared with
var
can be re-declared and updated.
function testVar() {
console.log(a); // undefined (hoisted)
var a = 10;
console.log(a); // 10
}
testVar();
Explanation:
var a
is hoisted and initialized withundefined
before any code is executed.
let
- Scope: Block scope.
- Hoisting: Variables declared with
let
are hoisted but not initialized. They remain in a "temporal dead zone" from the start of the block until the declaration is encountered. - Re-declaration: Variables declared with
let
cannot be re-declared in the same scope.
function testLet() {
console.log(a); // ReferenceError: Cannot access 'a' before initialization
let a = 10;
console.log(a); // 10
}
testLet();
Explanation:
let a
is not initialized until its declaration is encountered, causing a ReferenceError if accessed before.
const
- Scope: Block scope.
- Hoisting: Variables declared with
const
are hoisted but not initialized, similar tolet
. They are also in the "temporal dead zone." - Re-declaration: Variables declared with
const
cannot be re-declared or reassigned.const
requires initialization at the time of declaration.
function testConst() {
console.log(a); // ReferenceError: Cannot access 'a' before initialization
const a = 10;
console.log(a); // 10
a = 20; // TypeError: Assignment to constant variable
}
testConst();
Explanation:
const a
must be initialized when declared and cannot be reassigned after initialization. Accessing it before initialization results in a ReferenceError.
Summary with Examples
Here is a consolidated example demonstrating all three:
var globalVar = 'I am global';
function scopeExample() {
var functionVar = 'I am function scoped';
if (true) {
let blockLet = 'I am block scoped with let';
const blockConst = 'I am block scoped with const';
console.log(globalVar); // Accessible
console.log(functionVar); // Accessible
console.log(blockLet); // Accessible
console.log(blockConst); // Accessible
}
console.log(blockLet); // Error: blockLet is not defined
console.log(blockConst); // Error: blockConst is not defined
}
scopeExample();
console.log(globalVar); // Accessible
console.log(functionVar); // Error: functionVar is not defined
Explanation:
- Global Scope:
globalVar
is accessible everywhere. - Function Scope:
functionVar
is only accessible withinscopeExample
. - Block Scope:
blockLet
andblockConst
are only accessible within theif
block. - Hoisting and Temporal Dead Zone:
var
variables are hoisted and initialized withundefined
, whilelet
andconst
are hoisted but not initialized, causing errors if accessed before their declaration.
This detailed explanation and examples should help clarify the different types of scope and the behavior of var
, let
, and const
in JavaScript.
Understanding Scoping with Tricky Examples
In JavaScript, understanding scope is crucial for writing predictable code. Let’s dive into some tricky examples to explore how variable declarations (var
, let
, const
) interact with different scopes and closures.
Example: Scoping and Shadowing
function outerFunction() {
var x = 10;
let y = 20;
const z = 30;
function innerFunction() {
var x = 40;
let y = 50;
const z = 60;
console.log('Inside innerFunction:');
console.log('x:', x); // Output: 40, `x` inside `innerFunction()` shadows the `x` from `outerFunction()`
console.log('y:', y); // Output: 50, `y` inside `innerFunction()` shadows the `y` from `outerFunction()`
console.log('z:', z); // Output: 60, `z` inside `innerFunction()` shadows the `z` from `outerFunction()`
}
innerFunction();
console.log('Inside outerFunction:');
console.log('x:', x); // Output: 10, `x` inside `outerFunction()` is unaffected by `x` inside `innerFunction()`
console.log('y:', y); // Output: 20, `y` inside `outerFunction()` is unaffected by `y` inside `innerFunction()`
console.log('z:', z); // Output: 30, `z` inside `outerFunction()` is unaffected by `z` inside `innerFunction()`
}
outerFunction();
Hoisting
Hoisting is a JavaScript behavior where variable and function declarations are moved to the top of their containing scope during the compilation phase. This allows variables and functions to be used before they are declared in the code.
Key Concepts
-
Variable Hoisting:
- Only the declaration is hoisted, not the initialization. Variables declared with
var
are initialized toundefined
until the actual assignment line is executed.
- Only the declaration is hoisted, not the initialization. Variables declared with
-
Function Hoisting:
- Entire function declarations are hoisted, allowing functions to be called before their declaration appears in the code.
-
Temporal Dead Zone (TDZ):
- For
let
andconst
, declarations are hoisted but are not initialized until their declaration line is reached. Accessing these variables before their declaration results in aReferenceError
due to the TDZ.
- For
Example of Hoisting
// Variable hoisting with var
console.log(x); // Output: undefined (declaration hoisted, initialization not)
var x = 10;
console.log(x); // Output: 10 (value assigned)
// Function hoisting
greet(); // Output: "Hello, world!" (function can be called before declaration)
function greet() {
console.log("Hello, world!");
}
// Function expression hoisting
try {
sayHi(); // Throws ReferenceError: Cannot access 'sayHi' before initialization
} catch (error) {
console.error(error.message); // Output: Cannot access 'sayHi' before initialization
}
var sayHi = function() {
console.log("Hi!");
};
Example of Temporal Dead Zone (TDZ)
function example() {
console.log(a); // ReferenceError: Cannot access 'a' before initialization
let a = 5;
console.log(b); // ReferenceError: Cannot access 'b' before initialization
const b = 10;
}
example();
Why Hoisting and TDZ Matter
-
Predictable Behavior:
- Hoisting allows functions to be called before they are declared, and TDZ helps prevent accessing variables before they are initialized, leading to more predictable code.
-
Error Prevention:
- TDZ prevents usage of
let
andconst
variables before they are declared, reducing potential bugs and improving code reliability.
- TDZ prevents usage of
Disadvantages
-
Confusion:
- Hoisting can be confusing, especially when dealing with variables declared with
var
versuslet
andconst
.
- Hoisting can be confusing, especially when dealing with variables declared with
-
Debugging Difficulty:
- Errors related to hoisting and TDZ can be challenging to debug, particularly in complex codebases.
Best Practices
- Declare Variables and Functions at the Top: To avoid issues, always declare variables and functions at the top of their scope.
- Use
let
andconst
: Preferlet
andconst
for block-scoped variables to avoid hoisting pitfalls and leverage TDZ for safer code. - Understand Scope and Initialization: Be aware of how and when variables are initialized to ensure correct usage.
Understanding hoisting and the Temporal Dead Zone helps in writing clear, reliable JavaScript code, preventing common errors and improving code maintainability.
Closures
What's Closure
Closure: In JavaScript, a closure is a function that retains access to its lexical scope, even after the outer function has finished executing. This allows the inner function to access variables and parameters from the outer function, effectively preserving and manipulating state across different executions.
User Account Manager
This example demonstrates how closures can encapsulate private data and manage user-specific operations.
function createUserManager(initialBalance) {
let balance = initialBalance; // Private variable to store the user's balance
return {
deposit: function(amount) {
if (amount > 0) {
balance += amount;
console.log(`Deposited: $${amount}`);
} else {
console.log('Deposit amount must be positive');
}
},
withdraw: function(amount) {
if (amount > 0 && amount <= balance) {
balance -= amount;
console.log(`Withdrew: $${amount}`);
} else {
console.log('Invalid withdrawal amount');
}
},
getBalance: function() {
return balance;
}
};
}
// Creating two user accounts
const user1 = createUserManager(1000);
const user2 = createUserManager(500);
// Operations on user1
user1.deposit(200); // Deposited: $200
console.log(user1.getBalance()); // Output: 1200
user1.withdraw(500); // Withdrew: $500
console.log(user1.getBalance()); // Output: 700
// Operations on user2
user2.deposit(300); // Deposited: $300
console.log(user2.getBalance()); // Output: 800
user2.withdraw(100); // Withdrew: $100
console.log(user2.getBalance()); // Output: 700
// Checking that user1 and user2 have independent balances
console.log(user1.getBalance()); // Output: 700
console.log(user2.getBalance()); // Output: 700
Explanation:
-
createUserManager
Function:- Initializes a private
balance
variable and returns an object with methods to interact with it.
- Initializes a private
-
Closure in Action:
- Each method (
deposit
,withdraw
,getBalance
) forms a closure around thebalance
variable, allowing them to access and modify it even aftercreateUserManager
has finished executing.
- Each method (
-
Encapsulation:
- The
balance
variable is encapsulated within the closure, preventing direct access from outside thecreateUserManager
function. This allows controlled access through the provided methods.
- The
Function Executes Only Once
This example demonstrates a function that ensures another function is executed only once, regardless of how many times it is called.
function createOnceFunction(fn) {
let executed = false; // Track whether the function has been executed
return function(...args) {
if (!executed) {
fn(...args); // Execute the function only once
executed = true; // Mark as executed
} else {
console.log('Function already executed'); // Notify if already executed
}
};
}
// Example function to execute only once
const initialize = () => console.log('Initialized');
// Create a function that executes `initialize` only once
const initializeOnce = createOnceFunction(initialize);
// Test the once function
initializeOnce(); // Output: Initialized
initializeOnce(); // Output: Function already executed
initializeOnce(); // Output: Function already executed
Explanation:
-
createOnceFunction
Function:- Initializes a variable
executed
to track if the provided functionfn
has been called. - Returns a function that checks if
fn
has been executed. If not, it executesfn
and setsexecuted
totrue
.
- Initializes a variable
-
Closure in Action:
- The inner function retains access to the
executed
variable and the functionfn
through the closure. - This ensures
fn
is executed only once, regardless of how many times the returned function is called.
- The inner function retains access to the
-
Use Case:
- Ideal for scenarios like configuration initialization, logging, or any task that should only be performed a single time throughout the application’s lifecycle.
Advantages of Closures
-
Data Encapsulation:
- Closures provide private variables and methods, enhancing data protection and encapsulation.
-
State Management:
- Closures help manage and preserve state across different function calls, useful for complex logic and persistent data.
-
Function Factories:
- Closures enable creating functions with customized behaviors and maintaining state, like creating functions that execute only once.
Disadvantages of Closures
-
Increased Memory Usage:
- Closures can lead to higher memory consumption because they retain references to their lexical environment.
-
Complexity:
- Overusing or deeply nesting closures can make code harder to understand and debug.
Best Practices
- Use Closures Wisely: Apply closures judiciously to balance memory usage and code complexity.
- Encapsulate Related Functionality: Group related functionality using closures to improve code organization.
- Monitor Performance: Be aware of potential performance impacts and optimize closure usage where necessary.
Closures are a powerful feature in JavaScript that enable advanced patterns for managing state and encapsulating logic, making them a valuable tool for building efficient and maintainable applications.
Exploring Modern JS Features
JavaScript has seen significant advancements over the years, with ECMAScript (ES) versions bringing new features and syntax improvements to the language. In this article, we will delve into some of the key features introduced in ES6 and ES14, providing a clear definition and practical examples for each.
ES6 Features
-
Arrow Functions
- Definition: Arrow functions offer a concise syntax for writing functions and inherit the
this
context from their surrounding scope, making them particularly useful for functions that need to preserve thethis
context from their surrounding code. - Example:
// Traditional function function add(a, b) { return a + b; } // Arrow function const add = (a, b) => a + b; console.log(add(2, 3)); // Output: 5
- Definition: Arrow functions offer a concise syntax for writing functions and inherit the
-
Classes
- Definition: Classes provide a cleaner syntax for creating objects and handling inheritance, offering a more structured approach compared to prototype-based inheritance.
- Example:
class Person { constructor(name) { this.name = name; } greet() { return `Hello, ${this.name}!`; } } const person = new Person('Alice'); console.log(person.greet()); // Output: Hello, Alice!
-
Template Literals
- Definition: Template literals enable embedded expressions and multi-line strings using backticks, making string interpolation and formatting more straightforward.
- Example:
const name = 'Bob'; const age = 25; const message = `My name is ${name} and I am ${age} years old.`; console.log(message); // Output: My name is Bob and I am 25 years old.
-
Destructuring Assignment
- Definition: Destructuring assignment allows for the extraction of values from arrays and objects into variables, simplifying code and improving readability.
- Example:
// Array destructuring const [a, b] = [1, 2]; console.log(a, b); // Output: 1 2 // Object destructuring const { x, y } = { x: 10, y: 20 }; console.log(x, y); // Output: 10 20
-
Default Parameters
- Definition: Default parameters allow you to specify default values for function parameters, simplifying function calls and avoiding
undefined
issues. - Example:
function greet(name = 'Guest') { return `Hello, ${name}!`; } console.log(greet()); // Output: Hello, Guest! console.log(greet('John')); // Output: Hello, John!
- Definition: Default parameters allow you to specify default values for function parameters, simplifying function calls and avoiding
-
Rest and Spread Operators
- Definition: The rest operator collects multiple function arguments into an array, while the spread operator expands an array into individual elements, streamlining array manipulation.
- Example:
// Rest parameters function sum(...numbers) { return numbers.reduce((acc, num) => acc + num, 0); } console.log(sum(1, 2, 3)); // Output: 6 // Spread operator const arr1 = [1, 2]; const arr2 = [3, 4]; const combined = [...arr1, ...arr2]; console.log(combined); // Output: [1, 2, 3, 4]
-
Enhanced Object Literals
- Definition: Enhanced object literals simplify the definition of object properties and methods using shorthand syntax, making the code more concise.
- Example:
const name = 'Eve'; const obj = { name, greet() { return `Hello, ${this.name}!`; } }; console.log(obj.greet()); // Output: Hello, Eve!
-
Promises
- Definition: Promises represent the completion or failure of an asynchronous operation, providing a cleaner approach to handling asynchronous code compared to traditional callbacks.
- Example:
const fetchData = new Promise((resolve, reject) => { setTimeout(() => resolve('Data received'), 1000); }); fetchData .then(result => console.log(result)) // Output: Data received .catch(error => console.log(error));
-
Modules
- Definition: Modules allow code to be split into separate files, with
import
andexport
statements facilitating code reuse and organization. - Example:
// In math.js export const add = (a, b) => a + b; // In main.js import { add } from './math.js'; console.log(add(2, 3)); // Output: 5
- Definition: Modules allow code to be split into separate files, with
-
Symbol
- Definition: Symbols are unique and immutable primitives used primarily as object property keys, ensuring that property names do not clash.
- Example:
const uniqueSymbol = Symbol('description'); const obj = { [uniqueSymbol]: 'This is a unique value' }; console.log(obj[uniqueSymbol]); // Output: This is a unique value
ES14 Features
-
Logical Assignment Operators
- Definition: Logical assignment operators combine logical operations with assignment in a single expression, simplifying common operations.
- Example:
let x = 0; x ||= 10; // x = x || 10 console.log(x); // Output: 10 let y = 5; y &&= 0; // y = y && 0 console.log(y); // Output: 0
-
Numeric Separators
- Definition: Numeric separators (
_
) improve the readability of large numbers by allowing digit grouping. - Example:
const billion = 1_000_000_000; console.log(billion); // Output: 1000000000 const price = 1_299.99; console.log(price); // Output: 1299.99
- Definition: Numeric separators (
-
WeakRefs
- Definition:
WeakRef
allows the creation of weak references to objects, which can be garbage-collected while still being referenced. - Example:
let obj = { name: 'A weak reference' }; const weakRef = new WeakRef(obj); console.log(weakRef.deref()); // Output: { name: 'A weak reference' } obj = null; // obj is eligible for garbage collection console.log(weakRef.deref()); // Output: undefined
- Definition:
-
FinalizationRegistry
- Definition:
FinalizationRegistry
registers cleanup callbacks that are called when objects are garbage-collected, useful for resource management. - Example:
const registry = new FinalizationRegistry((heldValue) => { console.log(`Cleaned up: ${heldValue}`); }); let obj = { name: 'To be cleaned up' }; registry.register(obj, obj.name); obj = null; // obj is eligible for garbage collection // The cleanup callback will be triggered when obj is collected
- Definition:
-
Top-Level Await
- Definition: Top-level
await
allowsawait
to be used at the top level of modules, simplifying asynchronous code without requiring async functions. - Example:
// In an ES module const response = await fetch('https://api.example.com/data'); const data = await response.json(); console.log(data);
- Definition: Top-level
-
Array Methods (
at
,findLast
,findLastIndex
)- Definition: New array methods
at
,findLast
, andfindLastIndex
enhance array manipulation by allowing easier access to elements from the end and finding elements based on conditions. - Example:
const arr = [10, 20, 30, 40, 50]; console.log(arr.at(-1)); // Output: 50 (last element) console.log(arr.findLast(x => x < 40)); // Output: 30 (last element less than 40) console.log(arr.findLastIndex(x => x < 40)); // Output: 2 (index of last element less than 40)
- Definition: New array methods
-
Class Fields
- Definition: Class fields allow defining instance fields directly within class bodies, including private fields for better encapsulation.
- Example:
class MyClass { publicField = 'Public Field'; #privateField = 'Private Field'; getPrivateField() { return this.#privateField; } } const instance = new MyClass(); console.log(instance.publicField); // Output: Public Field console.log(instance.getPrivateField()); // Output: Private Field
-
Private Methods and Accessors
-
Definition: Private methods and properties, indicated by
#
, are used within classes to provide encapsulation and hide internal details from outside access. -
Example:
class MyClass { #privateMethod() { return 'This is a private method'; } publicMethod() { return this.#privateMethod(); } } const instance = new MyClass(); console.log(instance.publicMethod()); // Output: This is a private method // console.log(instance.#privateMethod()); // SyntaxError: Private field '#privateMethod' must be declared in an enclosing class
-
-
WeakMap and WeakSet Enhancements
- Definition: Enhancements to
WeakMap
andWeakSet
improve memory management by ensuring objects can be garbage-collected when no longer needed. - Example:
const weakMap = new WeakMap(); let key = {}; weakMap.set(key, 'value'); console.log(weakMap.get(key)); // Output: value key = null; // key is eligible for garbage collection
- Definition: Enhancements to
-
Logical Nullish Assignment
- Definition: Logical nullish assignment combines the nullish coalescing operator with assignment to simplify setting default values.
- Example:
let foo = null; foo ??= 'default'; // foo = foo ?? 'default' console.log(foo); // Output: default let bar = 'value'; bar ??= 'default'; // bar remains 'value' console.log(bar); // Output: value
These features from ES6 and ES14 showcase JavaScript’s evolution, aiming to make code more efficient, readable, and powerful. Understanding these features can significantly enhance your coding practices and keep you up-to-date with modern JavaScript development.