4 вида путаницы с this в функциях |
- Statistics
- Participants
- Translate into Russian
- Translation result
- Translation complete.
В JavaScript ключевое слово this может быть очень коварно. Эта происходит из-за разного поведения функций в зависимости от способа их вызова. Что? Функции можно вызывать по-разному? Ага! Есть 4 основных способа вызова функций. Посмотрим, как работает каждый, и как они обходятся с this.
1. Вызов метода
2. Вызов функции
3. Вызов конструктора
4. Вызов через apply
Вызов метода
Первый способ использования функций выглядит наиболее привычно. При переходе с классического языка программирования (C#, Java) вы наверняка привыкли использовать классы как единственные строительные блоки программы. Ничто не может существовать вне класса. У вас есть функции только как методы классов. И абсолютно ясно, к чему относится this.
Отлично, таким же образом работает вывоз метода в JavaScript. Если вы определите объект Object, и сделаете одно из его свойств функцией, затем вызовете этот метод, тогда this будет объектом Object, к которому принадлежит эта функция.
var Obj = {
something: 'строка',
changeSomething: function() {
//this == Obj
return this.something.toUpperCase();
}
};
Obj.changeSomething(); //вернёт 'СТРОКА'
var Obj = {
something: 'строка',
changeSomething: function() {
//this == Obj
return this.something.toUpperCase();
}
};
Obj.changeSomething(); //вернёт 'СТРОКА'
Когда бы мы ни вызвали changeSomething объекта Obj таким способом, мы знаем, что это будет Obj, и нет необходимости постоянно повторять название объекта. Это также будет верно, если мы клонируем объект. this будет указывать на объект, владеющий методом.
Вызов функции
Второй способ может выглядеть немного странно, и его поведение также может показаться странным, но, как только вы лучше поймете особенности JavaScript, вы увидите логику такого поведения (точно-точно!). Следующий способ обычно называется «вызов функции», он заключается в определении функции самой по себе, а не как метода класса. В JavaScript это возможно, поскольку функции являются объектами первого рода. Их можно передавать как аргументы, сохранять в переменных, и делать прочие классные вещи. Предположим, что мы всё это знаем.
Когда мы объявляем функцию, не связанную с объектом, при вызове такой функции this связывается с глобальным объектом (в большинстве случаев это window). В основном потому, что каждая функция должна быть привязана к объекту. Если она не привязана к объекту другими способами вызова, то по-умолчанию этим объектом является объект верхнего уровня. Это кажется достаточно ясным при объявлении глобальных функций.
var logWindow = function() {
//this == window
console.log(this);
};
// оба способа одинаковы
function logWindow() {
//this == window
console.log(this);
}
logWindow() // выводит объект window в Firebug
var logWindow = function() {
//this == window
console.log(this);
};
// оба способа одинаковы
function logWindow() {
//this == window
console.log(this);
}
logWindow() // выводит объект window в Firebug
Тем не менее, такое поведение сохраняется где бы вы не определили такую функцию, а не только на глобальном уровне. Это создаёт людям большие проблемы. Предположим, что у нас есть функция объекта, и вызовем метод. Мы знаем, что this будет связано с объектом-владельцем. Но JavaScript позволяет объявлять функции внутри функций. Это можно делать для создания обработчиков событий, обратных вызовов, или просто служебных функций, которые используются много раз в методе. И внутри таких функций this больше не связан с объектом-владельцем. На самом деле, внутри этих областей видимости this связывается с глобальным объектом.
// упс
var Thing = {
name: 'Я принадлежал Thing',
woops: function() {
//this == Thing
var concat = function(first,second) {
//this == window
this.name = first + second;
};
concat(this.name, ', но только что изменил window!');
}
};
Thing.woops(); //устанавливает window.name = 'Я принадлежал Thing, но только что изменил window!'
Здесь метод woops определяет внутреннюю функцию (что очевидно), помогающую получению результата. Он передаёт ей 2 строки и обращается к this, которым, как мы надеялись, является Thing. Но затем внутренняя функция concat изменяет свойство объекта this. Итак, в этой функции this привязано к window, так что мы начали портить свойства window. Просто замечательно.
Решение этой проблемы не такое уж и трудное. Большинство просто сохраняют this в другой переменной до вызова внутренней функции, например self, и затем, внутри используем self вместо this. Тот же пример с использованием этого способа:
//решение с self
var Thing = {
name: 'я принадлежал Thing',
woops: function() {
//this == Thing
var self = this;
var concat = function(first,second) {
//this == window
//self == Thing
self.name = first + second;
};
concat(this.name, ', и до сих пор принадлежу. Уау!');
}
};
Thing.woops(); //устанавливает Thing.name = Я принадлежал Thing, и до сих пор принадлежу. Уау!'
Вызов конструктора
Третий путь — использовать функцию как конструктор — нарушает все правила предыдущих двух способов. Когда вы используете оператор new на функции, то во время выполнения этой функции this связан с только что созданным объектом. Этот объект получает прототип функции как прототип объекта. И любое использование this внутри этого конструктора будет изменять ваш новый объект во время его создания.
Этот способ довольно привычный, если вы привыкли к классическим языкам. Мы можем написать функцию, предназначенную для создания новых объектов. Эта функция будет выполнять команды на только что созданном объекте и вернёт его.
var Dog = function(name) {
//this == совершенно новый object ({});
this.name = name;
this.age = (Math.random() * 5) + 1;
};
var myDog = new Dog('Спайк');
//myDog.name == 'Спайк'
//myDog.age == 2
var yourDog = new Dog('Спот');
//yourDog.name == 'Спот'
//yourDog.age == 4
Когда myDog присваевается new Dog, this применяется в отношении нового объекта myDog. Позже, когда мы присваеваем new Dog переменной yourDog, this применяется уже к yourDog. Ясно?
Изящность состоит в том, что такая функция может находиться где угодно. Это может быть внутренняя функция, или глобальная либо метод объекта. Когда вы используете принцип конструктора, изменяется способ работы функции.
Однако, нужно предостеречь. Если вы создали функцию-конструктор, и использовали ее, но случайно пропустили ключевое слово new, то механизм связывания этой функции будет нарушен. В других классических языках вы бы получили ошибку компилятора или что-нибудь подобное в таком случае. А в Javascript вы просто испортите объект, которому принадлежит функция, будь то какой-то определённый объект или window.
var Dog = function(name) {
this.name = name;
this.age = (Math.random() * 5) + 1;
};
Original (English): 4 Ways Functions Mess With this
