Synchronous and asynchronous programming are two different approaches to handling program flow and operations.
Synchronous Programming:
Asynchronous Programming:
Example: Synchronous vs Asynchronous
// Synchronous
console.log("Start");
function syncOperation() {
for (let i = 0; i < 1000000000; i++) {} // Time-consuming operation
return "Sync completed";
}
console.log(syncOperation());
console.log("End");
// Output:
// Start
// (pause for several seconds)
// Sync completed
// End
// Asynchronous
console.log("Start");
function asyncOperation() {
return new Promise(resolve => {
setTimeout(() => resolve("Async completed"), 2000);
});
}
asyncOperation().then(console.log);
console.log("End");
// Output:
// Start
// End
// (after 2 seconds)
// Async completed
Diagram: Synchronous vs Asynchronous
Synchronous: Asynchronous:
┌─────┐ ┌─────┐
│Task1│ │Task1│──────────┐
└──┬──┘ └─────┘ │
│ ┌─────┐ │
┌──▼──┐ │Task2│──────┐ │
│Task2│ └─────┘ │ │
└──┬──┘ ┌─────┐ │ │
│ │Task3│ │ │
┌──▼──┐ └─────┘ │ │
│Task3│ ▲ │ │
└─────┘ │ │ │
┌───┴────────┴───▼──┐
│ Completion │
└──────────────────┘
Debouncing and throttling are techniques used to control how many times a function is called over time, often applied to optimize performance in event-driven programming.
Debouncing:
Throttling:
Example: Debounce
function debounce(func, delay) {
let timeoutId;
return function (...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func.apply(this, args), delay);
};
}
const expensiveOperation = () => console.log('Expensive operation');
const debouncedOperation = debounce(expensiveOperation, 300);
// Rapid invocations
debouncedOperation();
debouncedOperation();
debouncedOperation();
// Only the last invocation will be executed after 300ms
Example: Throttle
function throttle(func, limit) {
let inThrottle;
return function (...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
const scrollHandler = () => console.log('Scroll event');
const throttledScroll = throttle(scrollHandler, 1000);
// Attach to scroll event
window.addEventListener('scroll', throttledScroll);
Diagram: Debounce vs Throttle
Debounce:
Events: │ │ │ │ │ │ │ │
▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼
Execution: │ │
────────┘ └────────▶ time
Throttle:
Events: │ │ │ │ │ │ │ │
▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼
Execution: │ │ │ │
▼ ▼ ▼ ▼
──────────────────────────▶ time
Understanding the difference between deep copy and shallow copy is crucial when working with complex data structures in JavaScript.
Shallow Copy:
Deep Copy:
Example: Shallow Copy vs Deep Copy
// Original object
const original = {
name: "John",
age: 30,
address: {
city: "New York",
country: "USA"
}
};
// Shallow copy
const shallowCopy = { ...original };
// Deep copy (simple implementation, not suitable for complex objects)
const deepCopy = JSON.parse(JSON.stringify(original));
// Modifying copies
shallowCopy.name = "Jane";
shallowCopy.address.city = "Los Angeles";
deepCopy.name = "Alice";
deepCopy.address.city = "Chicago";
console.log(original);
// Output: { name: "John", age: 30, address: { city: "Los Angeles", country: "USA" } }
console.log(shallowCopy);
// Output: { name: "Jane", age: 30, address: { city: "Los Angeles", country: "USA" } }
console.log(deepCopy);
// Output: { name: "Alice", age: 30, address: { city: "Chicago", country: "USA" } }
Diagram: Shallow Copy vs Deep Copy
Shallow Copy: Deep Copy:
┌────────────┐ ┌────────────┐
│ Original │ │ Original │
├────────────┤ ├────────────┤
│ name: John │ │ name: John │
│ age: 30 │ │ age: 30 │
│ address: │◄───────┐ │ address: │
└────────────┘ │ └────────────┘
▲ │ ▲
│ │ │
┌─────┴──────┐ ┌─────┴─────┐ ┌─────┴──────┐
│ Shallow │ │ address: │ │ Deep Copy │
│ Copy │ │ city: NY │ ├────────────┤
├────────────┤ │ country: │ │ name: Alice│
│ name: Jane │ │ USA │ │ age: 30 │
│ age: 30 │ └───────────┘ │ address: │
│ address: │──┐ └────────────┘
└────────────┘ │ │
│ ┌─────▼─────┐
│ │ address: │
└──────────── │ city: Chi │
│ country: │
│ USA │
└───────────┘
In the diagram, you can see that the shallow copy shares the same reference to the nested address
object with the original, while the deep copy creates a completely new structure.
Error handling in JavaScript is primarily done using the try...catch
statement, along with the throw
statement for custom error creation. This mechanism allows developers to gracefully handle and respond to runtime errors.
Key components:
try
block: Contains the code that might throw an errorcatch
block: Handles the error if one occurs in the try
blockfinally
block: Executes regardless of whether an error occurred (optional)throw
statement: Used to create custom errorsExample: Basic Error Handling
function divide(a, b) {
if (b === 0) {
throw new Error("Division by zero");
}
return a / b;
}
try {
console.log(divide(10, 2)); // Output: 5
console.log(divide(10, 0)); // Throws an error
} catch (error) {
console.error("An error occurred:", error.message);
} finally {
console.log("Operation completed");
}
// Output:
// 5
// An error occurred: Division by zero
// Operation completed
Types of Errors:
Error
: Base object for user-defined exceptionsSyntaxError
: Raised for syntax errorsReferenceError
: Raised when using undefined variablesTypeError
: Raised when a value is not of the expected typeRangeError
: Raised when a value is not in the expected rangeURIError
: Raised when using global URI handling functions incorrectlyExample: Custom Error
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = "ValidationError";
}
}
function validateUser(user) {
if (!user.name) {
throw new ValidationError("Name is required");
}
if (user.age < 18) {
throw new ValidationError("User must be at least 18 years old");
}
}
try {
validateUser({ name: "John", age: 16 });
} catch (error) {
if (error instanceof ValidationError) {
console.error("Validation failed:", error.message);
} else {
console.error("An unexpected error occurred:", error);
}
}
// Output: Validation failed: User must be at least 18 years old
Diagram: Error Handling Flow
┌─────────────────┐
│ try block │
│ ┌───────────┐ │
│ │ Code │ │
│ └─────┬─────┘ │
└────────┼────────┘
│ Error?
▼
┌─────────────────┐ ┌─────────────────┐
│ catch block │◄───│ throw Error │
│ ┌───────────┐ │ └─────────────────┘
│ │ Handle │ │
│ │ Error │ │
│ └───────────┘ │
└────────┬────────┘
│
▼
┌─────────────────┐
│ finally block │
│ (if present) │
└─────────────────┘
This diagram illustrates the flow of execution in a try-catch-finally block, showing how errors are propagated and handled.
Strict mode is a feature introduced in ECMAScript 5 that allows you to place a program, or a function, in a “strict” operating context. This strict context prevents certain actions from being taken and throws more exceptions.
Key features of Strict mode:
How to enable Strict mode:
"use strict";
at the beginning of the script"use strict";
at the beginning of the function bodyExample: Strict mode
"use strict";
// This will throw an error in strict mode
x = 3.14; // ReferenceError: x is not defined
function strictFunc() {
"use strict";
var y = 3.14; // This is okay
}
function nonStrictFunc() {
z = 3.14; // This won't throw an error
}
strictFunc();
nonStrictFunc();
Key differences in Strict mode:
this
is undefined in the global context (not window in browsers)eval
cannot create variables in the surrounding scopeExample: Strict mode behaviors
"use strict";
// 1. Variables must be declared
x = 5; // Throws ReferenceError
// 2. Duplicate parameter names are not allowed
function sum(a, a, c) { // Throws SyntaxError
return a + a + c;
}
// 3. 'this' in functions is undefined, not the global object
function globalThis() {
console.log(this);
}
globalThis(); // undefined, not window or global
// 4. Assigning to read-only properties throws error
var obj = {};
Object.defineProperty(obj, "x", { value: 42, writable: false });
obj.x = 9; // Throws TypeError
// 5. Deleting undeletable properties throws error
delete Object.prototype; // Throws TypeError
// 6. 'eval' doesn't create variables in the surrounding scope
eval("var y = 10;");
console.log(y); // Throws ReferenceError
Strict mode is a powerful tool for writing cleaner, more robust JavaScript code, and is especially useful when working on large projects or when preparing code for future compatibility.
ES6 (ECMAScript 2015) introduced several new features and syntax improvements over ES5. Understanding these differences is crucial for modern JavaScript development.
Key differences:
Block-scoped variables (let
and const
):
ES5: Only function-scoped variables (var
)
ES6: Introduced block-scoped variables (let
and const
)
// ES5
var x = 10;
// ES6
let y = 20;
const Z = 30;
Arrow functions:
ES5: Regular function expressions
ES6: Arrow functions with shorter syntax and lexical this
binding
// ES5
var sum = function(a, b) {
return a + b;
};
// ES6
const sum = (a, b) => a + b;
Template literals: ES5: String concatenation ES6: Template literals with backticks
// ES5
var name = "John";
var greeting = "Hello, " + name + "!";
// ES6
const name = "John";
const greeting = `Hello, ${name}!`;
Destructuring assignment: ES5: Manual assignment ES6: Destructuring for arrays and objects
// ES5
var person = { name: "John", age: 30 };
var name = person.name;
var age = person.age;
// ES6
const { name, age } = { name: "John", age: 30 };
Default parameters: ES5: Manual default value assignment ES6: Default parameters in function declarations
// ES5
function greet(name) {
name = name || "Guest";
console.log("Hello, " + name);
}
// ES6
function greet(name = "Guest") {
console.log(`Hello, ${name}`);
}
Rest and Spread operators:
ES5: arguments
object and apply()
method
ES6: Rest (...
) and Spread (...
) operators
// ES5
function sum() {
var numbers = Array.prototype.slice.call(arguments);
return numbers.reduce(function(acc, num) { return acc + num; }, 0);
}
// ES6
function sum(...numbers) {
return numbers.reduce((acc, num) => acc + num, 0);
}
Classes: ES5: Constructor functions and prototype-based inheritance ES6: Class syntax (syntactic sugar over prototype-based inheritance)
// ES5
function Person(name) {
this.name = name;
}
Person.prototype.greet = function() {
console.log("Hello, " + this.name);
};
// ES6
class Person {
constructor(name) {
this.name = name;
}
greet() {
console.log(`Hello, ${this.name}`);
}
}
Modules:
ES5: CommonJS or AMD modules
ES6: Native module system with import
and export
// ES5 (CommonJS)
var math = require('./math');
module.exports = math.sum;
// ES6
import { sum } from './math';
export default sum;
Promises: ES5: Callback-based asynchronous programming ES6: Promise-based asynchronous programming
// ES5
function fetchData(callback) {
setTimeout(function() {
callback(null, "Data");
}, 1000);
}
// ES6
function fetchData() {
return new Promise((resolve) => {
setTimeout(() => resolve("Data"), 1000);
});
}
Enhanced object literals: ES5: Standard object literal syntax ES6: Shorthand property and method definitions, computed property names
// ES5
var name = "John";
var person = {
name: name,
greet: function() {
console.log("Hello");
}
};
// ES6
const name = "John";
const person = {
name,
greet() {
console.log("Hello");
},
["say" + "Hi"]() {
console.log("Hi");
}
};
These are some of the major differences between ES5 and ES6. ES6 brought significant improvements in terms of syntax, functionality, and developer experience, making JavaScript more powerful and expressive.
Modules in JavaScript are a way to organize code into separate files, making it easier to maintain, reuse, and avoid naming conflicts. ES6 introduced a native module system, which is now widely supported in modern browsers and Node.js.
Key concepts:
Syntax:
export
: Used to expose functionality from a moduleimport
: Used to bring functionality from other modules into the current moduleTypes of exports:
Example: Named exports and imports
// math.js
export const PI = 3.14159;
export function square(x) {
return x * x;
}
export function cube(x) {
return x * x * x;
}
// main.js
import { PI, square, cube } from './math.js';
console.log(PI); // 3.14159
console.log(square(2)); // 4
console.log(cube(3)); // 27
Example: Default export and import
// person.js
export default class Person {
constructor(name) {
this.name = name;
}
sayHello() {
console.log(`Hello, I'm ${this.name}`);
}
}
// main.js
import Person from './person.js';
const john = new Person("John");
john.sayHello(); // Hello, I'm John
Example: Mixing named and default exports
// utils.js
export const VERSION = "1.0.0";
export function helper() {
console.log("I'm a helper function");
}
export default function mainFunction() {
console.log("I'm the main function");
}
// main.js
import mainFunction, { VERSION, helper as helperFunc } from './utils.js';
console.log(VERSION); // 1.0.0
helperFunc(); // I'm a helper function
mainFunction(); // I'm the main function
Benefits of using modules:
Diagram: Module System
┌─────────────┐ ┌─────────────┐
│ Module A │ │ Module B │
│ ┌─────────┐ │ export │ ┌─────────┐ │
│ │Function │ ├────────▶│ │Function │ │
│ └─────────┘ │ import │ └─────────┘ │
│ ┌─────────┐ │ │ ┌─────────┐ │
│ │ Class │ │ │ │ Class │ │
│ └─────────┘ │ │ └─────────┘ │
└─────────────┘ └─────────────┘
▲ ▲
│ │
│ ┌─────────────┴─┐
└─────────┤ Main.js │
└───────────────┘
This diagram illustrates how different modules can export and import functionality, and how a main file can use multiple modules to build an application.
Optimizing the performance of a JavaScript application involves various strategies across different aspects of development. Here are some key approaches:
// Instead of this
for (let i = 0; i < 1000; i++) {
document.body.innerHTML += '<div>' + i + '</div>';
}
// Do this
const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
const div = document.createElement('div');
div.textContent = i;
fragment.appendChild(div);
}
document.body.appendChild(fragment);
// Instead of this
document.querySelectorAll('div.class p')[0].style.color = 'red';
// Do this
const element = document.getElementById('myElement');
element.style.color = 'red';
for
loops instead of for...in
for arraysmap
, filter
, reduce
// Instead of this
for (let i = 0; i < arr.length; i++) {
// ...
}
// Do this
const len = arr.length;
for (let i = 0; i < len; i++) {
// ...
}
function debounce(func, delay) {
let timeoutId;
return function (...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func.apply(this, args), delay);
};
}
window.addEventListener('resize', debounce(() => {
// Handle resize
}, 250));
// main.js
const worker = new Worker('worker.js');
worker.postMessage({ data: complexData });
worker.onmessage = function(e) {
console.log('Result:', e.data);
};
// worker.js
self.onmessage = function(e) {
const result = performComplexCalculation(e.data);
self.postMessage(result);
};
function memoize(fn) {
const cache = new Map();
return function(...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key);
}
const result = fn.apply(this, args);
cache.set(key, result);
return result;
}
}
const expensiveFunction = memoize(function(x, y) {
// Some expensive operation
});
requestAnimationFrame
for smooth animationsfunction animate() {
// Update animation
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
Diagram: Performance Optimization Strategies
┌─────────────────────────────────────────────────────────────────┐
│ JavaScript Performance │
├─────────────────┬─────────────────┬─────────────┬──────────────┤
│ Code Efficiency │ DOM Handling │ Network │ Monitoring │
├─────────────────┼─────────────────┼─────────────┼──────────────┤
│ - Optimize loops│ - Minimize │ - Minify │ - Use dev │
│ - Use efficient │ manipulation │ - Compress │ tools │
│ algorithms │ - Batch updates │ - Lazy load │ - Implement │
│ - Memoization │ - Use fragments │ - Caching │ production │
│ - Avoid bloat │ - Virtual DOM │ - CDN use │ monitoring │
└─────────────────┴─────────────────┴─────────────┴──────────────┘
fetch
API?The fetch
API is a modern interface for making network requests in JavaScript. It provides a more powerful and flexible feature set than older methods like XMLHttpRequest.
fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
sequenceDiagram
participant Client
participant Server
Client->>Server: fetch('https://api.example.com/data')
Server-->>Client: Response
Note over Client: response.json()
Note over Client: Process data
Imagine you’re at a restaurant. The fetch
API is like a waiter. You (the JavaScript code) give the waiter (fetch) your order (the URL you want to get data from). The waiter goes to the kitchen (server), gets your food (data), and brings it back to you. Just like you might need to unwrap your food before eating it, you often need to process the response (like converting it to JSON) before you can use the data.
Event Capturing and Event Bubbling are two phases of event propagation in the DOM (Document Object Model).
graph TD
A[Document] --> B[HTML]
B --> C[Body]
C --> D[Div]
D --> E[Button]
style E fill:#f9f,stroke:#333,stroke-width:4px
In this diagram:
<div id="outer">
<div id="inner">
<button id="button">Click me</button>
</div>
</div>
document.getElementById('outer').addEventListener('click', () => console.log('Outer'), true); // Capturing
document.getElementById('inner').addEventListener('click', () => console.log('Inner'), true); // Capturing
document.getElementById('button').addEventListener('click', () => console.log('Button')); // Bubbling
document.getElementById('inner').addEventListener('click', () => console.log('Inner')); // Bubbling
document.getElementById('outer').addEventListener('click', () => console.log('Outer')); // Bubbling
If you click the button, the console output will be:
Outer
Inner
Button
Inner
Outer
Think of a family tree. Event Capturing is like starting at the oldest ancestor and working your way down to the youngest. Event Bubbling is like starting with the youngest and working your way up to the oldest. In web pages, elements can be nested inside each other, forming a tree-like structure. When an event happens on an element, it can either “capture” down from the top of the tree or “bubble” up from where it happened.
The prototype chain is a mechanism in JavaScript that allows objects to inherit properties and methods from other objects. Each object has an internal link to another object called its prototype. That prototype object has its own prototype, and so on, forming a chain that ends with an object whose prototype is null.
let animal = {
eats: true
};
let rabbit = {
jumps: true
};
rabbit.__proto__ = animal; // Sets animal as the prototype of rabbit
console.log(rabbit.eats); // true (inherited from animal)
console.log(rabbit.jumps); // true (own property)
graph TD
A[rabbit] -->|__proto__| B[animal]
B -->|__proto__| C[Object.prototype]
C -->|__proto__| D[null]
Imagine a family tree. You inherit traits from your parents, who inherited traits from their parents, and so on. In JavaScript, objects work similarly. If an object doesn’t have a property, it checks its “parent” (prototype), then its “grandparent”, and so on, until it either finds the property or reaches the end of the family tree.
Prototypal inheritance is a way for one object to inherit properties and methods from another object. It’s the mechanism by which JavaScript objects can inherit features from one another.
Object.create()
method can be used to create an object with a specific prototype.new
keyword to create objects with a shared prototype.// Using Object.create()
let animal = {
eats: true,
walk() {
console.log("Animal walks");
}
};
let rabbit = Object.create(animal);
rabbit.jumps = true;
console.log(rabbit.eats); // true
rabbit.walk(); // "Animal walks"
// Using constructor functions
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function() {
console.log(this.name + ' makes a noise.');
};
let dog = new Animal('Rex');
dog.speak(); // "Rex makes a noise."
graph TD
A[animal] -->|prototype| B[Object.prototype]
C[rabbit] -->|prototype| A
D[Animal.prototype] -->|prototype| B
E[dog] -->|prototype| D
Think of prototypal inheritance like a recipe book that gets passed down through generations. Each generation can add its own recipes (properties and methods) to the book. When you want to cook something (use a method or property), you first check your own recipes. If you don’t have it, you look in the book you inherited from your parents, then grandparents, and so on.
There are several ways to create an object with a specific prototype in JavaScript:
Object.create()
new
__proto__
property (not recommended for production code)Object.setPrototypeOf()
(not recommended for performance reasons)// 1. Using Object.create()
let animal = {
eats: true
};
let rabbit = Object.create(animal);
console.log(rabbit.eats); // true
// 2. Using constructor functions
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function() {
console.log(this.name + ' makes a noise.');
};
let dog = new Animal('Rex');
dog.speak(); // "Rex makes a noise."
// 3. Using __proto__ (not recommended)
let cat = {
meows: true
};
cat.__proto__ = animal;
console.log(cat.eats); // true
// 4. Using Object.setPrototypeOf() (not recommended)
let bird = {
flies: true
};
Object.setPrototypeOf(bird, animal);
console.log(bird.eats); // true
graph TD
A[animal] -->|prototype| B[Object.prototype]
C[rabbit] -->|Object.create| A
D[Animal.prototype] -->|prototype| B
E[dog] -->|new| D
F[cat] -->|__proto__| A
G[bird] -->|setPrototypeOf| A
Creating an object with a specific prototype is like choosing your mentor. You’re saying, “I want to learn from this person and inherit their skills.” In JavaScript, you have different ways to do this:
Object.create()
is like formally becoming someone’s apprentice.new
are like joining a guild where everyone learns the same basic skills.__proto__
is like informally following someone around to learn from them (but it’s not recommended because it can cause confusion).Object.setPrototypeOf()
is like changing your mentor after you’ve already started learning (it works, but it can slow things down).Object.create()
and constructor functions?Both Object.create()
and constructor functions are used to create objects, but they work in slightly different ways:
new
keyword to create objects// Object.create()
let animal = {
eats: true
};
let rabbit = Object.create(animal);
rabbit.jumps = true;
console.log(rabbit.eats); // true
console.log(rabbit.jumps); // true
// Constructor Function
function Animal(name) {
this.name = name;
}
Animal.prototype.eats = true;
let dog = new Animal('Rex');
console.log(dog.name); // "Rex"
console.log(dog.eats); // true
graph TD
A[animal] -->|prototype| B[Object.prototype]
C[rabbit] -->|Object.create| A
D[Animal.prototype] -->|prototype| B
D -->|eats: true| D
E[dog] -->|new| D
E -->|name: 'Rex'| E
Think of Object.create()
like adopting a child. The child (new object) inherits traits (properties and methods) from their adoptive parent (prototype object), but you can also give them their own unique traits.
Constructor functions, on the other hand, are like a baby-making machine. Each time you use new
, it’s like pressing a button on the machine to create a new baby (object). This baby automatically inherits traits from its parent (the prototype), and the machine (constructor function) can also give it some initial traits of its own.
A polyfill is a piece of code (usually JavaScript on the Web) used to provide modern functionality on older browsers that do not natively support it. The term comes from polyfilla, a British product used to fill in cracks and holes in walls.
if (!Array.prototype.includes) {
Array.prototype.includes = function(searchElement /*, fromIndex*/) {
'use strict';
if (this == null) {
throw new TypeError('Array.prototype.includes called on null or undefined');
}
var O = Object(this);
var len = parseInt(O.length, 10) || 0;
if (len === 0) {
return false;
}
var n = parseInt(arguments[1], 10) || 0;
var k;
if (n >= 0) {
k = n;
} else {
k = len + n;
if (k < 0) {k = 0;}
}
var currentElement;
while (k < len) {
currentElement = O[k];
if (searchElement === currentElement ||
(searchElement !== searchElement && currentElement !== currentElement)) { // NaN !== NaN
return true;
}
k++;
}
return false;
};
}
graph TD
A[Modern Browser] -->|Native Support| B[Modern Feature]
C[Older Browser] -->|No Native Support| D{Polyfill Available?}
D -->|Yes| E[Polyfill]
E --> B
D -->|No| F[Feature Unavailable]
Imagine you have an old car that doesn’t have a USB port. A polyfill is like an adapter that you plug into the cigarette lighter to give you a USB port. It doesn’t change your old car into a new one, but it does allow you to use modern devices with it. In the same way, polyfills allow old browsers to use new JavaScript features, making sure all users can enjoy the same functionality regardless of their browser’s age.
Certainly! Here are some examples of commonly used polyfills:
Array.prototype.forEach
Array.prototype.map
Array.prototype.filter
Array.prototype.reduce
Object.create
Object.keys
Object.assign
String.prototype.trim
String.prototype.includes
if (!Array.prototype.map) {
Array.prototype.map = function(callback/*, thisArg*/) {
var T, A, k;
if (this == null) {
throw new TypeError('this is null or not defined');
}
var O = Object(this);
var len = O.length >>> 0;
if (typeof callback !== 'function') {
throw new TypeError(callback + ' is not a function');
}
if (arguments.length > 1) {
T = arguments[1];
}
A = new Array(len);
k = 0;
while (k < len) {
var kValue, mappedValue;
if (k in O) {
kValue = O[k];
mappedValue = callback.call(T, kValue, k, O);
A[k] = mappedValue;
}
k++;
}
return A;
};
}
if (typeof Promise !== 'function') {
window.Promise = function(executor) {
var callbacks = [];
var state = 'pending';
var value;
function resolve(result) {
if (state !== 'pending') return;
state = 'fulfilled';
value = result;
callbacks.forEach(function(callback) {
callback(value);
});
}
function reject(error) {
if (state !== 'pending') return;
state = 'rejected';
value = error;
callbacks.forEach(function(callback) {
callback(value);
});
}
this.then = function(onFulfilled) {
if (state === 'pending') {
callbacks.push(onFulfilled);
} else {
onFulfilled(value);
}
return this;
};
executor(resolve, reject);
};
}
graph TD
A[Browser] -->|Check Feature Support| B{Feature Supported?}
B -->|Yes| C[Use Native Implementation]
B -->|No| D[Load Polyfill]
D --> E[Use Polyfilled Implementation]
Think of polyfills like universal adapters for different electrical outlets around the world. When you travel, you might encounter outlets that don’t fit your devices. These adapters (polyfills) allow your devices (modern JavaScript code) to work in any country (browser), regardless of the type of outlet (feature support) available. The examples above are like specific adapters for different types of outlets, each allowing a modern feature to work in an older environment.
Polyfills work by adding missing functionality to older environments, typically by checking if a feature exists and providing an implementation if it doesn’t. Here’s a step-by-step explanation of how polyfills typically work:
if (!Array.prototype.includes) {
Array.prototype.includes = function(searchElement /*, fromIndex*/) {
'use strict';
// 1. Feature detection is done in the if statement above
// 2. Conditional implementation
var O = Object(this);
var len = parseInt(O.length, 10) || 0;
if (len === 0) {
return false;
}
var n = parseInt(arguments[1], 10) || 0;
var k;
if (n >= 0) {
k = n;
} else {
k = len + n;
if (k < 0) {k = 0;}
}
var currentElement;
while (k < len) {
currentElement = O[k];
// 3. Mimicking native behavior, including NaN handling
if (searchElement === currentElement ||
(searchElement !== searchElement && currentElement !== currentElement)) {
return true;
}
k++;
}
return false;
};
}
graph TD
A[Start] --> B{Feature Exists?}
B -->|Yes| C[Use Native Implementation]
B -->|No| D[Load Polyfill]
D --> E[Check for Global Conflicts]
E --> F[Implement Feature]
F --> G[Attach to Appropriate Object/Prototype]
G --> H[End]
Imagine you’re learning to cook in a kitchen that’s missing some modern appliances. A polyfill is like a chef who checks what’s missing and then shows you how to achieve the same result with the tools you do have. For example, if you don’t have a food processor, the chef might show you how to chop ingredients finely by hand to get a similar result. The chef (polyfill) first checks what’s available in your kitchen (feature detection), then teaches you a technique (implements the feature) that mimics what the modern appliance would do, allowing you to follow modern recipes (use modern JavaScript) even in an old-fashioned kitchen (older browser).