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
- Значение this вычисляется во время выполнения кода и зависит от контекста.
- Например, здесь одна и та же функция назначена двум разным объектам и имеет различное значение «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