JavaScript Mastery Part 3

ยท

8 min read

JavaScript Mastery Part 3

Synchronous vs Asynchronous:

Synchronous and asynchronous programming are two different ways of executing code. In synchronous programming, code is executed one line at a time, in the order that it is written. Synchronous programming is the most common type of programming, and it is the way that most code is written.

In asynchronous programming, code can be executed out of order, and some code may not be executed until later. asynchronous programming can be more efficient for certain tasks, such as network requests or long-running calculations.

There are two main types of asynchronous programming in JavaScript:

callbacks and promises. Callbacks are functions that are passed as arguments to other functions. When the other function is finished, it calls the callback function. Promises are objects that represent the eventual completion or failure of an asynchronous operation.

Asynchronous programming can be more difficult to understand than synchronous programming, but it can be a powerful tool for improving the performance and responsiveness of your code.

Axios CDN:

Callback:

Async / Await:

Callback vs Promises vs Async/ Await:

callback:

Promises:

Async/ Await:

Generators:

Web workers:

Main stack vs Side stack (callback queue):

In JavaScript, the main stack is the call stack. It is a data structure that follows the Last In First Out (LIFO) principle. The element that is added at last is accessed at first. The call stack is used to keep track of the execution of functions. When a function is called, it is pushed onto the call stack. When the function returns, it is popped off the call stack.

The side stack is a JavaScript engine-specific data structure that is used to keep track of the execution of asynchronous code. Asynchronous code is code that does not block the execution of the main thread. When asynchronous code is executed, it is added to the side stack. The JavaScript engine will then return to the main stack and execute the next function in the call stack. When the asynchronous code has finished executing, it is removed from the side stack.

The main stack and the side stack are two important data structures that are used by the JavaScript engine to keep track of the execution of code. The main stack is used to keep track of the execution of synchronous code, and the side stack is used to keep track of the execution of asynchronous code.

Event Loop:

The event loop is a mechanism that allows JavaScript to run asynchronous code. It is a single-threaded loop that continuously checks if there is any work to be done. If there is, it executes the next task in the queue. The event loop is responsible for handling all of the asynchronous operations in JavaScript, such as timers, I/O, and setImmediate() callbacks.

Optimize JavaScript Code

Certainly! Let's go through examples and syntax for some of the optimization techniques mentioned:

1. Asynchronous Operations:

  • Example using Promises:
function fetchData() {
  return new Promise((resolve, reject) => {
    // Simulating an asynchronous operation
    setTimeout(() => {
      resolve("Data fetched successfully");
    }, 1000);
  });
}

async function fetchDataAsync() {
  try {
    const result = await fetchData();
    console.log(result);
  } catch (error) {
    console.error(error);
  }
}

fetchDataAsync();

2. Optimize Loops:

  • Example using array methods:
// Inefficient loop
let sum = 0;
for (let i = 0; i < array.length; i++) {
  sum += array[i];
}

// Optimized loop using reduce
const sum = array.reduce((acc, currentValue) => acc + currentValue, 0);

3. Caching:

  • Example using a simple caching mechanism:
const cache = {};

function fetchData(id) {
  if (cache[id]) {
    return Promise.resolve(cache[id]);
  } else {
    return fetch(`https://api.example.com/data/${id}`)
      .then(response => response.json())
      .then(data => {
        cache[id] = data;
        return data;
      });
  }
}

4. Lazy Loading:

  • Example using the loading="lazy" attribute for images:
<img src="image.jpg" alt="Lazy-loaded image" loading="lazy">

5. Reduce DOM Manipulations:

  • Example of batched DOM updates using a document fragment:
const fragment = document.createDocumentFragment();

for (let i = 0; i < 1000; i++) {
  const element = document.createElement("div");
  element.textContent = `Item ${i}`;
  fragment.appendChild(element);
}

// Append all elements to the DOM at once
document.getElementById("container").appendChild(fragment);

6. Bundle and Minify:

  • Example using Webpack to bundle and UglifyJS to minify:
// Install necessary packages
npm install --save-dev webpack webpack-cli uglifyjs-webpack-plugin

// webpack.config.js
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
  },
  plugins: [
    new UglifyJsPlugin(),
  ],
};

7. Use Efficient Data Structures:

  • Example using Map instead of an object for key-value pairs:
// Inefficient using an object
const inefficientObject = {};
inefficientObject["key1"] = "value1";
inefficientObject["key2"] = "value2";

// Efficient using Map
const efficientMap = new Map();
efficientMap.set("key1", "value1");
efficientMap.set("key2", "value2");

8. Avoid Global Variables:

  • Example encapsulating code in a module:
// global.js
const globalVariable = "I'm global";

// module.js
const moduleVariable = "I'm encapsulated";

// main.js
console.log(globalVariable); // Accessing global variable
console.log(moduleVariable); // Error, moduleVariable is not defined

9. Debounce and Throttle:

  • Example using lodash library for debounce:
// Install lodash
// npm install lodash

// Using debounce
import debounce from 'lodash/debounce';

const fetchData = debounce(() => {
  // Your expensive operation
  console.log("Fetching data...");
}, 1000);

// Call fetchData when needed
fetchData();

10. Optimize Image Sizes:

  • Example using responsive images with the srcset attribute:
<img
  src="image.jpg"
  srcset="image-320w.jpg 320w,
          image-480w.jpg 480w,
          image-800w.jpg 800w"
  sizes="(max-width: 320px) 280px,
         (max-width: 480px) 440px,
         800px"
  alt="Responsive image"
>

11. Reduce HTTP Requests:

  • Example using CSS sprites for icons:
/* styles.css */
.icon1 {
  background-image: url('icons.png');
  background-position: 0 0;
  width: 16px;
  height: 16px;
}

.icon2 {
  background-image: url('icons.png');
  background-position: -20px 0;
  width: 16px;
  height: 16px;
}

12. Use Web Workers:

  • Example using a Web Worker for background computation:
// main.js
const worker = new Worker('worker.js');

worker.postMessage({ operation: 'multiply', operands: [3, 4] });

worker.onmessage = (event) => {
  console.log(`Result: ${event.data}`);
};

// worker.js
self.onmessage = (event) => {
  const { operation, operands } = event.data;

  if (operation === 'multiply') {
    const result = operands.reduce((acc, val) => acc * val, 1);
    self.postMessage(result);
  }
};

13. Profile and Benchmark:

  • Example using browser developer tools for profiling:
Open Chrome DevTools (F12), go to the "Performance" tab, and start recording.
- Interact with your web page to capture performance data.
- Analyze the timeline and identify bottlenecks or areas for improvement.

14. Update Regularly:

  • Keep your dependencies up-to-date using npm or yarn:
// Update a specific package
npm update package-name

// Update all packages to their latest versions
npm update

15. Minimize DOM Manipulations:

  • Example using document fragment to minimize DOM manipulations:
// Inefficient approach
const container = document.getElementById('container');
for (let i = 0; i < 1000; i++) {
  const element = document.createElement('div');
  element.textContent = `Item ${i}`;
  container.appendChild(element);
}

// Efficient approach using document fragment
const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
  const element = document.createElement('div');
  element.textContent = `Item ${i}`;
  fragment.appendChild(element);
}
container.appendChild(fragment);

16. Optimize String Concatenation:

  • Example using template literals instead of traditional string concatenation:
// Inefficient string concatenation
let result = '';
for (let i = 0; i < 1000; i++) {
  result = result + 'Item ' + i + ' ';
}

// Efficient string concatenation with template literals
let result = '';
for (let i = 0; i < 1000; i++) {
  result = `${result}Item ${i} `;
}

17. Use Object Pooling:

  • Example of object pooling to reuse objects instead of creating new ones:
// Inefficient object creation
function createObject() {
  return { data: 'some data' };
}

const obj1 = createObject();
const obj2 = createObject();

// Efficient object pooling
const objectPool = [];
function createPooledObject() {
  if (objectPool.length > 0) {
    return objectPool.pop();
  } else {
    return { data: 'some data' };
  }
}

const pooledObj1 = createPooledObject();
const pooledObj2 = createPooledObject();

// After use, push objects back to the pool
objectPool.push(pooledObj1, pooledObj2);

18. Avoid Unnecessary Function Calls:

  • Example of avoiding unnecessary function calls:
// Inefficient function calls
function processItem(item) {
  // Some processing logic
}

const items = [/* ... */];
items.forEach(item => processItem(item));

// Efficient: Inline the processing logic
const items = [/* ... */;
items.forEach(item => {
  // Some processing logic
});
```

19. Use Object Destructuring:

  • Example of using object destructuring for cleaner code:
// Inefficient variable assignment
const user = { name: 'John', age: 30 };
const name = user.name;
const age = user.age;

// Efficient object destructuring
const user = { name: 'John', age: 30 };
const { name, age } = user;

20. Optimize Event Listeners:

  • Example using event delegation for efficient event handling:
// Inefficient event handling
const buttons = document.querySelectorAll('.button');
buttons.forEach(button => {
  button.addEventListener('click', () => {
    // Handle click
  });
});

// Efficient event delegation
document.getElementById('container').addEventListener('click', (event) => {
  if (event.target.classList.contains('button')) {
    // Handle click
  }
});

These examples cover additional optimization techniques. Always consider the specific requirements and context of your project when implementing optimizations, and regularly profile and test your code to ensure improvements are achieved.

ย