Javascript — это синхронный однопоточный язык. Это означает, что JS может выполнять одну команду за раз в определенном порядке.

Как работает JS-код?

Возьмем простой пример

var n = 2;
function square (num) {
  var ans  = num * num;
  return ans;
}
var square2 = square(n);
var square4 = square(4);

Когда мы запускаем код JS, он создает глобальный контекст выполнения (GEC) внутри стека вызовов. GEC был создан в два этапа, т.е.

  1. Фаза создания памяти

На этом этапе все переменные и функции сохраняются в памяти. Но переменная хранится с неопределенным значением, а функции сохраняются со всем кодом, скопированным в память.

|----------------------|-------------------------------|
|     Memory           |      Code                     |
|----------------------|-------------------------------|
| n:undefined          |                               |
| square: {...all code |                               |
|   copied to memory } |                               |
|                      |                               |
| square2: undefined   |                               |
| square4: undefined   |                               |
|----------------------|-------------------------------|

Затем создается новый контекст выполнения для var square2 = square(n); внутри блока кода, и все новые переменные и функции сохраняются в памяти на этапе создания кода.

|----------------------|-------------------------------|
|     Memory           |      Code                     |
|----------------------|-------------------------------|
| n:undefined          | | Memory         | Code    |  |
| square: {...all code | |----------------|---------|  |
|   copied to memory } | | ans: undefined |         |  |
|                      | |                |         |  |
| square2: undefined   | |----------------|---------|  |
| square4: undefined   |                               |
|----------------------|-------------------------------|

Контекст выполнения будет удален после выполнения кода. Таким образом, переменная Square2 будет сохранена в памяти со значением 4.

И то же самое для var square4 = square(4); для этой строки снова создается новый контекст выполнения, который будет удален после выполнения кода.

И, наконец, GEC будет удален после выполнения кода.

Подъем

Теперь мы знаем, как Javascript выполняет код, так что теперь давайте проверим, что такое Hoisting.

var x = 10;

function sayHello() {
  console.log('Hello there!');
}

console.log(x); // 10
sayHello(); // Hello there!

Это простой пример, в котором мы определили переменную x и функцию sayHello(), а затем выполняем ее.

Теперь давайте вызовем функцию перед ее объявлением и зарегистрируем переменную.

console.log(x); // undefined

sayHello(); // Hello there!

var x = 10;

function sayHello() {
  console.log('Hello there!');
}

Как мы знаем, когда код JS выполняется, он создает GEC с памятью и кодом. И первая фаза хранит undefined для переменных и хранит весь блок кода для функций внутри памяти.

Это означает, что еще до того, как код начнет выполняться, память будет выделена для всех переменных и функций. поэтому даже до определения переменной и функции мы можем получить значение из памяти.

В нашем случае мы получаем значение x как undefined, а Hello there! получаем из функции.

|----------------------|---------------|
|     Memory           |      Code     |
|----------------------|---------------|
| x:undefined          |               |
| sayName: {... stores |               |
|  all code of func    |               |
|  inside the memory } |               |
|                      |               |
|----------------------|---------------|

но он ведет себя по-разному для let и const .

Объявления let и const также поднимаются наверх своей области видимости, но в отличие от var они не инициализируются внутри глобального объекта. Вместо этого они вводят temporal dead zone до точки объявления в коде. Это означает, что если вы попытаетесь получить доступ к переменной let до ее объявления, вы получите ReferenceError (Uncaught ReferenceError: невозможно получить доступ к «xxx» до инициализации)

Таким образом, мы можем сказать, что temporal dead zone — это время с момента, когда переменная let/const была поднята до ее инициализации с некоторым значением.

Другой пример

var x = 1;
a();             // 10
b();             // 100       
console.log(x);  // 1

function a() {
  var x = 10;
  console.log(x);
}

function b() {
  var x = 100;
  console.log(x);
}

Лексическая область видимости и цепная область видимости

Лексическое окружение создается всякий раз, когда создается контекст выполнения, а лексическое окружение — это память вместе со ссылкой на лексическое окружение родителя.

Здесь функция b лексически находится внутри функции a, а функция b имеет ссылку на лексическое окружение функции a.

function a() {
  var x = 10;
  function b() {
    console.log(x);
  }
  b();
}
a(); // 10

Объем цепочки

Область действия цепочки — это область действия функции, а также область действия ее родителя, прародителя и так далее.

Пример:

function a() {
  var x = 10;
  function b() {
    function c() {
      console.log(x);
    }
    c();
  }
  b();
}
a(); // 10

В этом примере внутри функции c у нас есть console.log(x), сначала он попытается получить переменную внутри функции c, если ее там нет, затем он перейдет к функции b, затем к функции a, а затем к глобальной области видимости. Это называется цепочкой области видимости.

Посмотрим в браузере

Другой пример

let a = 10;
var b = 100;

Если мы запустим приведенный выше код, var b будет прикреплен к объекту window, но в случае b это не так.

Пусть, var & const

давайте разберемся на примере

console.log(a); // undefined -> no error for var
var a = 10;
var a = 100; // no error at this line

console.log(b); // Uncaught ReferenceError: Cannot access 'a' before initialization
let b = 10;
let b = 100; // Error: Uncaught SyntaxError: Identifier 'a' has already been declared

// Same for const
const c = 10;
const c = 100; // Error: Uncaught SyntaxError: Identifier 'a' has already been declared


// ****Difference between let and const****

// 1 > We can re assign a value to a let variable but not to a const variable;

b = 100; // no error
c = 100; // Error: Uncaught TypeError: Assignment to constant variable.

// 2 > In case of let we can define a variable and later we can assign a value
// but we can't do for const
let b2; 
b2 = 20;

const c2; // Uncaught SyntaxError: Missing initializer in const declaration

// 3> We can change the value of let but can't change value of const.

let b3 = 30;
b3 = 40;

const c3 = 25;
c3 = 35;  // Uncaught TypeError: Assignment to constant variable.

Блок, область действия и затенение

Блок окружен { и } .

{
  ....
}

Давайте посмотрим, как поднимаются var, let и const.

Мы не можем получить доступ к переменным блочной области вне блока,

{
  var a = 10;
  let b = 20;
  const c = 30;
}

console.log(a); // 10
console.log(b); // Uncaught ReferenceError: b is not defined
console.log(c); // Uncaught ReferenceError: c is not defined

слежка

Когда переменная, объявленная в определенной области (например, локальная переменная), имеет то же имя, что и переменная во внешней области (например, глобальная переменная), это называется затенением.

Давайте поймем это на примере,

var a = 100;
let b = 200;
const c = 300;
console.log("a: ", a); // 100

{
  var a = 10; // a is shadowed in the block
  let b = 20;
  const c = 30;
  console.log("a: ", a); // 10
  console.log("b: ", b); // 20
}

console.log("a: ", a); // 10 -> var a is shadowed inside the block and
//the value is updated because, they both are pointing to same memory location

console.log("b: ", b); // 200 -> let b is shadowed in the block but,
// the value does not changed, because they both are pointing to
// different memory location

console.log("c: ", c); // 300

Посмотрим, как это выглядит в браузере

Другие варианты использования

// Use case 1
let a = 10;
{
  var a = 20; //Uncaught SyntaxError: Identifier 'a' has already been declared
}

// Use case 2
var b = 10;
{
  let b = 20;
  console.log(b); // 20
}
console.log(b);  // 10

// Use case 3
let a = 10;
{
  const a = 20;
  console.log(a);  // 20
}
console.log(a);  // 10

// Below code is a valid shodowing
const c = 10;
{
  const c = 20;
  {
    const c = 30;
    console.log(c); // 30
  }
  console.log(c); // 20
}
console.log(c); // 10

Закрытия

Замыкание — это комбинация функции, объединенной вместе (заключенной) со ссылками на ее окружающее состояние (лексическое окружение). Другими словами, замыкание дает вам доступ к области действия внешней функции из внутренней функции. В JavaScript замыкания создаются каждый раз, когда создается функция, во время создания функции.

Пример 1:

Пример 2:

function x() {
  var a = 7;
  function y() {
    console.log(a); // -> 7 this is a closure
  }
  return y;
}
var z = x();

z();

В этой программе мы возвращаем функцию y из x. После возврата функции y execution context (EC) из x будет удалено из call stack. Но мы по-прежнему можем получить доступ к переменной a из функции y. Это называется closure. Функция y запоминает, откуда она пришла через замыкание.

Посмотрим в браузере

Пример 3:

Например, в 3 функция y находится внутри x(), а функция x находится внутри z(). И мы пытаемся получить доступ к var b из z() и к var a из x(), и мы можем получить к ним доступ через замыкания.

Даже если вы вернете функцию y из строки 5 и выполните снаружи, она все равно сохранит значение a и b.

Использование замыканий:

  • Шаблон проектирования модуля
  • карри
  • Функции как один
  • Запомнить
  • Поддержание состояния в асинхронном мире
  • setTimeouts
  • Итераторы
  • и многое другое…

Недостаток закрытия

  1. Потребление памяти. Замыкания могут привести к увеличению потребления памяти. Когда замыкание создается, оно сохраняет ссылки на свои внешние переменные, предотвращая их сборку мусора. Если замыкания создаются в цикле или в длительном процессе, они могут накапливать и потреблять значительный объем памяти.
  2. Влияние на производительность. Замыкания могут влиять на производительность, особенно когда они используются во вложенных или глубоко вложенных функциях. Доступ к переменным из внешних областей требует прохождения цепочки областей, что может быть медленнее по сравнению с доступом к переменным в той же области действия функции.
  3. Возможные утечки памяти. Замыкания могут непреднамеренно привести к утечкам памяти, если их не использовать осторожно. Если замыкание сохраняет ссылки на объекты или ресурсы, которые больше не нужны, эти объекты не будут удалены сборщиком мусора, что приведет к утечке памяти.
  4. Читаемость и сопровождение. Чрезмерное или неправильное использование замыканий может затруднить чтение, понимание и обслуживание кода.

Замыкания с помощью setTimeout

Давайте попробуем вывести увеличенный счетчик через каждую 1 секунду,

function counter() {
  for (var i = 1; i <= 5; i++) {
    setTimeout(() => {
      console.log(i);
    }, i * 1000);
  }
  console.log("Hello there!");
}
counter(); // output after every one sec: 6, 6, 6, 6, 6, 6

Чтобы решить вышеуказанную проблему, мы можем использовать let вместо var в приведенной выше программе, но мы также можем сделать это с var, используя closure.

for(var i=1; i < 6; i++) {
  function counter(x) {
      setTimeout(() => {
          console.log(x);
      }, x * 1000)
  }
  counter(i);
}
// 1, 2, 3, 4, 5

Пример того, чем замыкание отличается от лексического env

Пример лексического окружения

function z() {
  var b = 900;
  function x() {
    var a = 7;
    function y() {
      console.log(a, b);
    }
    y();
  }
  x();
}

z();

Пример закрытия

function z() {
  var b = 900;
  function x() {
    var a = 7;
    function y() {
      console.log(a, b);
    }
    return y;
  }
  x();
}

var inner = z();
inner();

После возврата функции y функции z и x будут удалены из стека вызовов, но мы все еще можем получить доступ через замыкание. (можно обратиться к 2-му примеру для более подробной информации).

Закрытие как сокрытие данных

Пример 1:

function counter() {
  var count = 0;
  function increment() {
    return count++;
  }
  increment();
}

console.log(count); // can't access count here
counter();
console.log(count); // can't access count here either

Мы не можем получить доступ к счетчику переменных вне функции, потому что перед вызовом функции EC не был создан, поэтому переменная x не была поднята и инициализирована. И мы не можем вызвать его после вызова функции, потому что после завершения выполнения функции EC функции будет удален и больше недоступен.

Пример 2:

function counter() {
  var count = 0;
  return function increment() {
    return count +=1;
  }
}

var counter1 = counter();
console.log(counter1()); // 1
console.log(counter1()); // 2

var counter2 = counter(); // new instance
console.log(counter2()); // 1

console.log(counter1()); // 3
console.log(counter1()); // 4

console.log(counter2()); // 2

Закрытие с функциями конструктора

function Counter() { // since it's constructure func name start with caps
  var count = 0;
  this.incrementCounter = function() {
    return count +=1;
  }
  this.decrementCounter = function() {
    return count -=1;
  }
}

var counter1 = new Counter(); //

console.log(counter1.incrementCounter()); // 1
console.log(counter1.incrementCounter()); // 2
console.log(counter1.decrementCounter()); // 1

Мы можем сделать то же самое в классе

class Counter {
  constructor() {
    var count = 0;
    this.incrementCounter = function () {
      return count += 1;
    };
    this.decrementCounter = function () {
      return count -= 1;
    };
  }
}

var counter1 = new Counter();

console.log(counter1.incrementCounter()); // 1
console.log(counter1.incrementCounter()); // 2
console.log(counter1.decrementCounter()); // 1

Циклы событий

Цикл событий постоянно проверяет ожидающие задачи в callback queue, если есть callback в callback queue, он перемещается в call stack, если стек вызовов пуст.

И если нет ожидающих событий или обратных вызовов, цикл событий будет ждать возникновения событий.

Давайте разберемся с этим на примере,

console.log("start");

setTimeout(() => {
  console.log("Callback called!");
}, 5000);

console.log("End");
  1. Начинается выполнение кода, и в консоль выводится «старт».
  2. Вызывается функция setTimeout. Он регистрирует функцию обратного вызова внутри web API env и устанавливает таймер на 5000 миллисекунд.
  3. Цикл событий переходит на следующую строку, не дожидаясь окончания таймера.
  4. «Конец» регистрируется в консоли.
  5. Цикл событий продолжает работать и проверяет наличие ожидающих выполнения задач.
  6. Через 5000 миллисекунд таймер для setTimeout завершает работу, и функция обратного вызова добавляется в очередь задач.
  7. Цикл событий проверяет очередь задач и находит функцию обратного вызова.
  8. Затем выполняется функция обратного вызова, и «Вызван обратный вызов!» регистрируется в консоли.

Цикл событий имеет два типа очереди задач.

  1. Очередь микрозадач
    * process.nextTick()
    * Обратный вызов Promise
    * асинхронный обратный вызов
  2. Очередь обратного вызова (Очередь макрозадач)
    * setTimeout()
    * setInterval()
    * setImmediate()

Очередь микрозадач имеет более высокий приоритет, чем очередь обратного вызова. Цикл событий выполняет задачи в следующем порядке:

  • Выполняет все задачи в очереди микрозадач.
  • Выполняет самую старую задачу в очереди обратного вызова.

Голодание в цикле событий

Если в Очереди обратного вызова большое количество задач, то задачи в Очереди микрозадач будут выполняться только после выполнения всех задач в Очереди обратного вызова. Это вызовет голодание в цикле событий.

Веб-API

Веб-API предоставляется средой веб-браузера. Эти API-интерфейсы встроены в браузер и позволяют JavaScript взаимодействовать с различными аспектами веб-платформы, такими как управление DOM (объектной моделью документа), создание HTTP-запросов, обработка таймеров и событий, доступ к хранилищу браузера и т. д.

Веб-API не являются частью основного языка JavaScript, но они доступны для JavaScript через среду браузера. Они предоставляют дополнительные функции и позволяют JavaScript взаимодействовать с браузером и окружающей его средой.

Обычно используют веб-API

  • setTimeout (API таймера)
  • console.log (консольный API)
  • DOM-API
  • Получить API
  • API геолокации
  • localstorage, sessionStorage (API веб-хранилища)
  • API веб-аудио
  • API холста

Проверьте здесь, чтобы увидеть все веб-API.

Обратные вызовы

Функция обратного вызова — это функция, передаваемая в другую функцию в качестве аргумента, которая затем вызывается внутри внешней функции для завершения какой-либо подпрограммы или действия.

function greeting(name) {
  alert(`Hello, ${name}`);
}
function processUserInput(callback) {
  const name = prompt("Please enter your name.");
  callback(name);
}
processUserInput(greeting);

Примеры предопределенных методов с обратным вызовом

setTimeout(callBackFunc, 100)
btn.addEventListener('click', callBackFunc);
array1.map(function callbackFunc(el) {} )
array1.filter(function callbackFunc(el) {} )
array1.forEach(function callbackFunc(el) {} )

Преимущество обратного звонка

  • Как мы уже знаем, Javascript — это синхронный и однопоточный язык, но с помощью обратных вызовов мы можем выполнять asynchronous operations.

Ограничение обратного вызова

  • Ад обратного вызова

Ад обратных вызовов, также известный как pyramid of doom, представляет собой ситуацию, которая возникает в асинхронном программировании, когда имеется несколько вложенных обратных вызовов. Это происходит, когда обратные вызовы связаны друг с другом, что приводит к глубоко вложенным и трудночитаемым структурам кода. Это может затруднить понимание, поддержку и отладку кода.

  • Инверсия управления

Всякий раз, когда у нас есть функция обратного вызова и мы передаем ее какой-либо другой функции, мы передаем управление нашей функцией другой функции, и мы не знаем, что сейчас происходит за кулисами. Он может не вызывать функцию обратного вызова или может вызывать ее несколько раз.

Обещать

Промисы используются для обработки асинхронных операций в JavaScript. Ими легко управлять при работе с несколькими асинхронными операциями, когда обратные вызовы могут создать ад обратных вызовов, ведущий к неуправляемому коду.

Компоненты обещания

  • Состояние
  • Результат

Промис имеет три состояния:

  • ожидание: обещание все еще находится в разработке.
  • выполнено: обещание успешно разрешается и возвращает значение
  • отклонено: обещание не выполнено с ошибкой

продолжение..

Вскоре…

  • Дросселирование
  • устранение дребезга
  • асинхронно и отложить