Chapter 2
Promise, Callback Function, Async/Await in JavaScript
Promise: A promise in JavaScript represents the eventual completion (or failure) of an asynchronous operation and its resulting value. It is a cleaner and more manageable way to handle asynchronous operations compared to traditional callback functions.
A promise can be in one of three states:
- Pending: Initial state, neither fulfilled nor rejected.
- Fulfilled: The operation completed successfully.
- Rejected: The operation failed.
Promises provide .then(), .catch(), and .finally() methods to handle these states.
Callback Function: A callback function is a function passed into another function as an argument, which is then invoked inside the outer function to complete some kind of routine or action. Callbacks are commonly used to continue execution after an asynchronous operation has completed.
Async/Await:
Async/Await is a syntactic sugar built on top of Promises, allowing asynchronous code to be written in a more synchronous fashion. The async keyword is used to declare an asynchronous function, and the await keyword is used to pause execution until a promise is resolved or rejected.
Example usage:
// Promise examplelet promise = new Promise((resolve, reject) => { setTimeout(() => { resolve("Success!"); }, 1000);});
promise.then(value => { console.log(value); // "Success!"}).catch(error => { console.error(error);});
// Callback examplefunction doSomething(callback) { setTimeout(() => { callback("Done!"); }, 1000);}
doSomething(result => { console.log(result); // "Done!"});
// Async/Await exampleasync function asyncFunction() { try { let result = await promise; console.log(result); // "Success!" } catch (error) { console.error(error); }}
asyncFunction();Difference Between Promise and Callback in JavaScript
Promises vs Callbacks:
-
Readability and Maintainability:
- Callbacks: Can lead to “callback hell” where callbacks are nested within other callbacks, making the code harder to read and maintain.
- Promises: Provide a cleaner, more readable way to chain asynchronous operations using
.then(),.catch(), and.finally()methods.
-
Error Handling:
- Callbacks: Error handling can be more complex and inconsistent since errors need to be handled in each callback separately.
- Promises: Have a more structured approach to error handling using
.catch()to handle errors in the entire chain.
-
Chaining:
- Callbacks: Chaining multiple asynchronous operations can be cumbersome and lead to deeply nested code.
- Promises: Allow easy chaining of multiple asynchronous operations, making the code flatter and more readable.
-
Return Values:
- Callbacks: Cannot directly return values from asynchronous operations; instead, you pass the result to the next callback.
- Promises: Can return a promise from one
.then()method to another, allowing values to be passed through the chain.
Example comparison:
Callback Hell:
doSomething((result1) => { doSomethingElse(result1, (result2) => { doMore(result2, (result3) => { console.log(result3); }); });});Promise Chain:
doSomething() .then(result1 => doSomethingElse(result1)) .then(result2 => doMore(result2)) .then(result3 => { console.log(result3); }) .catch(error => { console.error(error); });In summary, promises and async/await offer more readable and maintainable ways to handle asynchronous operations compared to callbacks, with better error handling and chaining capabilities.
Event delegation
Explanation
Event delegation is a technique involving adding event listeners to a parent element instead of adding them to the descendant elements. The listener will fire whenever the event is triggered on the descendant elements due to event bubbling up the DOM. The benefits of this technique are:
Memory footprint goes down because only one single handler is needed on the parent element, rather than having to attach event handlers on each descendant. There is no need to unbind the handler from elements that are removed and to bind the event for new elements.
Hoisting in JavaScript
Explanation
Hoisting is a JavaScript mechanism where variable and function declarations are moved to the top of their containing scope during the compile phase. This means you can use variables and functions before they are declared in the code.
Variable Hoisting:
- Variables declared with
varare hoisted and initialized withundefined. - Variables declared with
letandconstare hoisted but are not initialized, leading to a “temporal dead zone” until the declaration is encountered.
Function Hoisting:
- Function declarations are hoisted and can be used before they are defined.
- Function expressions are not hoisted.
Example:
console.log(hoistedVar); // undefinedvar hoistedVar = 'This is hoisted';
console.log(hoistedLet); // ReferenceError: hoistedLet is not definedlet hoistedLet = 'This is not hoisted';
hoistedFunction(); // This function is hoistedfunction hoistedFunction() { console.log('This function is hoisted');}
hoistedFunctionExpr(); // TypeError: hoistedFunctionExpr is not a functionvar hoistedFunctionExpr = function() { console.log('This function is not hoisted');};Temporal Dead Zone in JavaScript
Explanation
The Temporal Dead Zone (TDZ) is the period between entering a block scope and the point where a variable declared with let or const is initialized. During this time, accessing the variable results in a ReferenceError.
Example:
console.log(x); // ReferenceError: Cannot access 'x' before initializationlet x = 10;In the example above, accessing x before its declaration results in a ReferenceError due to the TDZ.
MutationObserver in JavaScript
Explanation
MutationObserver is a built-in object that allows you to observe changes to the DOM tree. You can use it to watch for changes to the child elements, attributes, and text content of a specified node.
Example:
const targetNode = document.getElementById('myElement');const config = { attributes: true, childList: true, subtree: true };
const callback = function(mutationsList, observer) { for(let mutation of mutationsList) { if (mutation.type === 'childList') { console.log('A child node has been added or removed.'); } else if (mutation.type === 'attributes') { console.log('The ' + mutation.attributeName + ' attribute was modified.'); } }};
const observer = new MutationObserver(callback);observer.observe(targetNode, config);
// To stop observing// observer.disconnect();In the example above, MutationObserver watches for changes to the specified element’s children and attributes.
Memoization in JavaScript
Explanation
Memoization is an optimization technique to speed up function calls by caching the results of expensive function calls and returning the cached result when the same inputs occur again.
Example:
function memoize(fn) { const cache = {}; return function(...args) { const key = JSON.stringify(args); if (cache[key]) { return cache[key]; } else { const result = fn(...args); cache[key] = result; return result; } };}
const slowFunction = (num) => { // Some expensive computation return num * 2;};
const memoizedFunction = memoize(slowFunction);
console.log(memoizedFunction(5)); // Slow computationconsole.log(memoizedFunction(5)); // Fast return from cacheIn the example above, memoize function caches the results of slowFunction to avoid recomputing for the same inputs.
Difference Between Map and Set in JavaScript
Explanation
Map:
- A
Mapis a collection of key-value pairs where keys can be of any type. - It maintains the insertion order of the keys.
- Methods:
set,get,has,delete,clear,size.
Example:
let map = new Map();map.set('key1', 'value1');map.set('key2', 'value2');console.log(map.get('key1')); // "value1"console.log(map.has('key2')); // trueSet:
- A
Setis a collection of unique values (no duplicates). - It also maintains the insertion order of the values.
- Methods:
add,has,delete,clear,size.
Example:
let set = new Set();set.add(1);set.add(2);set.add(1); // Duplicate, will be ignoredconsole.log(set.has(1)); // trueconsole.log(set.size); // 2WeakMap and WeakSet in JavaScript
Explanation
WeakMap:
- A
WeakMapis a collection of key-value pairs where the keys must be objects and the values can be of any type. - The keys in a
WeakMapare held weakly, meaning they do not prevent garbage collection if there are no other references to the object. - Methods:
set,get,has,delete.
Example:
let weakMap = new WeakMap();let obj = {};weakMap.set(obj, 'value');console.log(weakMap.get(obj)); // "value"obj = null; // The object is now eligible for garbage collectionWeakSet:
- A
WeakSetis a collection of unique objects (no duplicates). - The objects in a
WeakSetare held weakly, meaning they do not prevent garbage collection if there are no other references to the object. - Methods:
add,has,delete.
Example:
let weakSet = new WeakSet();let obj = {};weakSet.add(obj);console.log(weakSet.has(obj)); // trueobj = null; // The object is now eligible for garbage collectionsessionStorage, localStorage, and Cookie in JavaScript
Explanation
sessionStorage:
- Stores data for a single session (data is lost when the browser tab is closed).
- Storage limit is about 5-10 MB.
Example:
sessionStorage.setItem('key', 'value');console.log(sessionStorage.getItem('key')); // "value"sessionStorage.removeItem('key');sessionStorage.clear();localStorage:
- Stores data with no expiration time (data persists even when the browser is closed).
- Storage limit is about 5-10 MB.
Example:
localStorage.setItem('key', 'value');console.log(localStorage.getItem('key')); // "value"localStorage.removeItem('key');localStorage.clear();Cookie:
- Stores data that can be sent to the server with HTTP requests (can have an expiration date).
- Storage limit is about 4 KB.
Example:
document.cookie = "username=John Doe; expires=Fri, 31 Dec 2024 23:59:59 GMT; path=/";console.log(document.cookie); // "username=John Doe"Truthy and Falsy Values in JavaScript
Explanation
Truthy Values: In JavaScript, a truthy value is a value that is considered true when evaluated in a Boolean context. All values are truthy unless they are defined as falsy.
Examples of truthy values:
if (true) // trueif ({}) // trueif ([]) // trueif (42) // trueif ("0") // trueif ("false") // trueif (new Date()) // trueif (-42) // trueif (12n) // trueif (3.14) // trueif (-3.14) // trueif (Infinity) // trueif (-Infinity) // trueFalsy Values: A falsy value is a value that is considered false when evaluated in a Boolean context.
Examples of falsy values:
if (false) // falseif (0) // falseif (-0) // falseif (0n) // falseif ("") // falseif (null) // falseif (undefined) // falseif (NaN) // falsethis
Explanation
The value of this depends on how the function is called its (call site).
The following rules are applied:
-
If the
newkeyword is used when calling the function, this inside the function is a brand new object. -
If
apply, call, or bindare used to call/create a function, this inside the function is the object that is passed in as the argument. -
If a function is called as a method, such as obj.method() —
thisis the object that the function is a property of. -
If a function is invoked as a free function invocation, meaning it was invoked without any of the conditions present above, this is the global object. In a browser, it is the
windowobject. If in strict mode ('use strict'), this will beundefinedinstead of the global object. -
If multiple of the above rules apply, the rule that is higher wins and will set the this value.
-
If the function is an ES2015 arrow function, it ignores all the rules above and receives the
thisvalue of its surrounding scope at the time it is created.
Spread Operator
Explanation
Both “rest operator” and “spread operator” refer to the same operator (…), used differently. When you see “rest”, it’s being used to gather up properties. When you see “spread”, it’s spreading them out.
Spreading Function Arguments example
function calculateSum(... numbers) {return numbers. reduce( (sum, num) => sum + num, 0);}const result = calculateSum(1, 2, 3, 4, 5);In JS, is functions is an Object?
Explanation
Yes, in JavaScript, functions are indeed objects. This is one of the features of JavaScript’s flexible and dynamic nature. Specifically, functions in JavaScript are first-class objects, which means they can be:
-
Assigned to variables:
let myFunction = function() {console.log("Hello, world!");}; -
Passed as arguments to other functions:
function callFunction(fn) {fn();}callFunction(myFunction); -
Returned from other functions:
function createFunction() {return function() {console.log("Function created and called!");};}let newFunction = createFunction();newFunction(); -
Have properties and methods:
function example() {console.log("This is an example function.");}example.description = "This function is just an example.";console.log(example.description); // Outputs: This function is just an example.
Functions are objects in the sense that they have properties and methods. Every function has properties like length and name, and can have custom properties. Moreover, functions also have methods such as call, apply, and bind.
Here’s a quick demonstration:
function greet(name) { return `Hello, ${name}!`;}
console.log(typeof greet); // Outputs: functionconsole.log(greet instanceof Object); // Outputs: true
// Adding a property to the functiongreet.description = "This function greets a person.";console.log(greet.description); // Outputs: This function greets a person.
// Using a method available on function objectsconst greetJohn = greet.bind(null, "John");console.log(greetJohn()); // Outputs: Hello, John!As you can see, the function greet can have properties (like description), and can use methods (bind in this case) just like any other object in JavaScript. This demonstrates the object nature of functions in JavaScript.
Difference bw let, const and var
Answer
Comparing Block Scope and Function Scope: Inside a function but outside blocks:
function example() { if (true) { var functionScoped = "I'm function scoped"; let blockScoped = "I'm block scoped"; } console.log(functionScoped); // Output: "I'm function scoped" console.log(blockScoped); // ReferenceError: blockScoped is not defined}example();Outside of functions:
{ var globalVar = "I'm globally scoped"; let blockVar = "I'm block scoped";}console.log(globalVar); // Output: "I'm globally scoped"console.log(blockVar); // ReferenceError: blockVar is not definedSummary of Differences
-
Hoisting Behavior:
var: Declaration is hoisted to the top of the function or global scope and initialized toundefined.letandconst: Declarations are hoisted to the top of the block scope, but not initialized, leading to the Temporal Dead Zone.
-
Temporal Dead Zone (TDZ):
var: No Temporal Dead Zone; variables are accessible and initialized toundefined.letandconst: Have a Temporal Dead Zone; accessing the variable before its declaration results in aReferenceError.
-
Initialization Requirement:
var: Initialization can happen later in the code.let: Initialization can happen later, but accessing it before initialization within the block causes an error.const: Must be initialized at the point of declaration.
Examples Illustrating Hoisting
-
Function Scope and
varHoisting:function exampleFunction() {console.log(hoistedVar); // Output: undefinedvar hoistedVar = "I'm hoisted";console.log(hoistedVar); // Output: "I'm hoisted"}exampleFunction(); -
Block Scope and
let/constHoisting:{console.log(blockScopedLet); // ReferenceError: blockScopedLet is not definedlet blockScopedLet = "I'm block scoped with let";console.log(blockScopedLet); // Output: "I'm block scoped with let"}{console.log(blockScopedConst); // ReferenceError: blockScopedConst is not definedconst blockScopedConst = "I'm block scoped with const";console.log(blockScopedConst); // Output: "I'm block scoped with const"}
Can you give an example of one of the ways that working with this has changed in ES6?
Explanation
ES6 allows you to use arrow functions which uses the enclosing lexical scope. This is usually convenient, but does prevent the caller from controlling context via .call or .apply—the consequences being that a library such as jQuery will not properly bind this in your event handler functions. Thus, it’s important to keep this in mind when refactoring large legacy applications.
tip: To determine this, go one line above the arrow function’s creation and see what the value of this is there. It will be the same in the arrow function.
What’s the difference between .call and .apply?
Function.prototype.call() or Function.prototype.apply()
Explanation
Both .call and .apply are used to invoke functions and the first parameter will be used as the value of this within the function. However, .call takes in comma-separated arguments as the next arguments while .apply takes in an array of arguments as the next argument. An easy way to remember this is C for call and comma-separated and A for apply and an array of arguments.
function add(a, b) { return a + b;}
console.log(add.call(null, 1, 2)); // 3console.log(add.apply(null, [1, 2])); // 3Explain Function.prototype.bind
Explanation
Taken word-for-word from MDN:
The bind() method creates a new function that, when called, has its this keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called.
In my experience, it is most useful for binding the value of this in methods of classes that you want to pass into other functions. This is frequently done in React components.
Example:
function fn() { console.log(this);}var obj = { value: 5};var boundFn = fn.bind(obj);boundFn(); // -> { value: 5 }Explain inheritence
Inheritance in JavaScript can be achieved through a mechanism called “constructor chaining” or “pseudo-classical inheritance”. This is where one constructor function calls another constructor function to reuse code and inherit properties.
function Animal(name) { this.name = name;}function Dog(name) { Animal.call(this, name); // Call the parent constructor.}
const bolt = new Dog('Bolt');
console.log(bolt);Explain how prototypal inheritance works
When a property is accessed on an object and if the property is not found on that object, the JavaScript engine looks at the object’s __proto__, and the __proto__’s __proto__ and so on, until it finds the property defined on one of the __proto__s or until it reaches the end of the prototype chain.
Example
/function Animal(name) { this.name = name;}
// Add a method to the parent object's prototype.Animal.prototype.makeSound = function () { console.log('The ' + this.constructor.name + ' makes a sound.');};
// Child object constructor.function Dog(name) { Animal.call(this, name); // Call the parent constructor.}
// Set the child object's prototype to be the parent's prototype.Object.setPrototypeOf(Dog.prototype, Animal.prototype);
// Add a method to the child object's prototype.Dog.prototype.bark = function () { console.log('Woof!');};
// Create a new instance of Dog.const bolt = new Dog('Bolt');
bolt.makeSound(); // "The Dog makes a sound."Explanation
Step 1: Create a Parent Object Constructor
function Animal(name) { this.name = name;}- When you create a new instance of
Animalusingnew Animal('Name'), thethisinside theAnimalconstructor refers to the new object being created. - The line
this.name = name;sets thenameproperty of the new object to the value passed as an argument.
Step 2: Add a Method to the Parent Object’s Prototype
Animal.prototype.makeSound = function () { console.log('The ' + this.constructor.name + ' makes a sound.');};- The
makeSoundmethod is added toAnimal.prototype. - When you call
makeSoundon an instance ofAnimal(or any object that inherits fromAnimal),thisinside the method refers to the object on which the method was called. this.constructor.namegets the name of the constructor function that created the object, which will beAnimalor any subclass likeDog.
Step 3: Create a Child Object Constructor
function Dog(name) { Animal.call(this, name); // Call the parent constructor.}- When you create a new instance of
Dogusingnew Dog('Bolt'), thethisinside theDogconstructor refers to the new object being created. Animal.call(this, name);calls theAnimalconstructor function withthisset to the newDogobject.- This means
this.name = name;inside theAnimalconstructor sets thenameproperty of the newDogobject to ‘Bolt’.
Step 4: Set the Child Object’s Prototype
Object.setPrototypeOf(Dog.prototype, Animal.prototype);Object.setPrototypeOf(Dog.prototype, Animal.prototype);sets up the prototype chain so thatDog.prototypeinherits fromAnimal.prototype.- This means any instance of
Dogwill have access to methods defined onAnimal.prototype, likemakeSound.
Step 5: Add a Method to the Child Object’s Prototype
Dog.prototype.bark = function () { console.log('Woof!');};- The
barkmethod is added toDog.prototype. - When you call
barkon an instance ofDog,thisinside the method refers to theDogobject on which the method was called.
Step 6: Create an Instance of the Child Object
const bolt = new Dog('Bolt');new Dog('Bolt')creates a new instance ofDog.- Inside the
Dogconstructor,Animal.call(this, name)setsthis.nameto ‘Bolt’ for the newDogobject.
Step 7: Call Methods on the Child Object
console.log(bolt.name); // "Bolt"bolt.makeSound(); // "The Dog makes a sound."bolt.bark(); // "Woof!"bolt.nameaccesses thenameproperty of theboltobject, which is ‘Bolt’.bolt.makeSound()calls themakeSoundmethod fromAnimal.prototype. Here,thisrefers tobolt, andthis.constructor.nameis ‘Dog’, so it logs “The Dog makes a sound.”bolt.bark()calls thebarkmethod fromDog.prototype. Here,thisrefers tobolt, and it logs “Woof!“.
Summary of this in Each Step
- Inside
Animalconstructor:thisrefers to the newAnimal(orDog) instance being created. - Inside
makeSoundmethod:thisrefers to the instance (e.g.,bolt) on which the method is called. - Inside
Dogconstructor:thisrefers to the newDoginstance being created. - Setting prototypes:
thisis not directly used. - Inside
barkmethod:thisrefers to the instance (e.g.,bolt) on which the method is called.
What do you think of AMD vs CommonJS?
Explanation
Both are ways to implement a module system, which was not natively present in JavaScript until ES2015 came along. CommonJS is synchronous while AMD (Asynchronous Module Definition) is obviously asynchronous. CommonJS is designed with server-side development in mind while AMD, with its support for asynchronous loading of modules, is more intended for browsers.
I find AMD syntax to be quite verbose and CommonJS is closer to the style you would write import statements in other languages. Most of the time, I find AMD unnecessary, because if you served all your JavaScript into one concatenated bundle file, you wouldn’t benefit from the async loading properties. Also, CommonJS syntax is closer to Node style of writing modules and there is less context-switching overhead when switching between client side and server side JavaScript development.
I’m glad that with ES2015 modules, that has support for both synchronous and asynchronous loading, we can finally just stick to one approach. Although it hasn’t been fully rolled out in browsers and in Node, we can always use transpilers to convert our code.
What’s a typical use case for anonymous functions?
Explanation
They can be used in IIFEs to encapsulate some code within a local scope so that variables declared in it do not leak to the global scope.
(function () { // Some code here.})();As a callback that is used once and does not need to be used anywhere else. The code will seem more self-contained and readable when handlers are defined right inside the code calling them, rather than having to search elsewhere to find the function body.
setTimeout(function () { console.log('Hello world!');}, 1000);How do you organize your code? (module pattern, classical inheritance?)
Explanation
These days, I use React/Redux which utilize a single-directional data flow based on Flux architecture. I would represent my app’s models using plain objects and write utility pure functions to manipulate these objects. State is manipulated using actions and reducers like in any other Redux application.
I avoid using classical inheritance where possible.
What’s the difference between host objects and native objects?
Explanation
Native objects are objects that are part of the JavaScript language defined by the ECMAScript specification, such as String, Math, RegExp, Object, Function, etc.
Host objects are provided by the runtime environment (browser or Node), such as window, XMLHTTPRequest, etc.
Difference between: function Person(){}, var person = Person(), and var person = new Person()?
Explanation
This question is pretty vague. My best guess at its intention is that it is asking about constructors in JavaScript. Technically speaking, function Person(){} is just a normal function declaration. The convention is to use PascalCase for functions that are intended to be used as constructors.
var person = Person() invokes the Person as a function, and not as a constructor. Invoking as such is a common mistake if the function is intended to be used as a constructor. Typically, the constructor does not return anything, hence invoking the constructor like a normal function will return undefined and that gets assigned to the variable intended as the instance.
var person = new Person() creates an instance of the Person object using the new operator, which inherits from Person.prototype. An alternative would be to use Object.create, such as: Object.create(Person.prototype).
function Person(name) { this.name = name;}
var person = Person('John');console.log(person); // undefinedconsole.log(person.name); // Uncaught TypeError: Cannot read property 'name' of undefined
var person = new Person('John');console.log(person); // Person { name: "John" }console.log(person.name); // "john"What’s the difference between a variable that is: null, undefined or undeclared? How would you go about checking for any of these states?
Explanation
Undeclared variables are created when you assign a value to an identifier that is not previously created using var, let or const. Undeclared variables will be defined globally, outside of the current scope. In strict mode, a ReferenceError will be thrown when you try to assign to an undeclared variable. Undeclared variables are bad just like how global variables are bad. Avoid them at all cost! To check for them, wrap its usage in a try/catch block.
function foo() { x = 1; // Throws a ReferenceError in strict mode}
foo();console.log(x); // 1A variable that is undefined is a variable that has been declared, but not assigned a value. It is of type undefined. If a function does not return any value as the result of executing it is assigned to a variable, the variable also has the value of undefined. To check for it, compare using the strict equality (===) operator or typeof which will give the ‘undefined’ string. Note that you should not be using the abstract equality operator to check, as it will also return true if the value is null.
A variable that is null will have been explicitly assigned to the null value. It represents no value and is different from undefined in the sense that it has been explicitly assigned. To check for null, simply compare using the strict equality operator. Note that like the above, you should not be using the abstract equality operator (==) to check, as it will also return true if the value is undefined.
As a personal habit, I never leave my variables undeclared or unassigned. I will explicitly assign null to them after declaring if I don’t intend to use it yet. If you use a linter in your workflow, it will usually also be able to check that you are not referencing undeclared variables.
What is a closure, and how/why would you use one?
Explanation
A closure is the combination of a function and the lexical environment within which that function was declared. The word “lexical” refers to the fact that lexical scoping uses the location where a variable is declared within the source code to determine where that variable is available. Closures are functions that have access to the outer (enclosing) function’s variables—scope chain even after the outer function has returned.
Why would you use one?
Data privacy / emulating private methods with closures. Commonly used in the module pattern.
When would you use document.write()??
Explanation
document.write() writes a string of text to a document stream opened by document.open(). When document.write() is executed after the page has loaded, it will call document.open which clears the whole document ( and removed!) and replaces the contents with the given parameter value. Hence it is usually considered dangerous and prone to misuse.
What’s the difference between feature detection, feature inference, and using the UA string??
Explanation
Feature detection involves working out whether a browser supports a certain block of code, and running different code depending on whether it does (or doesn’t), so that the browser can always provide a working experience rather crashing/erroring in some browsers. For example:
if ('geolocation' in navigator) { // Can use navigator.geolocation} else { // Handle lack of feature}Feature Inference
Feature inference checks for a feature just like feature detection, This is not really recommended. Feature detection is more foolproof.
UA String - User Agent String
This is a browser-reported string that allows the network protocol peers to identify the application type, operating system, software vendor or software version of the requesting software user agent. It can be accessed via navigator.userAgent. However, the string is tricky to parse and can be spoofed. For example, Chrome reports both as Chrome and Safari. So to detect Safari you have to check for the Safari string and the absence of the Chrome string. Avoid this method.
Explain Ajax in as much detail as possible?
Explanation
Ajax (asynchronous JavaScript and XML) is a set of web development techniques using many web technologies on the client side to create asynchronous web applications. With Ajax, web applications can send data to and retrieve from a server asynchronously (in the background) without interfering with the display and behavior of the existing page. By decoupling the data interchange layer from the presentation layer, Ajax allows for web pages, and by extension web applications, to change content dynamically without the need to reload the entire page. In practice, modern implementations commonly use JSON instead of XML, due to the advantages of JSON being native to JavaScript.
The XMLHttpRequest API is frequently used for the asynchronous communication or these days, the fetch API.
What are the advantages and disadvantages of using Ajax?
Explanation
Advantages
- Better interactivity. New content from the server can be changed dynamically without the need to reload the entire page.
- Reduce connections to the server since scripts and stylesheets only have to be requested once.
- State can be maintained on a page. JavaScript variables and DOM state will persist because the main container page was not reloaded.
- Basically most of the advantages of an SPA.
Disadvantages
- Dynamic webpages are harder to bookmark.
- Does not work if JavaScript has been disabled in the browser.
- Some webcrawlers do not execute JavaScript and would not see content that has been loaded by JavaScript.
- Webpages using Ajax to fetch data will likely have to combine the fetched remote data with client-side templates to update the DOM. For this to happen, JavaScript will have to be parsed and executed on the browser, and low-end mobile devices might struggle with this.
- Basically most of the disadvantages of an SPA.