4 вида путаницы с this в функциях

Sean, “4 Ways Functions Mess With this”, public translation into Russian from English More about this translation.

Translate into another language.

В 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;

};

Pages: ← previous Ctrl next
1 2

Original (English): 4 Ways Functions Mess With this

Translation: © Fenrir, m_vokhm, Алексей, ciiccii .

translated.by crowd

Like this translation? Share it or bookmark!