Статьи

Голый JavaScript — динамика закрытия

В шестой части серии « Голый JavaScript» я собираюсь обсудить концепцию замыканий в JavaScript. В предыдущей статье я лишь кратко упомянул тему замыканий в JavaScript. В этой статье мы рассмотрим абсолютные детали этой интересной темы. Замыкания в JavaScript — это очень важная концепция, и она очень удобна, когда вы пытаетесь вызывать функции или писать классы. Это не только одна из самых важных концепций в JavaScript, но и удивительно интересная, которая вносит значительный вклад в выразительную силу этого замечательного языка программирования.

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

Рассмотрим блок кода, который написан непосредственно внутри тега script, то есть в глобальной области видимости.

<script type="text/javascript">
var bingo={name"'xyz'};
console.log(bingo.name);
</script>

Поскольку этот блок кода был написан непосредственно внутри тега сценария, переменное лото становится глобальной переменной. Глобальные переменные — это переменные, которые объявлены в объекте окна. В этом случае, поскольку вы не указали родительский объект для бинго, предполагается, что родительский объект — «окно».

Эта логика по умолчанию может привести к огромному беспорядку, как мы узнали в предыдущей статье , потому что размер программы постепенно увеличивается со временем. И с новыми функциями, появляющимися время от времени, вы, скорее всего, захотите назвать свои переменные на основе концепций реального мира, которые они представляют. Однако объявление переменных в глобальной области может привести к серьезным проблемам, потому что одна функция может закончить чтение значений, установленных другой функцией, только потому, что они используют одно и то же имя переменной. Это где область видимости входит в картину. Однако область видимости в JavaScript — это совершенно другой зверь. В отличие от других языков программирования, таких как Java, JavaScript не поддерживает область действия на уровне блоков.

Одним из решений этой проблемы было бы указать отдельный родительский объект для каждой переменной с одинаковым именем. Например

//Defined on the global 'window' object
var bingo={name:'xyz'};
 
//Defined in a nested object
var myObj = {};
myObj.bingo={name:'xyz'};
console.log(bingo.name);
console.log(myObj.bingo.name);

Как видите, поскольку мы указали имя объекта внутри другого объекта, мы смогли повторно использовать имя переменной без каких-либо конфликтов. Переменные bingo и myObj являются глобальными свойствами. то есть они имеют глобальный охват. Это означает, что для ваших собственных программ вы можете создать один глобальный объект и определить все свои функции как свойства этого объекта. Таким образом, вы можете гарантировать, что вы загромождаете глобальную область только одним объектом. Все идет нормально.

Теперь поговорим о функциях.

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

function popeye(){
 var msg = 'I love olive';
 console.log(msg);
};
 
function bluto(){
 var msg = 'I love olive too!';
 console.log(msg);
};

Как вы видите, в нашей функции мы смогли повторно использовать переменную «msg» только потому, что они существовали в разных областях. Даже это довольно просто, и, конечно, работает как положено. Но подождите, это еще не все.

Введите вложенные функции.

В отличие от Java, JavaScript позволяет создавать вложенные функции. Синтаксис объявления вложенных функций такой же, как объявление нормальной функции. Давайте посмотрим на пример.

function popeye(){
 var msg = 'I love olive';
  
 function printMe(){
  console.log(msg);
 }
  
 printMe();
};

Даже это было легко. Мы просто создали новую функцию внутри функции popeye и вызвали ее. Но обратите внимание, что вы смогли получить доступ к значению переменной ‘msg’ даже при том, что вы не объявили его внутри функции printMe. Это говорит о том, что вложенные функции имеют доступ к переменным, объявленным в области видимости внешней функции. И это вложение может продолжаться и дальше.

function slimShady(){
 var msg = 'I am slim shady';
  
 function iAmSlimShady(){
   
  function theRealSlimShady(){
   console.log(msg);
  };
  theRealSlimShady();
   
 };
  
 iAmSlimShady();
};
slimShady();

Хорошо, даже это довольно легко понять. Мы создаем функцию slim shady, которая объявляет и вызывает внутреннюю функцию iAmSlimShady, которая снова объявляет и создает внутреннюю функцию theRealSlimShady. Но история на этом не заканчивается. Независимо от глубины вложения, внутренняя функция может получить доступ к переменной, объявленной во внешней функции. Вот что такое закрытие. Способность внутренней функции динамически получать доступ к значениям переменных, объявленных во вмещающей функции, даже после того, как вмещающая функция завершила свое выполнение, называется замыканием. Концепция замыканий фактически становится очевидной, когда к внутренним ссылкам на функции обращаются вне контекста метода, в котором они были объявлены. Рассмотрим этот пример.

function printer(msg){
  
 function printMe(){
   console.log(msg);
 };
  
 return printMe;
};
var p = printer('Popeye says - Olive is mine');
var b = printer('Bluto says - Your Olive is mine');
 
p();
b();

Что вы видите в своей консоли ?? Чего ты ожидал? Если вы читали другие части серии, то вы будете знать, что я упоминал ранее, что функции — это не что иное, как объекты. Итак, мы можем просто вернуть ссылку на внутренний функционал из внешней функции, и это именно то, что мы сделали. Мы вернули функцию printMe из функции принтера. Таким образом, в нашем случае обе функции p () и b () ссылаются на одну и ту же функцию, т.е. на внутреннюю функцию printMe.

При первом вызове функции function printer, т.е. p, мы передали в другую строку для popeye. Во втором вызове мы передали строку для bluto. И так как p и b ссылаются на одну и ту же функцию — printMe, может возникнуть ощущение, что оба p () и b () должны вывести самое последнее значение «msg», которое было передано внешней функции.

К сожалению .. Или, возможно, с удовольствием, это не то, что происходит. Как выясняется, переменные, объявленные во внешней функции, могут быть доступны с помощью функции innner даже вне времени жизни внешней функции. Таким образом, даже несмотря на то, что после вызова функции принтера с аргументом для утверждения popeye и выполнения кода для внешней функции внутренняя функция сохранит скрытую ссылку на состояние переменной во внешней функции. В нашем случае переменная во внешней функции была не чем иным, как аргументом «msg», который является ничем иным, как локальной переменной в функции принтера.

Это означает, что если вы можете сохранить ссылку на внутреннюю функцию, вы можете искусственно увеличить срок службы локальной переменной, которая была объявлена ​​во внешней функции.

Игра замыканий становится немного хитрее, когда используется вместе с ключевым словом this. Давайте посмотрим, как это происходит, на примере, аналогичном тому, который мы видели выше.

function outer(name){
  
 this.name = name;
 console.log('Outer name : ' + this.name);
  
 function inner(){
   console.log("Inner Name : " + this.name);
 };
  
 inner();
};
outer('Popeye');
outer('Bluto');

Соблюдайте внимательно. Когда я вызывал внешнюю функцию без указания контекста, ключевое слово «this» во внешней функции ссылается на объект окна и присваивает значение «Popeye» свойству name объекта окна. Даже когда вызывается внутренняя функция, во время вызова контекст не указывается, значение «this» также устанавливается для объекта глобального окна, и мы можем просто напечатать имя.

Теперь давайте немного покрутим этот пример.

var holder = {};
holder.outer =function (name){
  
 this.name = name;
 console.log('Outer name : ' + this.name);
  
 function inner(){
   console.log("Inner Name : " + this.name);
 };
  
 inner();
};
holder.outer('Popeye');
holder.outer('Bluto');

Единственное отличие в этом случае заключалось в том, что вместо определения внешней функции как глобальной функции мы объявили ее как функцию для объекта «holder». Это небольшое изменение привело к радикальным изменениям в продукции. Внутренняя функция ничего не печатает на этот раз. Это то, что происходит вместо этого.

Когда вы находитесь во внешней функции, «this» относится к объекту-держателю. И вы устанавливаете свойство name на него. Просто. Но когда вы вызываете внутреннюю функцию, так как вы не указали никакого контекста, this внутри внутренней функции ссылается на объект окна. А поскольку вы не установили никакого значения для свойства с именем name в объекте window, все, что вы получите, — это неопределенное значение.

Давайте посмотрим на другой вариант приведенного выше примера.

var holder = {};
holder.outer =function (name){
  
 console.log('Outer name : ' + name);
  
 function inner(){
   console.log("Inner Name : " + name);
 };
  
 inner();
};
holder.outer('Popeye');
holder.outer('Bluto');

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

var holder = {};
holder.outer =function (name){
  
 console.log('Outer name : ' + name);
  
 function inner(){
   console.log("Inner Name : " + name);
 };
  
 return inner;
};
var p = holder.outer('Popeye');
var b = holder.outer('Bluto');
console.log('Invoking Inner functions');
p();
b();

Посмотрите на сделанные изменения. Прежде всего, функция возвращает ссылку на функцию внутренней функции. Итак, теперь оба объекта p и b ссылаются на одну и ту же внутреннюю функцию. Когда вы создаете p с помощью параметра «Popeye», для локальной переменной «name» устанавливается значение «Popeye». И это значение, которое доступно из внутренней функции. Но обратите внимание, что когда вы вызываете функцию с параметром ‘Bluto’, значение локальной переменной имеет значение CHANGED. Это важный момент, на который следует обратить внимание.

Позже, когда вы вызываете функцию p (), вы все равно можете получить доступ к значению, которое было передано, когда значением name было «Popeye». Это то, что делают замыкания. Когда вы находитесь в замыкании, внутренняя функция может «запомнить» старое значение внешней функции, которое находилось в ее области видимости. И всякий раз, когда вызывается внутренняя функция, она работает со ссылкой на то же самое внешнее значение. Таким образом, если у вас есть несколько ссылок на одну и ту же внутреннюю функцию, которая получает одно и то же внешнее значение, вы можете эффективно написать код, как если бы значения, принятые во внутренней функции, различались для разных вызовов одной и той же функции, как если бы они находились в совершенно разных прицелы.

Единственная переменная, для которой замыкания не эффективны, это ключевое слово this. Это потому, что каждая функция имеет различное значение ключевого слова this, которое зависит исключительно от способа вызова функции. Но что, если вы хотите использовать ссылку «this» вашей внешней функции внутри вашей внутренней функции? Довольно просто Вы оборачиваете это в переменную замыкания. Смотрите пример ниже.

var holder = {};
holder.outer =function (name){
 
 this.name=name;
 var that = this;
 console.log('Outer name : ' + name);
  
 function inner(){
   console.log("Inner Name : " + that.name);
 };
  
 return inner;
};
 
var p = holder.outer('Popeye');
p();

Это было довольно легко. Мы только что создали локальную переменную ‘that’ и присвоили ей значение this. И вот как печенье крошится!

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

Это все, что у меня есть для этой статьи. Я надеюсь, что вы сочли полезным. Есть еще несколько интересных вещей, которые я намереваюсь осветить в следующих статьях этой серии. Если вы чувствуете себя потерянным где-то, зайдите и посмотрите предыдущие части этой серии . Если нет, следите за обновлениями!