JS

closure and this

Closure

Замыкания — это функции, ссылающиеся на независимые (свободные) переменные. Другими словами, функция, определённая в замыкании, «запоминает» окружение, в котором она была создана.

Task

let name = "John";

function sayHi() {
  console.log("Hi, " + name);
}

name = "Pete";

sayHi(); // ??????

Что такое хостинг?

Task

function makeWorker() {
  let name = "Pete";

  return function() {
    console.log(name);
  };
}

let name = "John";

// create a function
let work = makeWorker();
// call it
work();  // ?????

// or

makeWorker()();

lexical scoping

  • Каждая функция при создании получает ссылку [[Scope]] на объект с переменными, в контексте которого была создана.
  • При запуске функции создаётся новый объект с переменными LexicalEnvironment. Он получает ссылку на внешний объект переменных из [[Scope]].
  • При поиске переменных он осуществляется сначала в текущем объекте переменных, а потом – по этой ссылке.

lexical scoping

Объект лексического окружения состоит из двух частей:

  • Environment Record – объект, в котором как свойства хранятся все локальные переменные (а также некоторая другая информация, такая как значение this).

  • Ссылка на внешнее лексическое окружение – то есть то, которое соответствует коду снаружи (снаружи от текущих фигурных скобок).

lexical scoping

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

 

Если переменная не была найдена, это будет ошибкой в strict mode. Без strict mode, для обратной совместимости, присваивание несуществующей переменной создаёт новую глобальную переменную с таким именем.

Task

let name = "John";

function sayHi() {
  console.log("Hi, " + name);
}

name = "Pete";

sayHi(); // ??????

Расскажите что происходит с использованием лексического окружения?

Task

Какую функцию называют вложенной?

Function

function sayHiBye(firstName, lastName) {
  
  // функция-помощник, которую мы используем ниже
  function getFullName() {
    return firstName + " " + lastName;
  }

  console.log( "Hello, " + getFullName() );
  console.log( "Bye, " + getFullName() );
}

sayHiBye('Vic', 'V');

Function

function sayHiBye(firstName, lastName) {

  // функция-помощник, которую мы используем ниже
  function getFullName() {
    return firstName + " " + lastName;
  }

  console.log( "Hello, " + getFullName() );
  console.log( "Bye, " + getFullName() );
}

Вложенная функция может быть возвращена: либо в качестве свойства нового объекта, либо сама по себе. И затем может быть использована в любом месте. Не важно где, она всё так же будет иметь доступ к тем же внешним переменным.

Closure

function makeFunc() {
  var name = "Mozilla";

  function displayName() {
    alert(name);
  }

  return displayName;
};

let myFunc = makeFunc();
myFunc()
// or
let myFunc = makeFunc()();

Замыкание – это функция вместе со всеми внешними переменными, которые ей доступны в момент вызова.

function makeFunc() {
  var name = "Mozilla";

  function displayName() {
    alert(name);
  }

  displayName();
};

let myFunc = makeFunc();

Closure

  • Все переменные и параметры функций являются свойствами объекта переменных LexicalEnvironment. Каждый запуск функции создает новый такой объект. На верхнем уровне им является «глобальный объект», в браузере – window.
  • При создании функция получает системное свойство [[Scope]], которое ссылается на LexicalEnvironment, в котором она была создана.
  • При вызове функции, куда бы её ни передали в коде – она будет искать переменные сначала у себя, а затем во внешних LexicalEnvironment с места своего «рождения».

Task

 Напишите counter

Closure

function makeCounter() {
  let count = 0;

  return function() {
    return count++; // есть доступ к внешней переменной "count"
  };
}

let counter = makeCounter();

console.log( counter() ); // 0
console.log( counter() ); // 1
console.log( counter() ); // 2

Task

Task

Если мы вызываем makeCounter несколько раз – нам возвращается много функций counter. Они независимы или разделяют одну и ту же переменную count?

Closure

function makeCounter() {
  let count = 0;
  return function() {
    return count++;
  };
}

let counter1 = makeCounter();
let counter2 = makeCounter();

console.log( counter1() ); // 0
console.log( counter1() ); // 1

console.log( counter2() ); // 0 (независимо)

Task

let firstFunc = function () {
    let index = 5;
     
    return function() {
        return index;
    };
};
     
let secondFunc = function() {
    let index = 15;
     
    console.log( firstFunc()() );
};
 
secondFunc(); // ?????

Closure

Обычно, говоря «замыкание функции», подразумевают не саму эту функцию, а именно внешние переменные.

Иногда говорят «переменная берётся из замыкания». Это означает – из внешнего объекта переменных.

Why we use Closure?

<button id="button0">save 1</button>
<button id="button1">save 2</button>
<button id="button2">save 3</button>

function t() {
    for (var i = 0; i < 3; i++) {
        document.getElementById('button' + i).onclick = function() {
            console.log(i);
        };
    }
}

t();

// click1 = 3
// click2 = 3
// click3 = 3

Проблема в том, что функции, присвоенные как обработчики события, являются замыканиями. Они состоят из описания функции и контекста исполнения (окружения), унаследованного от  функции t. Было создано три замыкания, но все они были созданы с одним и тем же контекстом исполнения (окружением). К моменту возникновения события onclick цикл уже давно отработал, а значит переменная i (одна и та же для всех трех замыканий) указывает на последний элемент массива, который как раз 3.

Why we use Closure?

function t() {
    for (var i = 0; i < 3; i++) {
        (function(){
            var index = i;
            document.getElementById('button' + index).onclick = function() {
                console.log(index + 1);
            };
        })();
    }
}

t();

// click1 = 1
// click2 = 2
// click3 = 3

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

Why we use Closure?

function makeHelpCallback(index) {
    return function() {
        console.log(index + 1);
    };
}

function t() {
    for (var i = 0; i < 3; i++) {
        document.getElementById('button' + i).onclick = makeHelpCallback(i);
    }
}

t();

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

this

Значение this называется контекстом вызова и будет определено в момент вызова функции.

this and object

var user = {
   name: 'Вася',
   getName: function() {
      return 'Вася';
   }
}

user.name;     // ???
user.getName();   // ???

Обьекты создаются для представления данных из реального мира.

Как пример это может быть корзина из онлайн магазина или информация о пользователе.

this and object

Когда мы пишем наш код, используя объекты для представления сущностей реального мира, – это называется объектно-ориентированное программирование или сокращённо: «ООП».

this and object

var a = {
   name: 'Вася',
   getName() {
      return 'Вася';
   }
}

a.name;     // ???
a.getName();   // ???

Сокращенная запись

this and object

var user = {
   name: 'Вася',
   age: 23,
   getObjInfo: function() {
      return this;
   },
   getName() {
     return this.name;
   }
}

user.getObjInfo(); // obj
user.getName();    // Вася

Для доступа к информации внутри объекта метод может использовать ключевое слово this.

this and object

var obj1 = {
  hello: function() {
    console.log('Hello world');
    return this;
  },
  obj2: {
      breed: 'dog',
      speak: function(){
            console.log('woof!');
            return this;
        }
    }
};
 
console.log(obj1);
console.log(obj1.hello());  // выводит 'Hello world' и возвращает obj1
console.log(obj1.obj2);
console.log(obj1.obj2.speak());  // выводит 'woof!' и возвращает obj2

Применение this во вложенных объектах может создать некоторую путаницу. В подобных ситуациях стоит помнить о том, что ключевое слово this относиться к тому объекту, в методе которого оно используется.

this

Значение this называется контекстом вызова и будет определено в момент вызова функции.

this

  1. Значение this вычисляется во время выполнения кода и зависит от контекста.
  2. Например, здесь одна и та же функция назначена двум разным объектам и имеет различное значение «this» при вызовах:
let user = { name: "Джон" };
let admin = { name: "Админ" };

function getName() {
  return this.name;
}

user.getName = getName;
admin.getName = getName;

user.getName(); // Джон  (this == user)
admin.getName(); // Админ  (this == admin)

this

Некоторые хитрые способы вызова метода приводят к потере значения this, например:

let user = {
  name: "Джон",
  hi: function() { 
    console.log(this.name); // ошибка
    console.log(this);      // window
  }
};

// разделим получение метода объекта и его вызов в разных строках
let hi = user.hi;
hi(); // ошибка

Здесь hi = user.hi сохраняет функцию в переменной, и далее в последней строке она вызывается полностью сама по себе, без объекта, так что нет this. Используется Reference Type.

this

У стрелочных функций нет this.

let user = {
  firstName: "Илья",
  sayHi: function() {
    let arrow = () => console.log(this.firstName);
    arrow();
  }
};

user.sayHi(); // Илья

Стрелочные функции особенные: у них нет своего «собственного» this. Если мы используем this внутри стрелочной функции, то его значение берётся из внешней «нормальной» функции.

this

У стрелочных функций нет this.

var objReg = {
  hello: function() {
    return this;
  }
};
 
var objArrow = {
    hello: () => this
};
 
objReg.hello(); // возвращает, как и ожидается, объект objReg 
objArrow.hello(); // возвращает объект Window!

this

Итого:

  • Функции, которые находятся в объекте в качестве его свойств, называются «методами».
  • Методы позволяют объектам «действовать»: object.doSomething().
  • Методы могут ссылаться на объект через this.

Значение this определяется во время исполнения кода.

  • При объявлении любой функции в ней можно использовать this, но этот this не имеет значения до тех пор, пока функция не будет вызвана.
  • Эта функция может быть скопирована между объектами (из одного объекта в другой).
  • Когда функция вызывается синтаксисом «метода» – object.method(), значением this во время вызова является объект перед точкой.

function this

console.log(this); // window

function myFunction() {
  console.log(this);
}

myFunction() // window

При использовании функций, которые имеются в глобальном контексте (это отличает их от методов объектов) ключевое слово this в них будет указывать на объект window.

Однако если функция выполняется в строгом режиме, то в this будет записано undefined

function this

var t1;

function test() {
    this.a = 13;

    return this;
}

t1 = test();

console.log(t1);   // window
console.log(t1.a); // 13

Ключевое слово new и this

let user;

function User() {
    this.age = 13;
    this.name = 'Vic';

    return this;
}

user = new User();
console.log(user);      // object
console.log(user.age);  // 13

Function constructor

Когда функцию-конструктор вызывают с использованием ключевого слова new, this в ней указывает на новый объект, который, с помощью конструктора, снабжают свойствами и методами.

Context and this

function GetFullTime() {
    this.time = new Date().getTime();

    console.log(this);
}

GetFullTime();     // window
new GetFullTime(); // свой this

this нам нужен для эмулирования областей видимости, например если у нас есть класс который отвечает за поведение кнопки, например кнопка с контекстным меню мы можем создать window.ContextMenuButton.link, но у нас возникает проблема когда таких кнопок на странице много и нам без ООП не обойтись. У каждого класса есть контекст вызова, своё состояние и так далее.

JS Closure

By Oleg Rovenskyi

JS Closure

Closure and this

  • 265