Давайте сначала обсудим, чем объявлены переменные withvar
и let
.
И let
, и var
используются для объявления переменных, которые могут быть дополнительно инициализированы во время объявления.
Согласно MDN:
Оператор
var
объявляет переменную с функциональной или глобальной областью видимости, при необходимости инициализируя ее значением.
Оператор
let
объявляет локальную переменную с блочной областью видимости, при необходимости инициализируя ее значением.
Но есть существенные различия с точки зрения их создания и использования.
Отличия на момент создания и исполнения
Анализ программы JavaScript - это двухэтапный процесс.
Двухэтапный процесс для переменных с var
- Этап создания. На этом этапе выполняется поиск всех объявлений переменных и функций в файле JavaScript и создается память для них в глобальном или функциональном контексте выполнения. например
1| console.log(a); // undefined 2| var a = 10;
Как я упоминал выше, при синтаксическом анализе вышеуказанной программы этап создания будет искать все объявления переменных и игнорировать все другие операторы. Поэтому будет создана память для переменной a
, которая по умолчанию будет присвоена undefined
.
2. Этап выполнения - на этом этапе выполняется построчная оценка каждого оператора и выполняется программа.
В приведенном выше примере строка 1 попытается найти переменную a
в глобальном контексте выполнения и попытается распечатать ее значение, которое в то время не определено.
Это поведение также известно как подъем, при котором переменная может использоваться даже до ее объявления.
Двухэтапный процесс для переменных с let
- Этап создания - аналогично этапам создания для var, память создается и для
let
операторов, но в этом случае память не назначена дляundefined
по умолчанию. Вместо этого переменная попадает в временную мертвую зону (TDZ), что означает, что переменную нельзя использовать до ее объявления.
1| console.log(a); // Refernce Error 2| let a = 10;
2. Этап выполнения - В строке номер 1 код теперь выдает эталонную ошибку, то есть переменную a
нельзя использовать до ее объявления (из-за TDZ ).
Различия в объемах переменных
Давайте разберемся на примерах, чем var
и let
переменные различаются по своему объему.
Глобальное загрязнение объекта
var a = 10; console.log(a); // 10 console.log(window.a); // a
Это означает, что когда переменная создается в глобальном контексте (с помощью var), она загрязняет глобальный объект окна.
let a = 10; console.log(a); // 10 console.log(window.a); // undefined
Но переменные, созданные с помощью let в глобальном контексте, не загрязняют глобальный объект окна.
Повторное объявление переменных
Мы можем повторно объявить var
переменных в том же контексте выполнения, но не можем сделать это с переменными let.
var a = 10; var a = 20; // works fine let b = 10; let b = 20; // Error
Объем функции по сравнению с Область действия блока
function a() { var b = 10; if (b > 0) { var b = 20; // same variables } console.log(x); // 20 } function z() { var x = 10; if (x > 0) { let x = 20; // different variables } console.log(x); // 10 }
Замыкания 2.0
Давайте сначала поймем замыкание. По MDN
Замыкание - это сочетание функции, объединенной (заключенной), со ссылками на ее окружающее состояние (лексическое окружение). Другими словами, замыкание дает вам доступ к области внешней функции из внутренней функции. В JavaScript замыкания создаются каждый раз, когда создается функция, во время создания функции.
Замыкания полезны, потому что они позволяют вам связывать данные (лексическую среду) с функцией, которая работает с этими данными. В этом есть очевидные параллели с объектно-ориентированным программированием, где объекты позволяют связывать данные (свойства объекта) с одним или несколькими методами.
function outer() { var a = 10; return function inner() { return a + 10; } } var inner = outer(); console.log(inner()); // 20
Замыкания также очень полезны при реализации модульного JavaScript. В этом отношении может оказаться полезным IIFE (Выражение немедленного вызова функции).
var App = {}; (function(){ var loggedInUser = { name: "John Doe", email: "[email protected]", password: "somepassword", token: "sometoken", } function getLoggedInUser() { var user = { ...loggedInUser }; delete user.password; return user; } function updateToken(token) { loggedInUser = {...loggedInUser, token}; } App.auth = { getLoggedInUser, updateToken, } })() console.log(App.auth.getLoggedInUser()); // Works Fine console.log(getLoggedInUser()); // Reference Error
Но когда я впервые посмотрел на это, я задумался, почему мы должны использовать одну дополнительную анонимную функцию и немедленно ее вызывать. Но поскольку до ES2015 в JavaScript не было классов и пространств имен, это был единственный шаблон для создания модулей (создание частного состояния и реализаций и предоставление только общедоступных API, которые будут использоваться в других модулях).
Теперь, почему IIFE, поскольку для создания состояния или частных свойств / переменных у нас было var
(до ES2015), а var
- это переменная функциональной области, у нас не было другого способа, кроме как обернуть это состояние и методы во внешнюю функцию. А поскольку мы хотим запустить ее один раз, внешняя функция должна быть анонимной и вызываться после ее объявления.
Теперь попробуем реализовать тот же модуль, используя только блоки.
let App = {}; { let loggedInUser = { name: "John Doe", email: "[email protected]", password: "somepassword", token: "sometoken", } let getLoggedInUser = function() { var user = { ...loggedInUser }; delete user.password; return user; } let updateToken = function(token) { loggedInUser = {...loggedInUser, token}; } App.auth = { getLoggedInUser, updateToken, } } console.log(App.auth.getLoggedInUser()); // Works Fine console.log(getLoggedInUser()); // Reference Error
Большой!! Теперь мы избавились от анонимной функции-оболочки и вызова функции. Поскольку let - это переменная с областью видимости блока, нам просто нужно обернуть все частные переменные и методы внутри блока.
Знаменитый вопрос для интервью
Мне лично задали этот вопрос, в котором есть var-forloop, а внутри блока цикла for есть setTimeout, который печатает переменную i
. Давайте проверим это ниже.
for(var i=0; i<3; i++) { setTimeout(() => console.log(i), 1000); } > 3 not 0 > 3 not 1 > 3 not 2
Если вы попытаетесь запустить приведенный выше фрагмент кода, вы получите 3 раза по 3, а не 0, 1 и 2, как вы могли ожидать.
Давайте узнаем, как var
работает здесь.
- Поскольку
var i
не является переменной с областью видимости блока,i
будет создан в глобальной области, если вы напечатаетеwindow.i
, вы получите 3 (что ожидается). - Поскольку внутренняя функция, которая передается как обратный вызов в setTimeout, в каждом цикле регистрируется новый обратный вызов, который лексически привязан к переменной
i
, которая была объявлена в глобальном контексте. - Поскольку все три обратных вызова лексически привязаны к одному и тому же
window.i
, то есть 3, когда вызываются обратные вызовы. Каждый раз печатается 3.
Как теперь решить эту проблему?
for(let i=0; i<3; i++) { setTimeout(() => console.log(i), 1000); } // Output > 0 > 1 > 2
Преобразуйте var i
в let i
, и вы получите 0, 1 и 2, как и ожидалось. Но как изменение просто var
на let
отлично работает в этом случае.
Давайте узнаем, как let
работает здесь.
- В каждом цикле создается новый блок, и для каждого блока создается
let i
. - Теперь внутри этого блока обратный вызов ликсификационно привязан к let i, который будет равен 0, 1 и 2.
- Поэтому выполняются обратные вызовы, мы получаем 0, 1 и 2.
Но как заставить код работать, даже не меняя let
с var
? Поскольку var
- это переменная с функциональной областью действия, нам нужно будет обернуть setTimeout внутри функции, которая будет получать новое значение в каждом цикле, а обратный вызов будет привязан к переменной с функциональной областью, а не к глобальной переменной.
for(let i=0; i<3; i++) { (function(a){ setTimeout(() => console.log(a), 1000); })(i) }
Надеюсь, вам понравилось читать. Спасибо!!!